]> WPIA git - gigi.git/blob - src/org/cacert/gigi/dbObjects/User.java
ADD: user group management (with testcase)
[gigi.git] / src / org / cacert / gigi / dbObjects / User.java
1 package org.cacert.gigi.dbObjects;
2
3 import java.sql.Date;
4 import java.sql.PreparedStatement;
5 import java.sql.ResultSet;
6 import java.sql.SQLException;
7 import java.util.Calendar;
8 import java.util.Collections;
9 import java.util.HashSet;
10 import java.util.Locale;
11 import java.util.Set;
12
13 import org.cacert.gigi.GigiApiException;
14 import org.cacert.gigi.database.DatabaseConnection;
15 import org.cacert.gigi.localisation.Language;
16 import org.cacert.gigi.util.Notary;
17 import org.cacert.gigi.util.PasswordHash;
18 import org.cacert.gigi.util.PasswordStrengthChecker;
19
20 public class User implements IdCachable {
21
22     private int id;
23
24     private Name name = new Name(null, null, null, null);
25
26     private Date dob;
27
28     private String email;
29
30     private Assurance[] receivedAssurances, madeAssurances;
31
32     private Locale locale;
33
34     private Set<Group> groups = new HashSet<>();
35
36     private User(int id) {
37         this.id = id;
38         updateName(id);
39     }
40
41     private void updateName(int id) {
42         try {
43             PreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `fname`, `lname`,`mname`, `suffix`, `dob`, `email`, `language` FROM `users` WHERE id=?");
44             ps.setInt(1, id);
45             ResultSet rs = ps.executeQuery();
46             if (rs.next()) {
47                 name = new Name(rs.getString(1), rs.getString(2), rs.getString(3), rs.getString(4));
48                 dob = rs.getDate(5);
49                 email = rs.getString(6);
50                 String localeStr = rs.getString(7);
51                 if (localeStr == null || localeStr.equals("")) {
52                     locale = Locale.getDefault();
53                 } else {
54                     locale = Language.getLocaleFromString(localeStr);
55                 }
56             }
57             rs.close();
58             PreparedStatement psg = DatabaseConnection.getInstance().prepare("SELECT permission FROM user_groups WHERE user=? AND deleted is NULL");
59             psg.setInt(1, id);
60             ResultSet rs2 = psg.executeQuery();
61             while (rs2.next()) {
62                 groups.add(Group.getByString(rs2.getString(1)));
63             }
64             rs2.close();
65         } catch (SQLException e) {
66             e.printStackTrace();
67         }
68     }
69
70     public User() {}
71
72     public int getId() {
73         return id;
74     }
75
76     public String getFname() {
77         return name.fname;
78     }
79
80     public String getLname() {
81         return name.lname;
82     }
83
84     public String getMname() {
85         return name.mname;
86     }
87
88     public Name getName() {
89         return name;
90     }
91
92     public void setMname(String mname) {
93         this.name.mname = mname;
94     }
95
96     public String getSuffix() {
97         return name.suffix;
98     }
99
100     public void setSuffix(String suffix) {
101         this.name.suffix = suffix;
102     }
103
104     public Date getDob() {
105         return dob;
106     }
107
108     public void setDob(Date dob) {
109         this.dob = dob;
110     }
111
112     public String getEmail() {
113         return email;
114     }
115
116     public void setEmail(String email) {
117         this.email = email;
118     }
119
120     public void setId(int id) {
121         this.id = id;
122     }
123
124     public void setFname(String fname) {
125         this.name.fname = fname;
126     }
127
128     public void setLname(String lname) {
129         this.name.lname = lname;
130     }
131
132     public void insert(String password) throws SQLException {
133         if (id != 0) {
134             throw new Error("refusing to insert");
135         }
136         PreparedStatement query = DatabaseConnection.getInstance().prepare("insert into `users` set `email`=?, `password`=?, " + "`fname`=?, `mname`=?, `lname`=?, " + "`suffix`=?, `dob`=?, `created`=NOW(), locked=0, `language`=?");
137         query.setString(1, email);
138         query.setString(2, PasswordHash.hash(password));
139         query.setString(3, name.fname);
140         query.setString(4, name.mname);
141         query.setString(5, name.lname);
142         query.setString(6, name.suffix);
143         query.setDate(7, new java.sql.Date(dob.getTime()));
144         query.setString(8, locale.toString());
145         synchronized (User.class) {
146             query.execute();
147             id = DatabaseConnection.lastInsertId(query);
148             myCache.put(this);
149         }
150     }
151
152     public void changePassword(String oldPass, String newPass) throws GigiApiException {
153         try {
154             PreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `password` FROM users WHERE id=?");
155             ps.setInt(1, id);
156             ResultSet rs = ps.executeQuery();
157             if ( !rs.next()) {
158                 throw new GigiApiException("User not found... very bad.");
159             }
160             if ( !PasswordHash.verifyHash(oldPass, rs.getString(1))) {
161                 throw new GigiApiException("Old password does not match.");
162             }
163             rs.close();
164             PasswordStrengthChecker.assertStrongPassword(newPass, this);
165             ps = DatabaseConnection.getInstance().prepare("UPDATE users SET `password`=? WHERE id=?");
166             ps.setString(1, PasswordHash.hash(newPass));
167             ps.setInt(2, id);
168             if (ps.executeUpdate() != 1) {
169                 throw new GigiApiException("Password update failed.");
170             }
171         } catch (SQLException e) {
172             throw new GigiApiException(e);
173         }
174     }
175
176     public boolean canAssure() throws SQLException {
177         if ( !isOfAge(14)) { // PoJAM
178             return false;
179         }
180         if (getAssurancePoints() < 100) {
181             return false;
182         }
183
184         return hasPassedCATS();
185
186     }
187
188     public boolean hasPassedCATS() throws SQLException {
189         PreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT 1 FROM `cats_passed` where `user_id`=?");
190         query.setInt(1, id);
191         ResultSet rs = query.executeQuery();
192         if (rs.next()) {
193             return true;
194         } else {
195             return false;
196         }
197     }
198
199     public int getAssurancePoints() throws SQLException {
200         PreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT sum(points) FROM `notary` where `to`=? AND `deleted`=0");
201         query.setInt(1, id);
202         ResultSet rs = query.executeQuery();
203         int points = 0;
204         if (rs.next()) {
205             points = rs.getInt(1);
206         }
207         rs.close();
208         return points;
209     }
210
211     public int getExperiencePoints() throws SQLException {
212         PreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT count(*) FROM `notary` where `from`=? AND `deleted`=0");
213         query.setInt(1, id);
214         ResultSet rs = query.executeQuery();
215         int points = 0;
216         if (rs.next()) {
217             points = rs.getInt(1) * 2;
218         }
219         rs.close();
220         return points;
221     }
222
223     @Override
224     public boolean equals(Object obj) {
225         if ( !(obj instanceof User)) {
226             return false;
227         }
228         User s = (User) obj;
229         return name.equals(s.name) && email.equals(s.email) && dob.toString().equals(s.dob.toString()); // This
230                                                                                                         // is
231                                                                                                         // due
232                                                                                                         // to
233                                                                                                         // day
234                                                                                                         // cutoff
235     }
236
237     /**
238      * Gets the maximum allowed points NOW. Note that an assurance needs to
239      * re-check PoJam as it has taken place in the past.
240      * 
241      * @return the maximal points
242      * @throws SQLException
243      */
244     public int getMaxAssurePoints() throws SQLException {
245         if ( !isOfAge(18)) {
246             return 10; // PoJAM
247         }
248
249         int exp = getExperiencePoints();
250         int points = 10;
251
252         if (exp >= 10) {
253             points += 5;
254         }
255         if (exp >= 20) {
256             points += 5;
257         }
258         if (exp >= 30) {
259             points += 5;
260         }
261         if (exp >= 40) {
262             points += 5;
263         }
264         if (exp >= 50) {
265             points += 5;
266         }
267         return points;
268     }
269
270     public boolean isOfAge(int desiredAge) {
271         Calendar c = Calendar.getInstance();
272         c.setTime(dob);
273         int year = c.get(Calendar.YEAR);
274         int month = c.get(Calendar.MONTH);
275         int day = c.get(Calendar.DAY_OF_MONTH);
276         c.set(year, month, day);
277         c.add(Calendar.YEAR, desiredAge);
278         return System.currentTimeMillis() >= c.getTime().getTime();
279     }
280
281     public EmailAddress[] getEmails() {
282         try {
283             PreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id FROM emails WHERE memid=? AND deleted=0");
284             ps.setInt(1, id);
285             ResultSet rs = ps.executeQuery();
286             rs.last();
287             int count = rs.getRow();
288             EmailAddress[] data = new EmailAddress[count];
289             rs.beforeFirst();
290             for (int i = 0; i < data.length; i++) {
291                 if ( !rs.next()) {
292                     throw new Error("Internal sql api violation.");
293                 }
294                 data[i] = EmailAddress.getById(rs.getInt(1));
295             }
296             rs.close();
297             return data;
298         } catch (SQLException e) {
299             e.printStackTrace();
300         }
301
302         return null;
303     }
304
305     public Domain[] getDomains() {
306         try {
307             PreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id FROM domains WHERE memid=? AND deleted IS NULL");
308             ps.setInt(1, id);
309             ResultSet rs = ps.executeQuery();
310             rs.last();
311             int count = rs.getRow();
312             Domain[] data = new Domain[count];
313             rs.beforeFirst();
314             for (int i = 0; i < data.length; i++) {
315                 if ( !rs.next()) {
316                     throw new Error("Internal sql api violation.");
317                 }
318                 data[i] = Domain.getById(rs.getInt(1));
319             }
320             rs.close();
321             return data;
322         } catch (SQLException e) {
323             e.printStackTrace();
324         }
325
326         return null;
327     }
328
329     public Certificate[] getCertificates() {
330         try {
331             PreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT serial FROM certs WHERE memid=? AND revoked=0");
332             ps.setInt(1, id);
333             ResultSet rs = ps.executeQuery();
334             rs.last();
335             int count = rs.getRow();
336             Certificate[] data = new Certificate[count];
337             rs.beforeFirst();
338             for (int i = 0; i < data.length; i++) {
339                 if ( !rs.next()) {
340                     throw new Error("Internal sql api violation.");
341                 }
342                 data[i] = Certificate.getBySerial(rs.getString(1));
343             }
344             rs.close();
345             return data;
346         } catch (SQLException e) {
347             e.printStackTrace();
348         }
349
350         return null;
351     }
352
353     public boolean isValidDomain(String domainname) {
354         for (Domain d : getDomains()) {
355             String sfx = d.getSuffix();
356             if (domainname.equals(sfx) || domainname.endsWith("." + sfx)) {
357                 return true;
358             }
359         }
360         return false;
361     }
362
363     public boolean isValidEmail(String email) {
364         for (EmailAddress em : getEmails()) {
365             if (em.getAddress().equals(email)) {
366                 return true;
367             }
368         }
369         return false;
370     }
371
372     public boolean isValidName(String name) {
373         return getName().matches(name);
374     }
375
376     public void updateDefaultEmail(EmailAddress newMail) throws GigiApiException {
377         try {
378             EmailAddress[] adrs = getEmails();
379             for (int i = 0; i < adrs.length; i++) {
380                 if (adrs[i].getAddress().equals(newMail.getAddress())) {
381                     if ( !adrs[i].isVerified()) {
382                         throw new GigiApiException("Email not verified.");
383                     }
384                     PreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE users SET email=? WHERE id=?");
385                     ps.setString(1, newMail.getAddress());
386                     ps.setInt(2, getId());
387                     ps.execute();
388                     email = newMail.getAddress();
389                     return;
390                 }
391             }
392             throw new GigiApiException("Given address not an address of the user.");
393         } catch (SQLException e) {
394             throw new GigiApiException(e);
395         }
396     }
397
398     public void deleteEmail(EmailAddress mail) throws GigiApiException {
399         if (getEmail().equals(mail.getAddress())) {
400             throw new GigiApiException("Can't delete user's default e-mail.");
401         }
402         EmailAddress[] emails = getEmails();
403         for (int i = 0; i < emails.length; i++) {
404             if (emails[i].getId() == mail.getId()) {
405                 try {
406                     PreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE emails SET deleted=? WHERE id=?");
407                     ps.setDate(1, new Date(System.currentTimeMillis()));
408                     ps.setInt(2, mail.getId());
409                     ps.execute();
410                 } catch (SQLException e) {
411                     e.printStackTrace();
412                     throw new GigiApiException(e);
413                 }
414                 return;
415             }
416         }
417         throw new GigiApiException("Email not one of user's email addresses.");
418     }
419
420     public Assurance[] getReceivedAssurances() throws SQLException {
421         if (receivedAssurances == null) {
422             PreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT * FROM notary WHERE `to`=? AND deleted=0");
423             query.setInt(1, getId());
424             ResultSet res = query.executeQuery();
425             res.last();
426             Assurance[] assurances = new Assurance[res.getRow()];
427             res.beforeFirst();
428             for (int i = 0; i < assurances.length; i++) {
429                 res.next();
430                 assurances[i] = new Assurance(res);
431             }
432             this.receivedAssurances = assurances;
433             return assurances;
434         }
435         return receivedAssurances;
436     }
437
438     public Assurance[] getMadeAssurances() throws SQLException {
439         if (madeAssurances == null) {
440             PreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT * FROM notary WHERE `from`=? AND deleted=0");
441             query.setInt(1, getId());
442             ResultSet res = query.executeQuery();
443             res.last();
444             Assurance[] assurances = new Assurance[res.getRow()];
445             res.beforeFirst();
446             for (int i = 0; i < assurances.length; i++) {
447                 res.next();
448                 assurances[i] = new Assurance(res);
449             }
450             this.madeAssurances = assurances;
451             return assurances;
452         }
453         return madeAssurances;
454     }
455
456     public void invalidateMadeAssurances() {
457         madeAssurances = null;
458     }
459
460     public void invalidateReceivedAssurances() {
461         receivedAssurances = null;
462     }
463
464     public void updateUserData() throws SQLException, GigiApiException {
465         synchronized (Notary.class) {
466             if (getAssurancePoints() != 0) {
467                 throw new GigiApiException("No change after assurance allowed.");
468             }
469             PreparedStatement update = DatabaseConnection.getInstance().prepare("UPDATE users SET fname=?, lname=?, mname=?, suffix=?, dob=? WHERE id=?");
470             update.setString(1, getFname());
471             update.setString(2, getLname());
472             update.setString(3, getMname());
473             update.setString(4, getSuffix());
474             update.setDate(5, getDob());
475             update.setInt(6, getId());
476             update.executeUpdate();
477         }
478     }
479
480     public Locale getPreferredLocale() {
481         return locale;
482     }
483
484     public void setPreferredLocale(Locale locale) {
485         this.locale = locale;
486
487     }
488
489     public boolean wantsDirectoryListing() throws SQLException {
490         PreparedStatement get = DatabaseConnection.getInstance().prepare("SELECT listme FROM users WHERE id=?");
491         get.setInt(1, getId());
492         ResultSet exec = get.executeQuery();
493         exec.next();
494         return exec.getBoolean("listme");
495     }
496
497     public String getContactInformation() throws SQLException {
498         PreparedStatement get = DatabaseConnection.getInstance().prepare("SELECT contactinfo FROM users WHERE id=?");
499         get.setInt(1, getId());
500         ResultSet exec = get.executeQuery();
501         exec.next();
502         return exec.getString("contactinfo");
503     }
504
505     public void setDirectoryListing(boolean on) throws SQLException {
506         PreparedStatement update = DatabaseConnection.getInstance().prepare("UPDATE users SET listme = ? WHERE id = ?");
507         update.setBoolean(1, on);
508         update.setInt(2, getId());
509         update.executeUpdate();
510     }
511
512     public void setContactInformation(String contactInfo) throws SQLException {
513         PreparedStatement update = DatabaseConnection.getInstance().prepare("UPDATE users SET contactinfo = ? WHERE id = ?");
514         update.setString(1, contactInfo);
515         update.setInt(2, getId());
516         update.executeUpdate();
517     }
518
519     public boolean isInGroup(Group g) {
520         return groups.contains(g);
521     }
522
523     public Set<Group> getGroups() {
524         return Collections.unmodifiableSet(groups);
525     }
526
527     public void grantGroup(User granter, Group toGrant) throws GigiApiException {
528         groups.add(toGrant);
529         try {
530             PreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO user_groups SET user=?, permission=?, grantedby=?");
531             ps.setInt(1, getId());
532             ps.setString(2, toGrant.getDatabaseName());
533             ps.setInt(3, granter.getId());
534             ps.execute();
535         } catch (SQLException e) {
536             throw new GigiApiException(e);
537         }
538     }
539
540     public void revokeGroup(User revoker, Group toRevoke) throws GigiApiException {
541         groups.remove(toRevoke);
542         try {
543             PreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE user_groups SET deleted=CURRENT_TIMESTAMP, revokedby=? WHERE deleted is NULL AND permission=? AND user=?");
544             ps.setInt(1, revoker.getId());
545             ps.setString(2, toRevoke.getDatabaseName());
546             ps.setInt(3, getId());
547             ps.execute();
548         } catch (SQLException e) {
549             throw new GigiApiException(e);
550         }
551     }
552
553     private static ObjectCache<User> myCache = new ObjectCache<>();
554
555     public static synchronized User getById(int id) {
556         User u = myCache.get(id);
557         if (u == null) {
558             myCache.put(u = new User(id));
559         }
560         return u;
561     }
562 }