]> WPIA git - gigi.git/blob - src/org/cacert/gigi/dbObjects/User.java
8e9bc762b227d88844edf05c0028b41df1488224
[gigi.git] / src / org / cacert / gigi / dbObjects / User.java
1 package org.cacert.gigi.dbObjects;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.HashSet;
6 import java.util.LinkedList;
7 import java.util.List;
8 import java.util.Locale;
9 import java.util.Set;
10
11 import org.cacert.gigi.GigiApiException;
12 import org.cacert.gigi.database.GigiPreparedStatement;
13 import org.cacert.gigi.database.GigiResultSet;
14 import org.cacert.gigi.dbObjects.CATS.CATSType;
15 import org.cacert.gigi.localisation.Language;
16 import org.cacert.gigi.output.DateSelector;
17 import org.cacert.gigi.pages.PasswordResetPage;
18 import org.cacert.gigi.util.CalendarUtil;
19 import org.cacert.gigi.util.DayDate;
20 import org.cacert.gigi.util.Notary;
21 import org.cacert.gigi.util.PasswordHash;
22 import org.cacert.gigi.util.PasswordStrengthChecker;
23
24 /**
25  * Represents an acting, assurable, user. Synchronizing on user means: no
26  * name-change and no assurance.
27  */
28 public class User extends CertificateOwner {
29
30     private Name name = new Name(null, null, null, null);
31
32     private DayDate dob;
33
34     private String email;
35
36     private Assurance[] receivedAssurances;
37
38     private Assurance[] madeAssurances;
39
40     private Locale locale;
41
42     private final Set<Group> groups = new HashSet<>();
43
44     public static final int MINIMUM_AGE = 16;
45
46     public static final int POJAM_AGE = 14;
47
48     public static final int ADULT_AGE = 18;
49
50     public static final boolean POJAM_ENABLED = false;
51
52     /**
53      * Time in months a verification is considered "recent".
54      */
55     public static final int VERIFICATION_MONTHS = 39;
56
57     protected User(GigiResultSet rs) {
58         super(rs.getInt("id"));
59         updateName(rs);
60     }
61
62     private void updateName(GigiResultSet rs) {
63         name = new Name(rs.getString("fname"), rs.getString("lname"), rs.getString("mname"), rs.getString("suffix"));
64         dob = new DayDate(rs.getDate("dob"));
65         email = rs.getString("email");
66
67         String localeStr = rs.getString("language");
68         if (localeStr == null || localeStr.equals("")) {
69             locale = Locale.getDefault();
70         } else {
71             locale = Language.getLocaleFromString(localeStr);
72         }
73
74         try (GigiPreparedStatement psg = new GigiPreparedStatement("SELECT `permission` FROM `user_groups` WHERE `user`=? AND `deleted` is NULL")) {
75             psg.setInt(1, rs.getInt("id"));
76
77             try (GigiResultSet rs2 = psg.executeQuery()) {
78                 while (rs2.next()) {
79                     groups.add(Group.getByString(rs2.getString(1)));
80                 }
81             }
82         }
83     }
84
85     public User(String email, String password, Name name, DayDate dob, Locale locale) throws GigiApiException {
86         this.email = email;
87         this.dob = dob;
88         this.name = name;
89         this.locale = locale;
90         try (GigiPreparedStatement query = new GigiPreparedStatement("INSERT INTO `users` SET `email`=?, `password`=?, " + "`fname`=?, `mname`=?, `lname`=?, " + "`suffix`=?, `dob`=?, `language`=?, id=?")) {
91             query.setString(1, email);
92             query.setString(2, PasswordHash.hash(password));
93             query.setString(3, name.getFname());
94             query.setString(4, name.getMname());
95             query.setString(5, name.getLname());
96             query.setString(6, name.getSuffix());
97             query.setDate(7, dob.toSQLDate());
98             query.setString(8, locale.toString());
99             query.setInt(9, getId());
100             query.execute();
101         }
102         new EmailAddress(this, email, locale);
103     }
104
105     public Name getName() {
106         return name;
107     }
108
109     public DayDate getDoB() {
110         return dob;
111     }
112
113     public void setDoB(DayDate dob) {
114         this.dob = dob;
115     }
116
117     public String getEmail() {
118         return email;
119     }
120
121     public void changePassword(String oldPass, String newPass) throws GigiApiException {
122         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `password` FROM `users` WHERE `id`=?")) {
123             ps.setInt(1, getId());
124             try (GigiResultSet rs = ps.executeQuery()) {
125                 if ( !rs.next()) {
126                     throw new GigiApiException("User not found... very bad.");
127                 }
128                 if (PasswordHash.verifyHash(oldPass, rs.getString(1)) == null) {
129                     throw new GigiApiException("Old password does not match.");
130                 }
131             }
132         }
133         setPassword(newPass);
134     }
135
136     private void setPassword(String newPass) throws GigiApiException {
137         PasswordStrengthChecker.assertStrongPassword(newPass, getName(), getEmail());
138         try (GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE users SET `password`=? WHERE id=?")) {
139             ps.setString(1, PasswordHash.hash(newPass));
140             ps.setInt(2, getId());
141             ps.executeUpdate();
142         }
143     }
144
145     public void setName(Name name) {
146         this.name = name;
147     }
148
149     public boolean canAssure() {
150         if (POJAM_ENABLED) {
151             if ( !CalendarUtil.isOfAge(dob, POJAM_AGE)) { // PoJAM
152                 return false;
153             }
154         } else {
155             if ( !CalendarUtil.isOfAge(dob, ADULT_AGE)) {
156                 return false;
157             }
158         }
159         if (getAssurancePoints() < 100) {
160             return false;
161         }
162
163         return hasPassedCATS();
164
165     }
166
167     public boolean hasPassedCATS() {
168         try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT 1 FROM `cats_passed` where `user_id`=? AND `variant_id`=?")) {
169             query.setInt(1, getId());
170             query.setInt(2, CATSType.ASSURER_CHALLENGE.getId());
171             try (GigiResultSet rs = query.executeQuery()) {
172                 if (rs.next()) {
173                     return true;
174                 } else {
175                     return false;
176                 }
177             }
178         }
179     }
180
181     public int getAssurancePoints() {
182         try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT sum(points) FROM `notary` where `to`=? AND `deleted` is NULL AND (`expire` IS NULL OR `expire` > CURRENT_TIMESTAMP)")) {
183             query.setInt(1, getId());
184
185             GigiResultSet rs = query.executeQuery();
186             int points = 0;
187
188             if (rs.next()) {
189                 points = rs.getInt(1);
190             }
191
192             return points;
193         }
194     }
195
196     public int getExperiencePoints() {
197         try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT count(*) FROM `notary` where `from`=? AND `deleted` is NULL")) {
198             query.setInt(1, getId());
199
200             GigiResultSet rs = query.executeQuery();
201             int points = 0;
202
203             if (rs.next()) {
204                 points = rs.getInt(1) * 2;
205             }
206
207             return points;
208         }
209     }
210
211     /**
212      * Gets the maximum allowed points NOW. Note that an assurance needs to
213      * re-check PoJam as it has taken place in the past.
214      * 
215      * @return the maximal points @
216      */
217     public int getMaxAssurePoints() {
218         if ( !CalendarUtil.isOfAge(dob, ADULT_AGE) && POJAM_ENABLED) {
219             return 10; // PoJAM
220         }
221
222         int exp = getExperiencePoints();
223         int points = 10;
224
225         if (exp >= 10) {
226             points += 5;
227         }
228         if (exp >= 20) {
229             points += 5;
230         }
231         if (exp >= 30) {
232             points += 5;
233         }
234         if (exp >= 40) {
235             points += 5;
236         }
237         if (exp >= 50) {
238             points += 5;
239         }
240
241         return points;
242     }
243
244     public boolean isValidName(String name) {
245         return getName().matches(name);
246     }
247
248     public void updateDefaultEmail(EmailAddress newMail) throws GigiApiException {
249         for (EmailAddress email : getEmails()) {
250             if (email.getAddress().equals(newMail.getAddress())) {
251                 if ( !email.isVerified()) {
252                     throw new GigiApiException("Email not verified.");
253                 }
254
255                 try (GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE users SET email=? WHERE id=?")) {
256                     ps.setString(1, newMail.getAddress());
257                     ps.setInt(2, getId());
258                     ps.execute();
259                 }
260
261                 this.email = newMail.getAddress();
262                 return;
263             }
264         }
265
266         throw new GigiApiException("Given address not an address of the user.");
267     }
268
269     public void deleteEmail(EmailAddress delMail) throws GigiApiException {
270         if (getEmail().equals(delMail.getAddress())) {
271             throw new GigiApiException("Can't delete user's default e-mail.");
272         }
273
274         for (EmailAddress email : getEmails()) {
275             if (email.getId() == delMail.getId()) {
276                 try (GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `emails` SET `deleted`=CURRENT_TIMESTAMP WHERE `id`=?")) {
277                     ps.setInt(1, delMail.getId());
278                     ps.execute();
279                 }
280                 return;
281             }
282         }
283         throw new GigiApiException("Email not one of user's email addresses.");
284     }
285
286     public synchronized Assurance[] getReceivedAssurances() {
287         if (receivedAssurances == null) {
288             try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT * FROM `notary` WHERE `to`=? AND `deleted` IS NULL")) {
289                 query.setInt(1, getId());
290
291                 GigiResultSet res = query.executeQuery();
292                 List<Assurance> assurances = new LinkedList<Assurance>();
293
294                 while (res.next()) {
295                     assurances.add(assuranceByRes(res));
296                 }
297
298                 this.receivedAssurances = assurances.toArray(new Assurance[0]);
299             }
300         }
301
302         return receivedAssurances;
303     }
304
305     public synchronized Assurance[] getMadeAssurances() {
306         if (madeAssurances == null) {
307             try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT * FROM notary WHERE `from`=? AND deleted is NULL")) {
308                 query.setInt(1, getId());
309
310                 try (GigiResultSet res = query.executeQuery()) {
311                     List<Assurance> assurances = new LinkedList<Assurance>();
312
313                     while (res.next()) {
314                         assurances.add(assuranceByRes(res));
315                     }
316
317                     this.madeAssurances = assurances.toArray(new Assurance[0]);
318                 }
319             }
320         }
321
322         return madeAssurances;
323     }
324
325     public synchronized void invalidateMadeAssurances() {
326         madeAssurances = null;
327     }
328
329     public synchronized void invalidateReceivedAssurances() {
330         receivedAssurances = null;
331     }
332
333     public void updateUserData() throws GigiApiException {
334         synchronized (Notary.class) {
335             if (getReceivedAssurances().length != 0) {
336                 throw new GigiApiException("No change after assurance allowed.");
337             }
338             rawUpdateUserData();
339         }
340     }
341
342     protected void rawUpdateUserData() {
343         try (GigiPreparedStatement update = new GigiPreparedStatement("UPDATE users SET fname=?, lname=?, mname=?, suffix=?, dob=? WHERE id=?")) {
344             update.setString(1, name.getFname());
345             update.setString(2, name.getLname());
346             update.setString(3, name.getMname());
347             update.setString(4, name.getSuffix());
348             update.setDate(5, getDoB().toSQLDate());
349             update.setInt(6, getId());
350             update.executeUpdate();
351         }
352     }
353
354     public Locale getPreferredLocale() {
355         return locale;
356     }
357
358     public void setPreferredLocale(Locale locale) {
359         this.locale = locale;
360
361     }
362
363     public boolean wantsDirectoryListing() {
364         try (GigiPreparedStatement get = new GigiPreparedStatement("SELECT listme FROM users WHERE id=?")) {
365             get.setInt(1, getId());
366             GigiResultSet exec = get.executeQuery();
367             return exec.next() && exec.getBoolean("listme");
368         }
369     }
370
371     public String getContactInformation() {
372         try (GigiPreparedStatement get = new GigiPreparedStatement("SELECT contactinfo FROM users WHERE id=?")) {
373             get.setInt(1, getId());
374
375             GigiResultSet exec = get.executeQuery();
376             exec.next();
377             return exec.getString("contactinfo");
378         }
379     }
380
381     public void setDirectoryListing(boolean on) {
382         try (GigiPreparedStatement update = new GigiPreparedStatement("UPDATE users SET listme = ? WHERE id = ?")) {
383             update.setBoolean(1, on);
384             update.setInt(2, getId());
385             update.executeUpdate();
386         }
387     }
388
389     public void setContactInformation(String contactInfo) {
390         try (GigiPreparedStatement update = new GigiPreparedStatement("UPDATE users SET contactinfo = ? WHERE id = ?")) {
391             update.setString(1, contactInfo);
392             update.setInt(2, getId());
393             update.executeUpdate();
394         }
395     }
396
397     public boolean isInGroup(Group g) {
398         return groups.contains(g);
399     }
400
401     public Set<Group> getGroups() {
402         return Collections.unmodifiableSet(groups);
403     }
404
405     public void grantGroup(User granter, Group toGrant) {
406         groups.add(toGrant);
407         try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `user_groups` SET `user`=?, `permission`=?::`userGroup`, `grantedby`=?")) {
408             ps.setInt(1, getId());
409             ps.setString(2, toGrant.getDatabaseName());
410             ps.setInt(3, granter.getId());
411             ps.execute();
412         }
413     }
414
415     public void revokeGroup(User revoker, Group toRevoke) {
416         groups.remove(toRevoke);
417         try (GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `user_groups` SET `deleted`=CURRENT_TIMESTAMP, `revokedby`=? WHERE `deleted` IS NULL AND `permission`=?::`userGroup` AND `user`=?")) {
418             ps.setInt(1, revoker.getId());
419             ps.setString(2, toRevoke.getDatabaseName());
420             ps.setInt(3, getId());
421             ps.execute();
422         }
423     }
424
425     public List<Organisation> getOrganisations() {
426         return getOrganisations(false);
427     }
428
429     public List<Organisation> getOrganisations(boolean isAdmin) {
430         List<Organisation> orgas = new ArrayList<>();
431         try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT `orgid` FROM `org_admin` WHERE `memid`=? AND `deleted` IS NULL" + (isAdmin ? " AND master='y'" : ""))) {
432             query.setInt(1, getId());
433             try (GigiResultSet res = query.executeQuery()) {
434                 while (res.next()) {
435                     orgas.add(Organisation.getById(res.getInt(1)));
436                 }
437
438                 return orgas;
439             }
440         }
441     }
442
443     public static synchronized User getById(int id) {
444         CertificateOwner co = CertificateOwner.getById(id);
445         if (co instanceof User) {
446             return (User) co;
447         }
448
449         return null;
450     }
451
452     public static User getByEmail(String mail) {
453         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `users`.`id` FROM `users` INNER JOIN `certOwners` ON `certOwners`.`id` = `users`.`id` WHERE `email`=? AND `deleted` IS NULL")) {
454             ps.setString(1, mail);
455             GigiResultSet rs = ps.executeQuery();
456             if ( !rs.next()) {
457                 return null;
458             }
459
460             return User.getById(rs.getInt(1));
461         }
462     }
463
464     public static User[] findByEmail(String mail) {
465         LinkedList<User> results = new LinkedList<User>();
466         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `users`.`id` FROM `users` INNER JOIN `certOwners` ON `certOwners`.`id` = `users`.`id` WHERE `users`.`email` LIKE ? AND `deleted` IS NULL GROUP BY `users`.`id` LIMIT 100")) {
467             ps.setString(1, mail);
468             GigiResultSet rs = ps.executeQuery();
469             while (rs.next()) {
470                 results.add(User.getById(rs.getInt(1)));
471             }
472             return results.toArray(new User[results.size()]);
473         }
474     }
475
476     public EmailAddress[] getEmails() {
477         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `id` FROM `emails` WHERE `memid`=? AND `deleted` IS NULL")) {
478             ps.setInt(1, getId());
479
480             GigiResultSet rs = ps.executeQuery();
481             LinkedList<EmailAddress> data = new LinkedList<EmailAddress>();
482
483             while (rs.next()) {
484                 data.add(EmailAddress.getById(rs.getInt(1)));
485             }
486
487             return data.toArray(new EmailAddress[0]);
488         }
489     }
490
491     @Override
492     public boolean isValidEmail(String email) {
493         for (EmailAddress em : getEmails()) {
494             if (em.getAddress().equals(email)) {
495                 return em.isVerified();
496             }
497         }
498
499         return false;
500     }
501
502     public String[] getTrainings() {
503         try (GigiPreparedStatement prep = new GigiPreparedStatement("SELECT `pass_date`, `type_text`, `language`, `version` FROM `cats_passed` LEFT JOIN `cats_type` ON `cats_type`.`id`=`cats_passed`.`variant_id`  WHERE `user_id`=? ORDER BY `pass_date` ASC")) {
504             prep.setInt(1, getId());
505             GigiResultSet res = prep.executeQuery();
506             List<String> entries = new LinkedList<String>();
507
508             while (res.next()) {
509                 StringBuilder training = new StringBuilder();
510                 training.append(DateSelector.getDateFormat().format(res.getTimestamp(1)));
511                 training.append(" (");
512                 training.append(res.getString(2));
513                 if (res.getString(3).length() > 0) {
514                     training.append(" ");
515                     training.append(res.getString(3));
516                 }
517                 if (res.getString(4).length() > 0) {
518                     training.append(", ");
519                     training.append(res.getString(4));
520                 }
521                 training.append(")");
522                 entries.add(training.toString());
523             }
524
525             return entries.toArray(new String[0]);
526         }
527
528     }
529
530     public int generatePasswordResetTicket(User actor, String token, String privateToken) {
531         try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `passwordResetTickets` SET `memid`=?, `creator`=?, `token`=?, `private_token`=?")) {
532             ps.setInt(1, getId());
533             ps.setInt(2, getId());
534             ps.setString(3, token);
535             ps.setString(4, PasswordHash.hash(privateToken));
536             ps.execute();
537             return ps.lastInsertId();
538         }
539     }
540
541     public static User getResetWithToken(int id, String token) {
542         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `memid` FROM `passwordResetTickets` WHERE `id`=? AND `token`=? AND `used` IS NULL AND `created` > CURRENT_TIMESTAMP - interval '1 hours' * ?")) {
543             ps.setInt(1, id);
544             ps.setString(2, token);
545             ps.setInt(3, PasswordResetPage.HOUR_MAX);
546             GigiResultSet res = ps.executeQuery();
547             if ( !res.next()) {
548                 return null;
549             }
550             return User.getById(res.getInt(1));
551         }
552     }
553
554     public synchronized void consumePasswordResetTicket(int id, String private_token, String newPassword) throws GigiApiException {
555         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `private_token` FROM `passwordResetTickets` WHERE `id`=? AND `memid`=? AND `used` IS NULL")) {
556             ps.setInt(1, id);
557             ps.setInt(2, getId());
558             GigiResultSet rs = ps.executeQuery();
559             if ( !rs.next()) {
560                 throw new GigiApiException("Token could not be found, has already been used, or is expired.");
561             }
562             if (PasswordHash.verifyHash(private_token, rs.getString(1)) == null) {
563                 throw new GigiApiException("Private token does not match.");
564             }
565             setPassword(newPassword);
566         }
567         try (GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `passwordResetTickets` SET  `used` = CURRENT_TIMESTAMP WHERE `id`=?")) {
568             ps.setInt(1, id);
569             ps.executeUpdate();
570         }
571     }
572
573     private Assurance assuranceByRes(GigiResultSet res) {
574         return new Assurance(res.getInt("id"), User.getById(res.getInt("from")), User.getById(res.getInt("to")), res.getString("location"), res.getString("method"), res.getInt("points"), res.getString("date"));
575     }
576
577     public static boolean isInVerificationLimit(int id) {
578         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT 1 FROM `notary` WHERE `to` = ? AND `when` > (now() - (interval '1 month' * ?)) AND (`expire` IS NULL OR `expire` > now()) AND `deleted` IS NULL;")) {
579             ps.setInt(1, id);
580             ps.setInt(2, VERIFICATION_MONTHS);
581
582             GigiResultSet rs = ps.executeQuery();
583             return rs.next();
584         }
585     }
586 }