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