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