1 //  UserRegistry.java
2 //
3 //  Created by Steven R. Loomis on 14/10/2005.
4 //  Copyright 2005-2013 IBM. All rights reserved.
5 //
6 
7 package org.unicode.cldr.web;
8 
9 import java.io.BufferedReader;
10 import java.io.File;
11 import java.io.FileNotFoundException;
12 import java.io.FileOutputStream;
13 import java.io.FileReader;
14 import java.io.IOException;
15 import java.io.OutputStreamWriter;
16 import java.io.PrintWriter;
17 import java.io.UnsupportedEncodingException;
18 import java.sql.Connection;
19 import java.sql.PreparedStatement;
20 import java.sql.ResultSet;
21 import java.sql.SQLException;
22 import java.sql.Statement;
23 import java.util.Date;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TreeMap;
31 import java.util.TreeSet;
32 
33 import org.apache.commons.codec.digest.DigestUtils;
34 import org.json.JSONException;
35 import org.json.JSONObject;
36 import org.json.JSONString;
37 import org.unicode.cldr.test.CheckCLDR.Phase;
38 import org.unicode.cldr.util.CLDRConfig;
39 import org.unicode.cldr.util.CLDRConfig.Environment;
40 import org.unicode.cldr.util.CLDRInfo.UserInfo;
41 import org.unicode.cldr.util.CLDRLocale;
42 import org.unicode.cldr.util.Organization;
43 import org.unicode.cldr.util.VoteResolver;
44 import org.unicode.cldr.util.VoteResolver.Level;
45 import org.unicode.cldr.util.VoteResolver.VoterInfo;
46 import org.unicode.cldr.util.XMLFileReader;
47 import org.unicode.cldr.util.XPathParts;
48 
49 import com.ibm.icu.dev.util.ElapsedTimer;
50 import com.ibm.icu.lang.UCharacter;
51 import com.ibm.icu.util.ULocale;
52 
53 /**
54  * This class represents the list of all registered users. It contains an inner
55  * class, UserRegistry.User, which represents an individual user.
56  *
57  * @see UserRegistry.User
58  * @see OldUserRegistry
59  **/
60 public class UserRegistry {
61 
62     /**
63      * Special constant for specifying access to all locales.
64      */
65     public static final String ALL_LOCALES = "*";
66     public static final String ALL_LOCALES_LIST[] = { ALL_LOCALES };
67     /**
68      * Special constant for specifying access to no locales. Used with intlocs (not with locale access)
69      */
70     public static final String NO_LOCALES = "none";
71 
72     /**
73      * The number of anonymous users, ANONYMOUS_USER_COUNT, limits the number of distinct values that
74      * can be added for a given locale and path as anonymous imported old losing votes. If eventually more
75      * are needed, ANONYMOUS_USER_COUNT can be increased and more anonymous users will automatically
76      * be created.
77      */
78     private static final int ANONYMOUS_USER_COUNT = 20;
79 
80     /**
81      * Thrown to indicate the caller should log out.
82      * @author srl
83      *
84      */
85     public class LogoutException extends Exception {
86 
87         /**
88          *
89          */
90         private static final long serialVersionUID = 8960959307439428532L;
91 
92     }
93 
getCovGroupsForOrg(String st_org)94     static Set<String> getCovGroupsForOrg(String st_org) {
95         Connection conn = null;
96         ResultSet rs = null;
97         PreparedStatement s = null;
98         Set<String> res = new HashSet<>();
99 
100         try {
101             conn = DBUtils.getInstance().getDBConnection();
102             s = DBUtils
103                 .prepareStatementWithArgs(
104                     conn,
105                     "select distinct cldr_interest.forum from cldr_interest where exists (select * from cldr_users  where cldr_users.id=cldr_interest.uid 	and cldr_users.org=?)",
106                     st_org);
107             rs = s.executeQuery();
108             while (rs.next()) {
109                 res.add(rs.getString(1));
110             }
111             return res;
112         } catch (SQLException se) {
113             SurveyLog.logException(se, "Querying cov groups for org " + st_org, null);
114             throw new InternalError("error: " + se.toString());
115         } finally {
116             DBUtils.close(rs, s, conn);
117         }
118     }
119 
anyVotesForOrg(String st_org)120     static Set<CLDRLocale> anyVotesForOrg(String st_org) {
121         //
122         Connection conn = null;
123         ResultSet rs = null;
124         PreparedStatement s = null;
125         Set<CLDRLocale> res = new HashSet<>();
126 
127         try {
128             conn = DBUtils.getInstance().getDBConnection();
129             s = DBUtils
130                 .prepareStatementWithArgs(
131                     conn,
132                     "select distinct " + DBUtils.Table.VOTE_VALUE + ".locale from " + DBUtils.Table.VOTE_VALUE
133                         + " where exists (select * from cldr_users	where " + DBUtils.Table.VOTE_VALUE + ".submitter=cldr_users.id and cldr_users.org=?)",
134                     st_org);
135             rs = s.executeQuery();
136             while (rs.next()) {
137                 res.add(CLDRLocale.getInstance(rs.getString(1)));
138             }
139             return res;
140         } catch (SQLException se) {
141             SurveyLog.logException(se, "Querying voter locs for org " + st_org, null);
142             throw new InternalError("error: " + se.toString());
143         } finally {
144             DBUtils.close(rs, s, conn);
145         }
146     }
147 
148     public interface UserChangedListener {
handleUserChanged(User u)149         public void handleUserChanged(User u);
150     }
151 
152     private List<UserChangedListener> listeners = new LinkedList<>();
153 
addListener(UserChangedListener l)154     public synchronized void addListener(UserChangedListener l) {
155         listeners.add(l);
156     }
157 
notify(User u)158     private synchronized void notify(User u) {
159         for (UserChangedListener l : listeners) {
160             l.handleUserChanged(u);
161         }
162     }
163 
164     private static java.util.logging.Logger logger;
165     // user levels
166     public static final int ADMIN = VoteResolver.Level.admin.getSTLevel();
167     /**< Administrator **/
168     public static final int TC = VoteResolver.Level.tc.getSTLevel();
169     /**< Technical Committee **/
170     public static final int MANAGER = VoteResolver.Level.manager.getSTLevel();
171     /**< manager **/
172     public static final int EXPERT = VoteResolver.Level.expert.getSTLevel();
173     /**< Expert Vetter **/
174     public static final int VETTER = VoteResolver.Level.vetter.getSTLevel();
175     /**< regular Vetter **/
176     public static final int STREET = VoteResolver.Level.street.getSTLevel();
177     /**< Guest Vetter **/
178     public static final int LOCKED = VoteResolver.Level.locked.getSTLevel();
179     /**< Locked user - can't login **/
180     public static final int ANONYMOUS = VoteResolver.Level.anonymous.getSTLevel();
181     /**< Anonymous user - special for imported old losing votes **/
182 
183     public static final int LIMIT_LEVEL = 10000;
184     /** max level **/
185     public static final int NO_LEVEL = -1;
186     /** min level **/
187 
188     public static final String FOR_ADDING = "(for adding)";
189     /** special "IP" value referring to a user being added **/
190     private static final String INTERNAL = "INTERNAL";
191 
192     /**
193      * List of all user levels - for UI presentation
194      **/
195     public static final int ALL_LEVELS[] = { ADMIN, TC, MANAGER, EXPERT, VETTER, STREET, LOCKED };
196 
197     /**
198      * get a level as a string - presentation form
199      **/
levelToStr(WebContext ctx, int level)200     public static String levelToStr(WebContext ctx, int level) {
201         return level + ": (" + levelAsStr(level) + ")";
202     }
203 
204     /**
205      * get just the raw level as a string
206      */
levelAsStr(int level)207     public static String levelAsStr(int level) {
208         VoteResolver.Level l = VoteResolver.Level.fromSTLevel(level);
209         if (l == null) {
210             return "??";
211         } else {
212             return l.name().toUpperCase();
213         }
214     }
215 
216     /**
217      * The name of the user sql database
218      */
219     public static final String CLDR_USERS = "cldr_users";
220     public static final String CLDR_INTEREST = "cldr_interest";
221 
222     public static final String SQL_insertStmt = "INSERT INTO " + CLDR_USERS
223         + "(userlevel,name,org,email,password,locales,lastlogin) " + "VALUES(?,?,?,?,?,?,NULL)";
224     public static final String SQL_queryStmt_FRO = "SELECT id,name,userlevel,org,locales,intlocs,lastlogin from " + CLDR_USERS
225         + " where email=? AND password=?";
226     // ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
227     public static final String SQL_queryIdStmt_FRO = "SELECT name,org,email,userlevel,intlocs,locales,lastlogin,password from "
228         + CLDR_USERS + " where id=?";
229     // ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
230     public static final String SQL_queryEmailStmt_FRO = "SELECT id,name,userlevel,org,locales,intlocs,lastlogin,password from "
231         + CLDR_USERS + " where email=?";
232     // ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
233     public static final String SQL_touchStmt = "UPDATE " + CLDR_USERS + " set lastlogin=CURRENT_TIMESTAMP where id=?";
234     public static final String SQL_removeIntLoc = "DELETE FROM " + CLDR_INTEREST + " WHERE uid=?";
235     public static final String SQL_updateIntLoc = "INSERT INTO " + CLDR_INTEREST + " (uid,forum) VALUES(?,?)";
236 
237     private UserSettingsData userSettings;
238 
239     /**
240      * This nested class is the representation of an individual user. It may not
241      * have all fields filled out, if it is simply from the cache.
242      */
243     public class User implements Comparable<User>, UserInfo, JSONString {
244         public int id; // id number
245         public int userlevel = LOCKED; // user level
246         public String password; // password
247         public String email; //
248         public String org; // organization
249         public String name; // full name
250         public java.sql.Timestamp last_connect;
251         public String locales;
252         public String intlocs = null;
253         public String ip;
254 
255         private String emailmd5 = null;
256 
getEmailHash()257         public String getEmailHash() {
258             if (emailmd5 == null) {
259                 String newHash = DigestUtils.md5Hex(email.trim().toLowerCase());
260                 emailmd5 = newHash;
261                 return newHash;
262             }
263             return emailmd5;
264         }
265 
266         private UserSettings settings;
267 
268         /**
269          * @deprecated may not use
270          */
271         @Deprecated
User()272         private User() {
273             this.id = -1;
274             settings = userSettings.getSettings(id); // may not use settings.
275         }
276 
User(int id)277         public User(int id) {
278             this.id = id;
279             settings = userSettings.getSettings(id);
280         }
281 
282         /**
283          * Get a settings object for use with this user.
284          *
285          * @return
286          */
settings()287         public UserSettings settings() {
288             return settings;
289         }
290 
touch()291         public void touch() {
292             UserRegistry.this.touch(id);
293         }
294 
295         @Override
equals(Object other)296         public boolean equals(Object other) {
297             if (!(other instanceof User)) {
298                 return false;
299             }
300             User u = (User) other;
301             return (u.id == id);
302         }
303 
printPasswordLink(WebContext ctx)304         public void printPasswordLink(WebContext ctx) {
305             UserRegistry.printPasswordLink(ctx, email, password);
306         }
307 
308         @Override
toString()309         public String toString() {
310             return email + "(" + org + ")-" + levelAsStr(userlevel) + "#" + userlevel + " - " + name + ", locs=" + locales;
311         }
312 
toHtml(User forUser)313         public String toHtml(User forUser) {
314             if (forUser == null || !userIsTC(forUser)) {
315                 return "(" + org + "#" + id + ")";
316             } else {
317                 return "<a href='mailto:" + email + "'>" + name + "</a>-" + levelAsStr(userlevel).toLowerCase();
318             }
319         }
320 
toHtml()321         public String toHtml() {
322             return "<a href='mailto:" + email + "'>" + name + "</a>-" + levelAsStr(userlevel).toLowerCase();
323         }
324 
toString(User forUser)325         public String toString(User forUser) {
326             if (forUser == null || !userIsTC(forUser)) {
327                 return "(" + org + "#" + id + ")";
328             } else {
329                 return email + "(" + org + ")-" + levelAsStr(userlevel) + "#" + userlevel + " - " + name;
330             }
331         }
332 
333         @Override
hashCode()334         public int hashCode() {
335             return id;
336         }
337 
338         /**
339          * is the user interested in this locale?
340          */
interestedIn(CLDRLocale locale)341         public boolean interestedIn(CLDRLocale locale) {
342             return UserRegistry.localeMatchesLocaleList(intlocs, locale);
343         }
344 
345         /**
346          * List of interest groups the user is interested in.
347          *
348          * @return list of locales, or null for ALL locales, or a 0-length list
349          *         for NO locales.
350          */
getInterestList()351         public String[] getInterestList() {
352             if (userIsExpert(this)) {
353                 if (intlocs == null || intlocs.length() == 0) {
354                     return null;
355                 } else {
356                     if (intlocs.equalsIgnoreCase(NO_LOCALES)) {
357                         return new String[0];
358                     }
359                     if (isAllLocales(intlocs)) return null; // all = null
360                     return tokenizeLocale(intlocs);
361                 }
362             } else if (userIsStreet(this)) {
363                 if (isAllLocales(locales)) return null; // all = null
364                 return tokenizeLocale(locales);
365             } else {
366                 return new String[0];
367             }
368         }
369 
370         /**
371          * @deprecated CLDR15 was a while ago.
372          * @param locale
373          * @return
374          */
375         @Deprecated
userIsSpecialForCLDR15(CLDRLocale locale)376         public final boolean userIsSpecialForCLDR15(CLDRLocale locale) {
377             return false;
378         }
379 
380         // if(locale.equals("be")||locale.startsWith("be_")) {
381         // if( ( id == 315 /* V. P. */ ) || (id == 8 /* S. M. */ ) ) {
382         // return true;
383         // } else {
384         // return false;
385         // }
386         // } else if ( id == 7 ) { // Erkki
387         // return true;
388         // } else {
389         // return false;
390         // }
391 
392         /**
393          * Convert this User to a VoteREsolver.VoterInfo. Not cached.
394          */
createVoterInfo()395         private VoterInfo createVoterInfo() {
396             // VoterInfo(Organization.google, Level.vetter, &quot;J.
397             // Smith&quot;) },
398             Organization o = this.getOrganization();
399             VoteResolver.Level l = this.getLevel();
400             Set<String> localesSet = new HashSet<>();
401             if (!isAllLocales(locales)) {
402                 for (String s : tokenizeLocale(locales)) {
403                     localesSet.add(s);
404                 }
405             }
406             VoterInfo v = new VoterInfo(o, l, this.name, localesSet);
407             return v;
408         }
409 
410         /**
411          * Return the value of this voter info, out of the cache
412          *
413          * @deprecated use getVoterInfo
414          * @see #getVoterInfo
415          */
416         @Deprecated
voterInfo()417         public VoterInfo voterInfo() {
418             return getVoterInfo();
419         }
420 
421         @Override
getVoterInfo()422         public VoterInfo getVoterInfo() {
423             return getVoterToInfo(id);
424         }
425 
getLevel()426         public synchronized VoteResolver.Level getLevel() {
427             if (vr_level == null) {
428                 vr_level = VoteResolver.Level.fromSTLevel(this.userlevel);
429             }
430             return vr_level;
431         }
432 
433         private VoteResolver.Level vr_level = null;
434 
getOrganization()435         public synchronized Organization getOrganization() {
436             if (vr_org == null) {
437                 vr_org = UserRegistry.computeVROrganization(this.org);
438             }
439             return vr_org;
440         }
441 
442         private Organization vr_org = null;
443 
444         private String voterOrg = null;
445 
446         /**
447          * Convenience function for returning the "VoteResult friendly"
448          * organization.
449          */
voterOrg()450         public String voterOrg() {
451             if (voterOrg == null) {
452                 voterOrg = getVoterInfo().getOrganization().name();
453             }
454             return voterOrg;
455         }
456 
457         /**
458          * Is this user an administrator 'over' this user?
459          *
460          * @param other
461          * @see VoteResolver.Level.isAdminFor()
462          * @deprecated
463          */
464         @Deprecated
isAdminFor(User other)465         public boolean isAdminFor(User other) {
466             return getLevel().isManagerFor(getOrganization(), other.getLevel(), other.getOrganization());
467         }
468 
isSameOrg(User other)469         public boolean isSameOrg(User other) {
470             return getOrganization() == other.getOrganization();
471         }
472 
473         /**
474          * Is this user an administrator 'over' this user? Always true if admin,
475          * orif TC in same org.
476          *
477          * @param other
478          */
isAdminForOrg(String org)479         public boolean isAdminForOrg(String org) {
480             boolean adminOrRelevantTc = UserRegistry.userIsAdmin(this) ||
481 
482                 ((UserRegistry.userIsTC(this) || this.userlevel == MANAGER) && (org != null) && this.org.equals(org));
483             return adminOrRelevantTc;
484         }
485 
486         @Override
compareTo(User other)487         public int compareTo(User other) {
488             if (other == this || other.equals(this))
489                 return 0;
490             if (this.id < other.id) {
491                 return -1;
492             } else {
493                 return 1;
494             }
495         }
496 
vrOrg()497         Organization vrOrg() {
498             return Organization.fromString(voterOrg());
499         }
500 
501         /**
502          * Doesn't send the password, does send other info
503          */
504         @Override
toJSONString()505         public String toJSONString() throws JSONException {
506             return new JSONObject().put("email", email)
507                 .put("emailHash", getEmailHash())
508                 .put("name", name)
509                 .put("userlevel", userlevel)
510                 .put("votecount", getLevel().getVotes())
511                 .put("userlevelName", UserRegistry.levelAsStr(userlevel))
512                 .put("org", vrOrg().name())
513                 .put("orgName", vrOrg().displayName)
514                 .put("id", id)
515                 .toString();
516         }
517 
canImportOldVotes()518         public boolean canImportOldVotes() {
519             return UserRegistry.userIsVetter(this) && (CLDRConfig.getInstance().getPhase() == Phase.SUBMISSION);
520         }
521     }
522 
printPasswordLink(WebContext ctx, String email, String password)523     public static void printPasswordLink(WebContext ctx, String email, String password) {
524         ctx.println("<a href='" + ctx.base() + "?email=" + email + "&amp;uid=" + password + "'>Login for " + email + "</a>");
525     }
526 
527     private static Map<String, Organization> orgToVrOrg = new HashMap<>();
528 
computeVROrganization(String org)529     public static synchronized Organization computeVROrganization(String org) {
530         Organization o = Organization.fromString(org);
531         if (o == null) {
532             o = orgToVrOrg.get(org);
533         } else {
534             orgToVrOrg.put(org, o); // from Organization.fromString
535         }
536         if (o == null) {
537             try {
538                 /*
539                  * TODO: "utilika" always logs WARNING: ** Unknown organization (treating as Guest): Utilika Foundation"
540                  * Map to "The Long Now Foundation" instead? Cf. https://unicode.org/cldr/trac/ticket/6320
541                  * Organization.java has: longnow("The Long Now Foundation", "Long Now", "PanLex")
542                  */
543                 String arg = org.replaceAll("Utilika Foundation", "utilika")
544                     .replaceAll("Government of Pakistan - National Language Authority", "pakistan")
545                     .replaceAll("ICT Agency of Sri Lanka", "srilanka").toLowerCase().replaceAll("[.-]", "_");
546                 o = Organization.valueOf(arg);
547             } catch (IllegalArgumentException iae) {
548                 o = Organization.guest;
549                 SurveyLog.warnOnce("** Unknown organization (treating as Guest): " + org);
550             }
551             orgToVrOrg.put(org, o);
552         }
553         return o;
554     }
555 
556     /**
557      * Called by SM to create the reg
558      *
559      * @param xlogger
560      *            the logger to use
561      * @param ourConn
562      *            the conn to use
563      */
createRegistry(java.util.logging.Logger xlogger, SurveyMain theSm)564     public static UserRegistry createRegistry(java.util.logging.Logger xlogger, SurveyMain theSm) throws SQLException {
565         sm = theSm;
566         UserRegistry reg = new UserRegistry(xlogger);
567         reg.setupDB();
568         // logger.info("UserRegistry DB: created");
569         return reg;
570     }
571 
572     /**
573      * Called by SM to shutdown
574      */
shutdownDB()575     public void shutdownDB() throws SQLException {
576         // DBUtils.closeDBConnection(conn);
577     }
578 
579     /**
580      * internal - called to setup db
581      */
setupDB()582     private void setupDB() throws SQLException {
583         // must be set up first.
584         userSettings = UserSettingsData.getInstance(sm);
585 
586         String sql = null;
587         Connection conn = DBUtils.getInstance().getDBConnection();
588         try {
589             synchronized (conn) {
590                 // logger.info("UserRegistry DB: initializing...");
591 
592                 boolean hadUserTable = DBUtils.hasTable(conn, CLDR_USERS);
593                 if (!hadUserTable) {
594                     sql = createUserTable(conn);
595                     conn.commit();
596                 } else if (!DBUtils.db_Derby) {
597                     /* update table to DATETIME instead of TIMESTAMP */
598                     Statement s = conn.createStatement();
599                     sql = "alter table cldr_users change lastlogin lastlogin DATETIME";
600                     s.execute(sql);
601                     s.close();
602                     conn.commit();
603                 }
604 
605                 //create review and post table
606                 sql = "(see ReviewHide.java)";
607                 ReviewHide.createTable(conn);
608                 boolean hadInterestTable = DBUtils.hasTable(conn, CLDR_INTEREST);
609                 if (!hadInterestTable) {
610                     Statement s = conn.createStatement();
611 
612                     sql = ("create table " + CLDR_INTEREST + " (uid INT NOT NULL , " + "forum  varchar(256) not null " + ")");
613                     s.execute(sql);
614                     sql = "CREATE  INDEX " + CLDR_INTEREST + "_id_loc ON " + CLDR_INTEREST + " (uid) ";
615                     s.execute(sql);
616                     sql = "CREATE  INDEX " + CLDR_INTEREST + "_id_for ON " + CLDR_INTEREST + " (forum) ";
617                     s.execute(sql);
618                     SurveyLog.debug("DB: created " + CLDR_INTEREST);
619                     sql = null;
620                     s.close();
621                     conn.commit();
622                 }
623 
624                 myinit(); // initialize the prepared statements
625 
626                 if (!hadInterestTable) {
627                     setupIntLocs(); // set up user -> interest table mapping
628                 }
629 
630             }
631         } catch (SQLException se) {
632             se.printStackTrace();
633             System.err.println("SQL err: " + DBUtils.unchainSqlException(se));
634             System.err.println("Last SQL run: " + sql);
635             throw se;
636         } finally {
637             DBUtils.close(conn);
638         }
639     }
640 
641     /**
642      * @param conn
643      * @return
644      * @throws SQLException
645      */
createUserTable(Connection conn)646     private String createUserTable(Connection conn) throws SQLException {
647         String sql;
648         Statement s = conn.createStatement();
649 
650         sql = ("create table " + CLDR_USERS + "(id INT NOT NULL " + DBUtils.DB_SQL_IDENTITY + ", " + "userlevel int not null, "
651             + "name " + DBUtils.DB_SQL_UNICODE + " not null, " + "email varchar(128) not null UNIQUE, "
652             + "org varchar(256) not null, " + "password varchar(100) not null, " + "audit varchar(1024) , "
653             + "locales varchar(1024) , " +
654             // "prefs varchar(1024) , " + /* deprecated Dec 2010. Not used
655             // anywhere */
656             "intlocs varchar(1024) , " + // added apr 2006: ALTER table
657             // CLDR_USERS ADD COLUMN intlocs
658             // VARCHAR(1024)
659             "lastlogin " + DBUtils.DB_SQL_TIMESTAMP0 + // added may 2006:
660             // alter table
661             // CLDR_USERS ADD
662             // COLUMN lastlogin
663             // TIMESTAMP
664             (!DBUtils.db_Mysql ? ",primary key(id)" : "") + ")");
665         s.execute(sql);
666         sql = ("INSERT INTO " + CLDR_USERS + "(userlevel,name,org,email,password) " + "VALUES(" + ADMIN + "," + "'admin',"
667             + "'SurveyTool'," + "'admin@'," + "'" + SurveyMain.vap + "')");
668         s.execute(sql);
669         sql = null;
670         SurveyLog.debug("DB: added user Admin");
671 
672         s.close();
673         return sql;
674     }
675 
676     /**
677      * ID# of the user
678      */
679     static final int ADMIN_ID = 1;
680 
681     /**
682      * special ID meaning 'all'
683      */
684     static final int ALL_ID = -1;
685 
myinit()686     private void myinit() throws SQLException {
687     }
688 
689     /**
690      * info = name/email/org immutable info, keep it in a separate list for
691      * quick lookup.
692      */
693     public final static int CHUNKSIZE = 128;
694     int arraySize = 0;
695     UserRegistry.User infoArray[] = new UserRegistry.User[arraySize];
696 
697     /**
698      * Mark user as modified
699      *
700      * @param id
701      */
userModified(int id)702     void userModified(int id) {
703         synchronized (infoArray) {
704             try {
705                 infoArray[id] = null;
706             } catch (IndexOutOfBoundsException ioob) {
707                 // nothing to do
708             }
709         }
710         userModified(); // do this if any users are modified
711     }
712 
713     /**
714      * Mark the UserRegistry as changed, purging the VoterInfo map
715      *
716      * @see #getVoterToInfo()
717      */
userModified()718     private void userModified() {
719         voterInfo = null;
720     }
721 
722     /**
723      * Get the singleton user for this ID.
724      *
725      * @param id
726      * @return singleton, or null if not found/invalid
727      */
getInfo(int id)728     public UserRegistry.User getInfo(int id) {
729         if (id < 0) {
730             return null;
731         }
732         // System.err.println("Fetching info for id " + id);
733         synchronized (infoArray) {
734             User ret = null;
735             try {
736                 // System.err.println("attempting array lookup for id " + id);
737                 // ret = (User)infoArray.get(id);
738                 ret = infoArray[id];
739             } catch (IndexOutOfBoundsException ioob) {
740                 // System.err.println("Index out of bounds for id " + id + " - "
741                 // + ioob);
742                 ret = null; // not found
743             }
744 
745             if (ret == null) { // synchronized(conn) {
746                 // System.err.println("go fish for id " + id);
747                 // queryIdStmt =
748                 // conn.prepareStatement("SELECT name,org,email from "
749                 // + CLDR_USERS +" where id=?");
750                 ResultSet rs = null;
751                 PreparedStatement pstmt = null;
752                 Connection conn = DBUtils.getInstance().getDBConnection();
753                 try {
754                     pstmt = DBUtils.prepareForwardReadOnly(conn, UserRegistry.SQL_queryIdStmt_FRO);
755                     pstmt.setInt(1, id);
756                     // First, try to query it back from the DB.
757                     rs = pstmt.executeQuery();
758                     if (!rs.next()) {
759                         // System.err.println("Unknown user#:" + id);
760                         return null;
761                     }
762                     User u = new UserRegistry.User(id);
763                     // from params:
764                     u.name = DBUtils.getStringUTF8(rs, 1);// rs.getString(1);
765                     u.org = rs.getString(2);
766                     u.getOrganization(); // verify
767 
768                     u.email = rs.getString(3);
769                     u.userlevel = rs.getInt(4);
770                     u.intlocs = rs.getString(5);
771                     u.locales = normalizeLocaleList(rs.getString(6));
772                     u.last_connect = rs.getTimestamp(7);
773                     u.password = rs.getString(8);
774                     // queryIdStmt =
775                     // conn.prepareStatement("SELECT name,org,email,userlevel,intlocs,lastlogin,password from "
776                     // + CLDR_USERS +" where id=?",
777 
778                     // System.err.println("SQL Loaded info for U#"+u.id +
779                     // " - "+u.name +"/"+u.org+"/"+u.email);
780                     ret = u; // let it finish..
781 
782                     if (id >= arraySize) {
783                         int newchunk = (((id + 1) / CHUNKSIZE) + 1) * CHUNKSIZE;
784                         // System.err.println("UR: userInfo resize from " +
785                         // infoArray.length + " to " + newchunk);
786                         infoArray = new UserRegistry.User[newchunk];
787                         arraySize = newchunk;
788                     }
789                     infoArray[id] = u;
790                     // good so far..
791                     if (rs.next()) {
792                         // dup returned!
793                         throw new InternalError("Dup user id # " + id);
794                     }
795                 } catch (SQLException se) {
796                     logger.log(java.util.logging.Level.SEVERE,
797                         "UserRegistry: SQL error trying to get #" + id + " - " + DBUtils.unchainSqlException(se), se);
798                     throw new InternalError("UserRegistry: SQL error trying to get #" + id + " - "
799                         + DBUtils.unchainSqlException(se));
800                     // return ret;
801                 } catch (Throwable t) {
802                     logger.log(java.util.logging.Level.SEVERE, "UserRegistry: some error trying to get #" + id, t);
803                     throw new InternalError("UserRegistry: some error trying to get #" + id + " - " + t.toString());
804                     // return ret;
805                 } finally {
806                     // close out the RS
807                     DBUtils.close(rs, pstmt, conn);
808                 } // end try
809             }
810             // /*srl*/ if(ret==null) { System.err.println("returning NULL for "
811             // + id); } else { User u = ret;
812             // System.err.println("Returned info for U#"+u.id + " - "+u.name
813             // +"/"+u.org+"/"+u.email); }
814             return ret;
815         } // end synch array
816     }
817 
normalizeEmail(String str)818     private final String normalizeEmail(String str) {
819         return str.trim().toLowerCase();
820     }
821 
get(String pass, String email, String ip)822     public final UserRegistry.User get(String pass, String email, String ip) throws LogoutException {
823         return get(pass, email, ip, false);
824     }
825 
touch(int id)826     public void touch(int id) {
827         // System.err.println("Touching: " + id);
828         Connection conn = null;
829         PreparedStatement pstmt = null;
830         // synchronized(conn) {
831         try {
832             conn = DBUtils.getInstance().getDBConnection();
833             pstmt = conn.prepareStatement(SQL_touchStmt);
834             pstmt.setInt(1, id);
835             pstmt.executeUpdate();
836             conn.commit();
837         } catch (SQLException se) {
838             logger.log(java.util.logging.Level.SEVERE,
839                 "UserRegistry: SQL error trying to touch " + id + " - " + DBUtils.unchainSqlException(se), se);
840             throw new InternalError("UserRegistry: SQL error trying to touch " + id + " - " + DBUtils.unchainSqlException(se));
841         } finally {
842             DBUtils.close(pstmt, conn);
843         }
844 
845         // }
846     }
847 
848     /**
849      * @param letmein
850      *            The VAP was given - allow the user in regardless
851      * @param pass
852      *            the password to match. If NULL, means just do a lookup
853      */
get(String pass, String email, String ip, boolean letmein)854     public UserRegistry.User get(String pass, String email, String ip, boolean letmein) throws LogoutException {
855         if ((email == null) || (email.length() <= 0)) {
856             return null; // nothing to do
857         }
858         if (((pass != null && pass.length() <= 0)) && !letmein) {
859             return null; // nothing to do
860         }
861 
862         if (email.startsWith("!") && pass != null && pass.equals(SurveyMain.vap)) {
863             email = email.substring(1);
864             letmein = true;
865         }
866 
867         email = normalizeEmail(email);
868 
869         ResultSet rs = null;
870         // synchronized(conn) {
871         Connection conn = null;
872         PreparedStatement pstmt = null;
873         try {
874             conn = DBUtils.getInstance().getDBConnection();
875             if ((pass != null) && !letmein) {
876                 // logger.info("Looking up " + email + " : " + pass);
877                 pstmt = DBUtils.prepareForwardReadOnly(conn, SQL_queryStmt_FRO);
878                 pstmt.setString(1, email);
879                 pstmt.setString(2, pass);
880             } else {
881                 // logger.info("Looking up " + email);
882                 pstmt = DBUtils.prepareForwardReadOnly(conn, SQL_queryEmailStmt_FRO);
883                 pstmt.setString(1, email);
884             }
885             // First, try to query it back from the DB.
886             rs = pstmt.executeQuery();
887             if (!rs.next()) { // user was not found.
888                 throw new UserRegistry.LogoutException();
889             }
890             User u = new UserRegistry.User(rs.getInt(1));
891 
892             // from params:
893             u.password = pass;
894             if (letmein) {
895                 u.password = rs.getString(8);
896             }
897             u.email = normalizeEmail(email);
898             // from db: (id,name,userlevel,org,locales)
899             u.name = DBUtils.getStringUTF8(rs, 2);// rs.getString(2);
900             u.userlevel = rs.getInt(3);
901             u.org = rs.getString(4);
902             u.locales = rs.getString(5);
903             u.intlocs = rs.getString(6);
904             u.last_connect = rs.getTimestamp(7);
905 
906             // good so far..
907 
908             if (rs.next()) {
909                 // dup returned!
910                 logger.severe("Duplicate user for " + email + " - ids " + u.id + " and " + rs.getInt(1));
911                 return null;
912             }
913             return u;
914         } catch (SQLException se) {
915             logger.log(java.util.logging.Level.SEVERE,
916                 "UserRegistry: SQL error trying to get " + email + " - " + DBUtils.unchainSqlException(se), se);
917             throw new InternalError("UserRegistry: SQL error trying to get " + email + " - " + DBUtils.unchainSqlException(se));
918             // return null;
919         } catch (LogoutException le) {
920             if (pass != null) {
921                 // only log this if they were actually trying to login.
922                 logger.log(java.util.logging.Level.SEVERE, "AUTHENTICATION FAILURE; email=" + email + "; ip=" + ip);
923             }
924             throw le; // bubble
925         } catch (Throwable t) {
926             logger.log(java.util.logging.Level.SEVERE, "UserRegistry: some error trying to get " + email, t);
927             throw new InternalError("UserRegistry: some error trying to get " + email + " - " + t.toString());
928             // return null;
929         } finally {
930             // close out the RS
931             DBUtils.close(rs, pstmt, conn);
932         } // end try
933           // } // end synch(conn)
934     } // end get
935 
get(String email)936     public UserRegistry.User get(String email) {
937         try {
938             return get(null, email, INTERNAL);
939         } catch (LogoutException le) {
940             return null;
941         }
942     }
943 
944     /**
945      * @deprecated
946      * @return
947      */
948     @Deprecated
getEmptyUser()949     public UserRegistry.User getEmptyUser() {
950         User u = new User();
951         u.name = "UNKNOWN";
952         u.email = "UN@KNOWN.example.com";
953         u.org = "NONE";
954         u.password = null;
955         u.locales = "";
956 
957         return u;
958     }
959 
960     static SurveyMain sm = null; // static for static checking of defaultContent
961 
UserRegistry(java.util.logging.Logger xlogger)962     private UserRegistry(java.util.logging.Logger xlogger) {
963         logger = xlogger;
964     }
965 
966     // ------- special things for "list" mode:
967 
list(String organization, Connection conn)968     public java.sql.ResultSet list(String organization, Connection conn) throws SQLException {
969         ResultSet rs = null;
970         Statement s = null;
971         final String ORDER = " ORDER BY org,userlevel,name ";
972         // synchronized(conn) {
973         // try {
974         s = conn.createStatement();
975         if (organization == null) {
976             rs = s.executeQuery("SELECT id,userlevel,name,email,org,locales,intlocs,lastlogin FROM " + CLDR_USERS + ORDER);
977         } else {
978             rs = s.executeQuery("SELECT id,userlevel,name,email,org,locales,intlocs,lastlogin FROM " + CLDR_USERS
979                 + " WHERE org='" + organization + "'" + ORDER);
980         }
981         // } finally {
982         // s.close();
983         // }
984         // }
985 
986         return rs;
987     }
988 
listPass(Connection conn)989     public java.sql.ResultSet listPass(Connection conn) throws SQLException {
990         ResultSet rs = null;
991         Statement s = null;
992         final String ORDER = " ORDER BY id ";
993         // synchronized(conn) {
994         // try {
995         s = conn.createStatement();
996         rs = s.executeQuery("SELECT id,userlevel,name,email,org,locales,intlocs, password FROM " + CLDR_USERS + ORDER);
997         // } finally {
998         // s.close();
999         // }
1000         // }
1001 
1002         return rs;
1003     }
1004 
setupIntLocs()1005     void setupIntLocs() throws SQLException {
1006         Connection conn = DBUtils.getInstance().getDBConnection();
1007         PreparedStatement removeIntLoc = null;
1008         PreparedStatement updateIntLoc = null;
1009         try {
1010             removeIntLoc = conn.prepareStatement(SQL_removeIntLoc);
1011             updateIntLoc = conn.prepareStatement(SQL_updateIntLoc);
1012             ResultSet rs = list(null, conn);
1013             ElapsedTimer et = new ElapsedTimer();
1014             int count = 0;
1015             while (rs.next()) {
1016                 int user = rs.getInt(1);
1017                 // String who = rs.getString(4);
1018 
1019                 updateIntLocs(user, false, conn, removeIntLoc, updateIntLoc);
1020                 count++;
1021             }
1022             conn.commit();
1023             SurveyLog.debug("update:" + count + " user's locales updated " + et);
1024         } finally {
1025             DBUtils.close(removeIntLoc, updateIntLoc, conn);
1026         }
1027     }
1028 
1029     /**
1030      * assumes caller has a lock on conn
1031      */
updateIntLocs(int user, Connection conn)1032     String updateIntLocs(int user, Connection conn) throws SQLException {
1033         PreparedStatement removeIntLoc = null;
1034         PreparedStatement updateIntLoc = null;
1035         try {
1036             removeIntLoc = conn.prepareStatement(SQL_removeIntLoc);
1037             updateIntLoc = conn.prepareStatement(SQL_updateIntLoc);
1038             return updateIntLocs(user, true, conn, removeIntLoc, updateIntLoc);
1039         } finally {
1040             DBUtils.close(removeIntLoc, updateIntLoc);
1041         }
1042     }
1043 
1044     /**
1045      * validate an interest locale list.
1046      * @param list
1047      * @return
1048      */
validateIntlocList(String list)1049     static String validateIntlocList(String list) {
1050         list = list.trim();
1051         StringBuilder sb = new StringBuilder();
1052         for (CLDRLocale l : tokenizeValidCLDRLocale(list)) {
1053             if (sb.length() > 0) {
1054                 sb.append(' ');
1055             }
1056             sb.append(l.getBaseName());
1057         }
1058         return sb.toString();
1059     }
1060 
normalizeLocaleList(String list)1061     public static String normalizeLocaleList(String list) {
1062         if (isAllLocales(list)) {
1063             return ALL_LOCALES;
1064         }
1065         if (list == null) {
1066             return "";
1067         }
1068         list = list.trim();
1069         if (list.length() > 0) {
1070             if (list.equals(NO_LOCALES)) {
1071                 return "";
1072             }
1073             Set<String> s = new TreeSet<>();
1074             for (String l : UserRegistry.tokenizeLocale(list)) {
1075                 String forum = new ULocale(l).getBaseName();
1076                 s.add(forum);
1077             }
1078             list = null;
1079             for (String forum : s) {
1080                 if (list == null) {
1081                     list = forum;
1082                 } else {
1083                     list = list + " " + forum;
1084                 }
1085             }
1086         }
1087         return list;
1088     }
1089 
1090     /**
1091      * assumes caller has a lock on conn
1092      */
updateIntLocs(int id, boolean doCommit, Connection conn, PreparedStatement removeIntLoc, PreparedStatement updateIntLoc)1093     String updateIntLocs(int id, boolean doCommit, Connection conn, PreparedStatement removeIntLoc, PreparedStatement updateIntLoc)
1094         throws SQLException {
1095 
1096         User user = getInfo(id);
1097         if (user == null) {
1098             return "";
1099         }
1100 
1101         removeIntLoc.setInt(1, id);
1102         removeIntLoc.executeUpdate();
1103 
1104         String[] il = user.getInterestList();
1105         if (il != null) {
1106             updateIntLoc.setInt(1, id);
1107             Set<String> s = new HashSet<>();
1108             for (String l : il) {
1109                 String forum = new ULocale(l).getLanguage();
1110                 s.add(forum);
1111             }
1112             for (String forum : s) {
1113                 updateIntLoc.setString(2, forum);
1114                 updateIntLoc.executeUpdate();
1115             }
1116         }
1117 
1118         if (doCommit) {
1119             conn.commit();
1120         }
1121         return "";
1122     }
1123 
setUserLevel(WebContext ctx, int theirId, String theirEmail, int newLevel)1124     String setUserLevel(WebContext ctx, int theirId, String theirEmail, int newLevel) {
1125         if (!ctx.session.user.getLevel().canCreateOrSetLevelTo(VoteResolver.Level.fromSTLevel(newLevel))) {
1126             return ("[Permission Denied]");
1127         }
1128 
1129         String orgConstraint = null;
1130         String msg = "";
1131         if (ctx.session.user.userlevel == ADMIN) {
1132             orgConstraint = ""; // no constraint
1133         } else {
1134             orgConstraint = " AND org='" + ctx.session.user.org + "' ";
1135         }
1136         Connection conn = null;
1137         try {
1138             conn = DBUtils.getInstance().getDBConnection();
1139             Statement s = conn.createStatement();
1140             String theSql = "UPDATE " + CLDR_USERS + " SET userlevel=" + newLevel + " WHERE id=" + theirId + " AND email='"
1141                 + theirEmail + "' " + orgConstraint;
1142             // msg = msg + " (<br /><pre> " + theSql + " </pre><br />) ";
1143             logger.info("Attempt user update by " + ctx.session.user.email + ": " + theSql);
1144             int n = s.executeUpdate(theSql);
1145             conn.commit();
1146             userModified(theirId);
1147             if (n == 0) {
1148                 msg = msg + " [Error: no users were updated!] ";
1149                 logger.severe("Error: 0 records updated.");
1150             } else if (n != 1) {
1151                 msg = msg + " [Error in updating users!] ";
1152                 logger.severe("Error: " + n + " records updated!");
1153             } else {
1154                 msg = msg + " [user level set]";
1155                 msg = msg + updateIntLocs(theirId, conn);
1156             }
1157         } catch (SQLException se) {
1158             msg = msg + " exception: " + DBUtils.unchainSqlException(se);
1159         } catch (Throwable t) {
1160             msg = msg + " exception: " + t.toString();
1161         } finally {
1162             DBUtils.closeDBConnection(conn);
1163         }
1164 
1165         return msg;
1166     }
1167 
setLocales(WebContext ctx, int theirId, String theirEmail, String newLocales)1168     String setLocales(WebContext ctx, int theirId, String theirEmail, String newLocales) {
1169         return setLocales(ctx, theirId, theirEmail, newLocales, false);
1170     }
1171 
setLocales(WebContext ctx, int theirId, String theirEmail, String newLocales, boolean intLocs)1172     String setLocales(WebContext ctx, int theirId, String theirEmail, String newLocales, boolean intLocs) {
1173         if (!intLocs && !ctx.session.user.isAdminFor(getInfo(theirId))) { // will make sure other user is at or below userlevel
1174             return ("[Permission Denied]");
1175         }
1176 
1177         if (!intLocs) {
1178             newLocales = normalizeLocaleList(newLocales);
1179             if (newLocales.isEmpty()) newLocales = "und";
1180         } else {
1181             newLocales = validateIntlocList(newLocales);
1182         }
1183         String orgConstraint = null;
1184         String msg = "";
1185         if (ctx.session.user.userlevel == ADMIN) {
1186             orgConstraint = ""; // no constraint
1187         } else {
1188             orgConstraint = " AND org='" + ctx.session.user.org + "' ";
1189         }
1190         Connection conn = null;
1191         PreparedStatement ps = null;
1192         try {
1193             final String normalizedLocales = newLocales;
1194             conn = DBUtils.getInstance().getDBConnection();
1195             String theSql = "UPDATE " + CLDR_USERS + " SET " + (intLocs ? "intlocs" : "locales") + "=? WHERE id=" + theirId
1196                 + " AND email='" + theirEmail + "' " + orgConstraint;
1197             ps = conn.prepareStatement(theSql);
1198             // msg = msg + " (<br /><pre> " + theSql + " </pre><br />) ";
1199             logger.info("Attempt user locales update by " + ctx.session.user.email + ": " + theSql + " - " + newLocales);
1200             ps.setString(1, normalizedLocales);
1201             int n = ps.executeUpdate();
1202             conn.commit();
1203             userModified(theirId);
1204             if (n == 0) {
1205                 msg = msg + " [Error: no users were updated!] ";
1206                 logger.severe("Error: 0 records updated.");
1207             } else if (n != 1) {
1208                 msg = msg + " [Error in updating users!] ";
1209                 logger.severe("Error: " + n + " records updated!");
1210             } else {
1211                 msg = msg + " [locales set]";
1212 
1213                 msg = msg + updateIntLocs(theirId, conn);
1214                 /*
1215                  * if(intLocs) { return updateIntLocs(theirId); }
1216                  */
1217             }
1218         } catch (SQLException se) {
1219             msg = msg + " exception: " + DBUtils.unchainSqlException(se);
1220         } catch (Throwable t) {
1221             msg = msg + " exception: " + t.toString();
1222         } finally {
1223             try {
1224                 if (ps != null)
1225                     ps.close();
1226                 if (conn != null)
1227                     conn.close();
1228             } catch (SQLException se) {
1229                 logger.log(java.util.logging.Level.SEVERE,
1230                     "UserRegistry: SQL error trying to close. " + DBUtils.unchainSqlException(se), se);
1231             }
1232         }
1233         // }
1234 
1235         return msg;
1236     }
1237 
delete(WebContext ctx, int theirId, String theirEmail)1238     String delete(WebContext ctx, int theirId, String theirEmail) {
1239         if (!ctx.session.user.isAdminFor(getInfo(theirId))) {
1240             return ("[Permission Denied]");
1241         }
1242 
1243         String orgConstraint = null; // keep org constraint in place
1244         String msg = "";
1245         if (ctx.session.user.userlevel == ADMIN) {
1246             orgConstraint = ""; // no constraint
1247         } else {
1248             orgConstraint = " AND org='" + ctx.session.user.org + "' ";
1249         }
1250         Connection conn = null;
1251         Statement s = null;
1252         try {
1253             conn = DBUtils.getInstance().getDBConnection();
1254             s = conn.createStatement();
1255             String theSql = "DELETE FROM " + CLDR_USERS + " WHERE id=" + theirId + " AND email='" + theirEmail + "' "
1256                 + orgConstraint;
1257             // msg = msg + " (<br /><pre> " + theSql + " </pre><br />) ";
1258             logger.info("Attempt user DELETE by " + ctx.session.user.email + ": " + theSql);
1259             int n = s.executeUpdate(theSql);
1260             conn.commit();
1261             userModified(theirId);
1262             if (n == 0) {
1263                 msg = msg + " [Error: no users were removed!] ";
1264                 logger.severe("Error: 0 users removed.");
1265             } else if (n != 1) {
1266                 msg = msg + " [Error in removing users!] ";
1267                 logger.severe("Error: " + n + " records removed!");
1268             } else {
1269                 msg = msg + " [removed OK]";
1270             }
1271         } catch (SQLException se) {
1272             msg = msg + " exception: " + DBUtils.unchainSqlException(se);
1273         } catch (Throwable t) {
1274             msg = msg + " exception: " + t.toString();
1275         } finally {
1276             DBUtils.close(s, conn);
1277             // s.close();
1278         }
1279         // }
1280 
1281         return msg;
1282     }
1283 
1284     public enum InfoType {
1285         INFO_EMAIL("E-mail", "email"), INFO_NAME("Name", "name"), INFO_PASSWORD("Password", "password"), INFO_ORG("Organization",
1286             "org");
1287         private static final String CHANGE = "change_";
1288         private String sqlField;
1289         private String title;
1290 
InfoType(String title, String sqlField)1291         InfoType(String title, String sqlField) {
1292             this.title = title;
1293             this.sqlField = sqlField;
1294         }
1295 
1296         @Override
toString()1297         public String toString() {
1298             return title;
1299         }
1300 
field()1301         public String field() {
1302             return sqlField;
1303         }
1304 
fromAction(String action)1305         public static InfoType fromAction(String action) {
1306             if (action != null && action.startsWith(CHANGE)) {
1307                 String which = action.substring(CHANGE.length());
1308                 return InfoType.valueOf(which);
1309             } else {
1310                 return null;
1311             }
1312         }
1313 
toAction()1314         public String toAction() {
1315             return CHANGE + name();
1316         }
1317     }
1318 
updateInfo(WebContext ctx, int theirId, String theirEmail, InfoType type, String value)1319     String updateInfo(WebContext ctx, int theirId, String theirEmail, InfoType type, String value) {
1320         if (type == InfoType.INFO_ORG && ctx.session.user.userlevel > ADMIN) {
1321             return ("[Permission Denied]");
1322         }
1323 
1324         if (type == InfoType.INFO_EMAIL) {
1325             value = normalizeEmail(value);
1326         } else {
1327             value = value.trim();
1328         }
1329 
1330         if (!ctx.session.user.isAdminFor(getInfo(theirId))) {
1331             return ("[Permission Denied]");
1332         }
1333 
1334         String msg = "";
1335         Connection conn = null;
1336         PreparedStatement updateInfoStmt = null;
1337         try {
1338             conn = DBUtils.getInstance().getDBConnection();
1339 
1340             updateInfoStmt = conn.prepareStatement("UPDATE " + CLDR_USERS + " set " + type.field() + "=? WHERE id=? AND email=?");
1341             if (type == UserRegistry.InfoType.INFO_NAME) { // unicode treatment
1342                 DBUtils.setStringUTF8(updateInfoStmt, 1, value);
1343             } else {
1344                 updateInfoStmt.setString(1, value);
1345             }
1346             updateInfoStmt.setInt(2, theirId);
1347             updateInfoStmt.setString(3, theirEmail);
1348 
1349             logger.info("Attempt user UPDATE by " + ctx.session.user.email + ": " + type.toString() + " = "
1350                 + ((type != InfoType.INFO_PASSWORD) ? value : "********"));
1351             int n = updateInfoStmt.executeUpdate();
1352             conn.commit();
1353             userModified(theirId);
1354             if (n == 0) {
1355                 msg = msg + " [Error: no users were updated!] ";
1356                 logger.severe("Error: 0 users updated.");
1357             } else if (n != 1) {
1358                 msg = msg + " [Error in updated users!] ";
1359                 logger.severe("Error: " + n + " updated removed!");
1360             } else {
1361                 msg = msg + " [updated OK]";
1362             }
1363         } catch (SQLException se) {
1364             msg = msg + " exception: " + DBUtils.unchainSqlException(se);
1365         } catch (Throwable t) {
1366             msg = msg + " exception: " + t.toString();
1367         } finally {
1368             DBUtils.close(updateInfoStmt, conn);
1369         }
1370         // }
1371 
1372         return msg;
1373     }
1374 
resetPassword(String forEmail, String ip)1375     public String resetPassword(String forEmail, String ip) {
1376         String msg = "";
1377         String newPassword = CookieSession.newId(false);
1378         if (newPassword.length() > 10) {
1379             newPassword = newPassword.substring(0, 10);
1380         }
1381         Connection conn = null;
1382         PreparedStatement updateInfoStmt = null;
1383         try {
1384             conn = DBUtils.getInstance().getDBConnection();
1385 
1386             updateInfoStmt = DBUtils.prepareStatementWithArgs(conn, "UPDATE " + CLDR_USERS + " set password=? ,  audit=? WHERE email=? AND userlevel <"
1387                 + LOCKED + "  AND userlevel >= " + TC, newPassword,
1388                 "Reset: " + new Date().toString() + " by " + ip,
1389                 forEmail);
1390 
1391             logger.info("** Attempt password reset " + forEmail + " from " + ip);
1392             int n = updateInfoStmt.executeUpdate();
1393 
1394             conn.commit();
1395             userModified(forEmail);
1396             if (n == 0) {
1397                 msg = msg + "Error: no valid accounts found";
1398                 logger.severe("Error in password reset:: 0 users updated.");
1399             } else if (n != 1) {
1400                 msg = msg + " [Error in updated users!] ";
1401                 logger.severe("Error in password reset: " + n + " updated removed!");
1402             } else {
1403                 msg = msg + "OK";
1404                 sm.notifyUser(null, forEmail, newPassword);
1405             }
1406         } catch (SQLException se) {
1407             SurveyLog.logException(se, "Resetting password for user " + forEmail + " from " + ip);
1408             msg = msg + " exception";
1409         } catch (Throwable t) {
1410             SurveyLog.logException(t, "Resetting password for user " + forEmail + " from " + ip);
1411             msg = msg + " exception: " + t.toString();
1412         } finally {
1413             DBUtils.close(updateInfoStmt, conn);
1414         }
1415         return msg;
1416     }
1417 
lockAccount(String forEmail, String reason, String ip)1418     public String lockAccount(String forEmail, String reason, String ip) {
1419         String msg = "";
1420         User u = this.get(forEmail);
1421         logger.info("** Attempt LOCK " + forEmail + " from " + ip + " reason " + reason);
1422         String newPassword = CookieSession.newId(false);
1423         if (newPassword.length() > 10) {
1424             newPassword = newPassword.substring(0, 10);
1425         }
1426         if (reason.length() > 500) {
1427             reason = reason.substring(0, 500) + "...";
1428         }
1429         Connection conn = null;
1430         PreparedStatement updateInfoStmt = null;
1431         try {
1432             conn = DBUtils.getInstance().getDBConnection();
1433 
1434             updateInfoStmt = DBUtils.prepareStatementWithArgs(conn, "UPDATE " + CLDR_USERS + " set password=?,userlevel=" + LOCKED
1435                 + ",  audit=? WHERE email=? AND userlevel <" + LOCKED + "  AND userlevel >= " + TC,
1436                 newPassword,
1437                 "Lock: " + new Date().toString() + " by " + ip + ":" + reason,
1438                 forEmail);
1439 
1440             int n = updateInfoStmt.executeUpdate();
1441 
1442             conn.commit();
1443             userModified(forEmail);
1444             if (n == 0) {
1445                 msg = msg + "Error: no valid accounts found";
1446                 logger.severe("Error in LOCK:: 0 users updated.");
1447             } else if (n != 1) {
1448                 msg = msg + " [Error in updated users!] ";
1449                 logger.severe("Error in LOCK: " + n + " updated removed!");
1450             } else {
1451                 msg = msg + "OK";
1452                 MailSender.getInstance().queue(null, 1, "User Locked: " + forEmail, "User account locked: " + ip + " reason=" + reason + " - " + u);
1453             }
1454         } catch (SQLException se) {
1455             SurveyLog.logException(se, "Locking account for user " + forEmail + " from " + ip);
1456             msg = msg + " exception";
1457         } catch (Throwable t) {
1458             SurveyLog.logException(t, "Locking account for user " + forEmail + " from " + ip);
1459             msg = msg + " exception: " + t.toString();
1460         } finally {
1461             DBUtils.close(updateInfoStmt, conn);
1462         }
1463         return msg;
1464     }
1465 
userModified(String forEmail)1466     private void userModified(String forEmail) {
1467         User u = get(forEmail);
1468         if (u != null) userModified(u);
1469     }
1470 
userModified(User u)1471     private void userModified(User u) {
1472         // TODO Auto-generated method stub
1473         userModified(u.id);
1474     }
1475 
getPassword(WebContext ctx, int theirId)1476     public String getPassword(WebContext ctx, int theirId) {
1477         ResultSet rs = null;
1478         Statement s = null;
1479         String result = null;
1480         Connection conn = null;
1481         // try {
1482         if (ctx != null) {
1483             logger.info("UR: Attempt getPassword by " + ctx.session.user.email + ": of #" + theirId);
1484         }
1485         try {
1486             conn = DBUtils.getInstance().getDBConnection();
1487             s = conn.createStatement();
1488             rs = s.executeQuery("SELECT password FROM " + CLDR_USERS + " WHERE id=" + theirId);
1489             if (!rs.next()) {
1490                 if (ctx != null)
1491                     ctx.println("Couldn't find user.");
1492                 return null;
1493             }
1494             result = rs.getString(1);
1495             if (rs.next()) {
1496                 if (ctx != null) {
1497                     ctx.println("Matched duplicate user (?)");
1498                 }
1499                 return null;
1500             }
1501         } catch (SQLException se) {
1502             logger.severe("UR:  exception: " + DBUtils.unchainSqlException(se));
1503             if (ctx != null)
1504                 ctx.println(" An error occured: " + DBUtils.unchainSqlException(se));
1505         } catch (Throwable t) {
1506             logger.severe("UR:  exception: " + t.toString());
1507             if (ctx != null)
1508                 ctx.println(" An error occured: " + t.toString());
1509         } finally {
1510             DBUtils.close(s, conn);
1511         }
1512         // }
1513 
1514         return result;
1515     }
1516 
makePassword(String email)1517     public static String makePassword(String email) {
1518         return CookieSession.newId(false).substring(0, 9);
1519         // return CookieSession.cheapEncode((System.currentTimeMillis()*100) +
1520         // SurveyMain.pages) + "x" +
1521         // CookieSession.cheapEncode(email.hashCode() *
1522         // SurveyMain.vap.hashCode());
1523     }
1524 
newUser(WebContext ctx, User u)1525     public User newUser(WebContext ctx, User u) {
1526         final boolean hushUserMessages = CLDRConfig.getInstance().getEnvironment() == Environment.UNITTEST;
1527         u.email = normalizeEmail(u.email);
1528         // prepare quotes
1529         u.email = u.email.replace('\'', '_').toLowerCase();
1530         u.org = u.org.replace('\'', '_');
1531         u.name = u.name.replace('\'', '_');
1532         u.locales = u.locales.replace('\'', '_');
1533 
1534         Connection conn = null;
1535         PreparedStatement insertStmt = null;
1536         try {
1537             conn = DBUtils.getInstance().getDBConnection();
1538             insertStmt = conn.prepareStatement(SQL_insertStmt);
1539             insertStmt.setInt(1, u.userlevel);
1540             DBUtils.setStringUTF8(insertStmt, 2, u.name); // insertStmt.setString(2,
1541             // u.name);
1542             insertStmt.setString(3, u.org);
1543             insertStmt.setString(4, u.email);
1544             insertStmt.setString(5, u.password);
1545             insertStmt.setString(6, normalizeLocaleList(u.locales));
1546             if (!insertStmt.execute()) {
1547                 if (!hushUserMessages) logger.info("Added.");
1548                 conn.commit();
1549                 if (ctx != null)
1550                     ctx.println("<p>Added user.<p>");
1551                 User newu = get(u.password, u.email, FOR_ADDING); // throw away
1552                 // old user
1553                 updateIntLocs(newu.id, conn);
1554                 resetOrgList(); // update with new org spelling.
1555                 notify(newu);
1556                 return newu;
1557             } else {
1558                 if (ctx != null)
1559                     ctx.println("Couldn't add user.");
1560                 conn.commit();
1561                 return null;
1562             }
1563         } catch (SQLException se) {
1564             SurveyLog.logException(se, "Adding User");
1565             logger.severe("UR: Adding " + u.toString() + ": exception: " + DBUtils.unchainSqlException(se));
1566         } catch (Throwable t) {
1567             SurveyLog.logException(t, "Adding User");
1568             logger.severe("UR: Adding  " + u.toString() + ": exception: " + t.toString());
1569         } finally {
1570             userModified(); // new user
1571             DBUtils.close(insertStmt, conn);
1572         }
1573 
1574         return null;
1575     }
1576 
1577     // All of the userlevel policy is concentrated here, or in above functions
1578     // (search for 'userlevel')
1579 
1580     // * user types
userIsAdmin(User u)1581     public static final boolean userIsAdmin(User u) {
1582         return (u != null) && (u.userlevel <= UserRegistry.ADMIN);
1583     }
1584 
userIsTC(User u)1585     public static final boolean userIsTC(User u) {
1586         return (u != null) && (u.userlevel <= UserRegistry.TC);
1587     }
1588 
userIsExactlyManager(User u)1589     public static final boolean userIsExactlyManager(User u) {
1590         return (u != null) && (u.userlevel == UserRegistry.MANAGER);
1591     }
1592 
userIsExpert(User u)1593     public static final boolean userIsExpert(User u) {
1594         return (u != null) && (u.userlevel <= UserRegistry.EXPERT);
1595     }
1596 
userIsVetter(User u)1597     public static final boolean userIsVetter(User u) {
1598         return (u != null) && (u.userlevel <= UserRegistry.VETTER);
1599     }
1600 
userIsStreet(User u)1601     public static final boolean userIsStreet(User u) {
1602         return (u != null) && (u.userlevel <= UserRegistry.STREET);
1603     }
1604 
userIsLocked(User u)1605     public static final boolean userIsLocked(User u) {
1606         return (u != null) && (u.userlevel == UserRegistry.LOCKED);
1607     }
1608 
userIsExactlyAnonymous(User u)1609     public static final boolean userIsExactlyAnonymous(User u) {
1610         return (u != null) && (u.userlevel == UserRegistry.ANONYMOUS);
1611     }
1612 
1613     // * user rights
1614     /** can create a user in a different organization? */
userCreateOtherOrgs(User u)1615     public static final boolean userCreateOtherOrgs(User u) {
1616         return userIsAdmin(u);
1617     }
1618 
1619     /** What level can the new user be, given requested? */
1620 
1621     @Deprecated
1622     /**
1623      *
1624      * @param u
1625      * @param requestedLevel
1626      * @return
1627      * @deprecated
1628      * @see Level#canCreateOrSetLevelTo
1629      */
userCanCreateUserOfLevel(User u, int requestedLevel)1630     public static final int userCanCreateUserOfLevel(User u, int requestedLevel) {
1631         if (requestedLevel < 0) {
1632             requestedLevel = 0;
1633         }
1634         if (requestedLevel < u.userlevel) { // pin to creator
1635             requestedLevel = u.userlevel;
1636         }
1637         if (requestedLevel == EXPERT && u.userlevel != ADMIN) {
1638             return VETTER; // only admin can create EXPERT
1639         }
1640         if (u.userlevel == MANAGER) {
1641             if (requestedLevel == MANAGER) {
1642                 return MANAGER;
1643             } else {
1644                 if (requestedLevel < VETTER) {
1645                     return VETTER;
1646                 } else {
1647                     return requestedLevel;
1648                 }
1649             }
1650         }
1651         return requestedLevel;
1652     }
1653 
1654     /** Can the user modify anyone's level? */
userCanModifyUsers(User u)1655     static final boolean userCanModifyUsers(User u) {
1656         return userIsTC(u) || userIsExactlyManager(u);
1657     }
1658 
userCanEmailUsers(User u)1659     static final boolean userCanEmailUsers(User u) {
1660         return userIsTC(u) || userIsExactlyManager(u);
1661     }
1662 
1663     /**
1664      * Returns true if the manager user can change the user's userlevel
1665      * @param managerUser the user doing the changing
1666      * @param targetId the user being changed
1667      * @param targetNewUserLevel the new userlevel of the user
1668      * @return true if the action can proceed, otherwise false
1669      */
userCanModifyUser(User managerUser, int targetId, int targetNewUserLevel)1670     static final boolean userCanModifyUser(User managerUser, int targetId, int targetNewUserLevel) {
1671         if (targetId == ADMIN_ID) {
1672             return false; // can't modify admin user
1673         }
1674         if (managerUser == null) {
1675             return false; // no user
1676         }
1677         if (userIsAdmin(managerUser)) {
1678             return true; // admin can modify everyone
1679         }
1680         final User otherUser = CookieSession.sm.reg.getInfo(targetId); // TODO static
1681         if (otherUser == null) {
1682             return false; // ?
1683         }
1684         if (!managerUser.org.equals(otherUser.org)) {
1685             return false;
1686         }
1687         if (!userCanModifyUsers(managerUser)) {
1688             return false;
1689         }
1690         if (targetId == managerUser.id) {
1691             return false; // cannot modify self
1692         }
1693         if (targetNewUserLevel < managerUser.userlevel) {
1694             return false; // Cannot assign a userlevel higher than the manager
1695         }
1696         return true;
1697     }
1698 
userCanDeleteUser(User managerUser, int targetId, int targetLevel)1699     static final boolean userCanDeleteUser(User managerUser, int targetId, int targetLevel) {
1700         return (userCanModifyUser(managerUser, targetId, targetLevel) && targetLevel > managerUser.userlevel); // must
1701         // be
1702         // at
1703         // a
1704         // lower
1705         // level
1706     }
1707 
userCanDoList(User managerUser)1708     static final boolean userCanDoList(User managerUser) {
1709         return (userIsVetter(managerUser));
1710     }
1711 
userCanCreateUsers(User u)1712     public static final boolean userCanCreateUsers(User u) {
1713         return (userIsTC(u) || userIsExactlyManager(u));
1714     }
1715 
userCanSubmit(User u)1716     static final boolean userCanSubmit(User u) {
1717         if (SurveyMain.isPhaseReadonly())
1718             return false;
1719         return ((u != null) && userIsStreet(u));
1720     }
1721 
1722     /**
1723      * Can the user use the vetting summary page?
1724      * @param u
1725      * @return
1726      */
userCanUseVettingSummary(User u)1727     public static final boolean userCanUseVettingSummary(User u) {
1728         return (u != null) && ( /* userIsExactlyManager(u) || */userIsTC(u));
1729     }
1730 
1731     /**
1732      * Can the user monitor forum participation?
1733      *
1734      * @param u the user
1735      * @return true or false
1736      */
userCanMonitorForum(User u)1737     public static final boolean userCanMonitorForum(User u) {
1738         return userIsTC(u) || userIsExactlyManager(u);
1739     }
1740 
localeMatchesLocaleList(String localeArray[], CLDRLocale locale)1741     static boolean localeMatchesLocaleList(String localeArray[], CLDRLocale locale) {
1742         return localeMatchesLocaleList(stringArrayToLocaleArray(localeArray), locale);
1743     }
1744 
localeMatchesLocaleList(CLDRLocale localeArray[], CLDRLocale locale)1745     static boolean localeMatchesLocaleList(CLDRLocale localeArray[], CLDRLocale locale) {
1746         for (CLDRLocale entry : localeArray) {
1747             if (entry.equals(locale)) {
1748                 return true;
1749             }
1750         }
1751         return false;
1752     }
1753 
localeMatchesLocaleList(String localeList, CLDRLocale locale)1754     static boolean localeMatchesLocaleList(String localeList, CLDRLocale locale) {
1755         if (isAllLocales(localeList)) {
1756             return true;
1757         }
1758         String localeArray[] = tokenizeLocale(localeList);
1759         return localeMatchesLocaleList(localeArray, locale);
1760     }
1761 
1762     public enum ModifyDenial {
1763         DENY_NULL_USER("No user specified"), DENY_LOCALE_READONLY("Locale is read-only"), DENY_PHASE_READONLY(
1764             "SurveyTool is in read-only mode"), DENY_ALIASLOCALE("Locale is an alias"), DENY_DEFAULTCONTENT(
1765                 "Locale is the Default Content for another locale"), DENY_PHASE_CLOSED("SurveyTool is in 'closed' phase"), DENY_NO_RIGHTS(
1766                     "User does not have any voting rights"), DENY_LOCALE_LIST("User does not have rights to vote for this locale");
1767 
ModifyDenial(String reason)1768         ModifyDenial(String reason) {
1769             this.reason = reason;
1770         }
1771 
1772         final String reason;
1773 
getReason()1774         public String getReason() {
1775             return reason;
1776         }
1777     }
1778 
userCanModifyLocale(User u, CLDRLocale locale)1779     public static final boolean userCanModifyLocale(User u, CLDRLocale locale) {
1780         return (userCanModifyLocaleWhy(u, locale) == null);
1781     }
1782 
userCanAccessForum(User u, CLDRLocale locale)1783     public static final boolean userCanAccessForum(User u, CLDRLocale locale) {
1784         return (userCanAccessForumWhy(u, locale) == null);
1785     }
1786 
userCanAccessForumWhy(User u, CLDRLocale locale)1787     private static Object userCanAccessForumWhy(User u, CLDRLocale locale) {
1788         if (u == null)
1789             return ModifyDenial.DENY_NULL_USER; // no user, no dice
1790         if (!userIsStreet(u))
1791             return ModifyDenial.DENY_NO_RIGHTS; // at least street level
1792         if (userIsAdmin(u))
1793             return null; // Admin can modify all
1794         if (userIsTC(u))
1795             return null; // TC can modify all
1796         if (locale.getLanguage().equals("und")) { // all user accounts can write
1797             // to und.
1798             return null;
1799         }
1800         if ((u.locales == null) && userIsExpert(u))
1801             return null; // empty = ALL
1802         if (false || userIsExactlyManager(u))
1803             return null; // manager can edit all
1804         if (isAllLocales(u.locales)) {
1805             return null; // all
1806         }
1807         String localeArray[] = tokenizeLocale(u.locales);
1808         final CLDRLocale languageLocale = locale.getLanguageLocale();
1809         for (final CLDRLocale l : stringArrayToLocaleArray(localeArray)) {
1810             if (l.getLanguageLocale() == languageLocale) {
1811                 return null;
1812             }
1813         }
1814         return ModifyDenial.DENY_LOCALE_LIST;
1815     }
1816 
countUserVoteForLocale(User theSubmitter, CLDRLocale locale)1817     public static boolean countUserVoteForLocale(User theSubmitter, CLDRLocale locale) {
1818         return (countUserVoteForLocaleWhy(theSubmitter, locale) == null);
1819     }
1820 
countUserVoteForLocaleWhy(User u, CLDRLocale locale)1821     public static final ModifyDenial countUserVoteForLocaleWhy(User u, CLDRLocale locale) {
1822         // must not have a null user
1823         if (u == null)
1824             return ModifyDenial.DENY_NULL_USER;
1825 
1826         // can't vote in a readonly locale
1827         if (STFactory.isReadOnlyLocale(locale))
1828             return ModifyDenial.DENY_LOCALE_READONLY;
1829 
1830         // user must have street level perms
1831         if (!userIsStreet(u))
1832             return ModifyDenial.DENY_NO_RIGHTS; // at least street level
1833 
1834         // locales that are aliases can't be modified.
1835         if (sm.isLocaleAliased(locale) != null) {
1836             return ModifyDenial.DENY_ALIASLOCALE;
1837         }
1838 
1839         // locales that are default content parents can't be modified.
1840         CLDRLocale dcParent = sm.getSupplementalDataInfo().getBaseFromDefaultContent(locale);
1841         if (dcParent != null) {
1842             return ModifyDenial.DENY_DEFAULTCONTENT; // it's a defaultcontent
1843             // locale or a pure alias.
1844         }
1845         // admin, TC, and manager can always modify.
1846         if (userIsAdmin(u) ||
1847             userIsTC(u) ||
1848             userIsExactlyManager(u))
1849             return null;
1850 
1851         // the 'und' locale and sublocales can always be modified
1852         if (locale.getLanguage().equals("und")) {
1853             return null;
1854         }
1855 
1856         // unrestricted experts can modify all
1857         if ((u.locales == null || isAllLocales(u.locales)) && userIsExpert(u))
1858             return null; // empty = ALL
1859 
1860         // User has a wildcard (*) - can modify all.
1861         if (isAllLocales(u.locales)) {
1862             return null;
1863         }
1864         String localeArray[] = tokenizeLocale(u.locales);
1865         if (localeMatchesLocaleList(localeArray, locale)) {
1866             return null;
1867         } else {
1868             return ModifyDenial.DENY_LOCALE_LIST;
1869         }
1870     }
1871 
1872     // TODO: speedup. precalculate list of locales on user load.
userCanModifyLocaleWhy(User u, CLDRLocale locale)1873     public static final ModifyDenial userCanModifyLocaleWhy(User u, CLDRLocale locale) {
1874         final ModifyDenial denyCountVote = countUserVoteForLocaleWhy(u, locale);
1875 
1876         // If we don't count the votes, modify is prohibited.
1877         if (denyCountVote != null) {
1878             return denyCountVote;
1879         }
1880 
1881         // We add more restrictions
1882 
1883         // Admin and TC users can always modify, even in closed state.
1884         if (userIsAdmin(u) || userIsTC(u))
1885             return null;
1886 
1887         // Otherwise, if closed, deny
1888         if (SurveyMain.isPhaseClosed())
1889             return ModifyDenial.DENY_PHASE_CLOSED;
1890         if (SurveyMain.isPhaseReadonly())
1891             return ModifyDenial.DENY_PHASE_READONLY;
1892 
1893         return null;
1894     }
1895 
stringArrayToLocaleArray(String[] localeArray)1896     private static CLDRLocale[] stringArrayToLocaleArray(String[] localeArray) {
1897         CLDRLocale arr[] = new CLDRLocale[localeArray.length];
1898         for (int j = 0; j < localeArray.length; j++) {
1899             arr[j] = CLDRLocale.getInstance(localeArray[j]);
1900         }
1901         return arr;
1902     }
1903 
userCanSubmitLocale(User u, CLDRLocale locale)1904     static final boolean userCanSubmitLocale(User u, CLDRLocale locale) {
1905         return userCanSubmitLocaleWhenDisputed(u, locale, false);
1906     }
1907 
userCanSubmitLocaleWhenDisputed(User u, CLDRLocale locale, boolean disputed)1908     static final boolean userCanSubmitLocaleWhenDisputed(User u, CLDRLocale locale, boolean disputed) {
1909         if (SurveyMain.isPhaseReadonly())
1910             return false;
1911         if (u == null)
1912             return false; // no user, no dice
1913         if (userIsTC(u))
1914             return true; // TC can modify all
1915         if ((SurveyMain.phase() == SurveyMain.Phase.VETTING_CLOSED)) {
1916             if (u.userIsSpecialForCLDR15(locale)) {
1917                 return true;
1918             } else {
1919                 return false;
1920             }
1921         }
1922         if (SurveyMain.isPhaseClosed())
1923             return false;
1924         if (!u.userIsSpecialForCLDR15(locale) && SurveyMain.isPhaseVetting() && !disputed && !userIsExpert(u))
1925             return false; // only expert can submit new data.
1926         return userCanModifyLocale(u, locale);
1927     }
1928 
userCanSubmitAnyLocale(User u)1929     static final boolean userCanSubmitAnyLocale(User u) {
1930         if (SurveyMain.isPhaseReadonly())
1931             return false;
1932         if (u == null)
1933             return false; // no user, no dice
1934         if (userIsTC(u))
1935             return true; // TC can modify all
1936         if ((SurveyMain.phase() == SurveyMain.Phase.VETTING_CLOSED)) {
1937             // if(u.userIsSpecialForCLDR15("be")) {
1938             // return true;
1939             // }
1940         }
1941         if (SurveyMain.isPhaseClosed())
1942             return false;
1943         if (SurveyMain.isPhaseVetting() && !userIsExpert(u))
1944             return false; // only expert can submit new data.
1945         return userCanSubmit(u);
1946     }
1947 
userCanVetLocale(User u, CLDRLocale locale)1948     static final boolean userCanVetLocale(User u, CLDRLocale locale) {
1949         if (SurveyMain.isPhaseReadonly())
1950             return false;
1951         if ((SurveyMain.phase() == SurveyMain.Phase.VETTING_CLOSED) && u.userIsSpecialForCLDR15(locale)) {
1952             return true;
1953         }
1954         if (userIsTC(u))
1955             return true; // TC can modify all
1956         if (SurveyMain.isPhaseClosed())
1957             return false;
1958         return userCanModifyLocale(u, locale);
1959     }
1960 
1961     static final String LOCALE_PATTERN = "[, \t\u00a0\\s]+"; // whitespace
1962     /**
1963      * Invalid user ID, representing NO USER.
1964      */
1965     public static final int NO_USER = -1;
1966 
isAllLocales(String localeList)1967     public static final boolean isAllLocales(String localeList) {
1968         return (localeList != null) && (localeList.contains(ALL_LOCALES) || localeList.trim().equals("all"));
1969     }
1970 
1971     /*
1972      * Split into an array. Will return {} if no locales match, or {ALL_LOCALES}==ALL_LOCALES_LIST if it matches all locales
1973      */
tokenizeLocale(String localeList)1974     static String[] tokenizeLocale(String localeList) {
1975         if (isAllLocales(localeList)) {
1976             throw new IllegalArgumentException("Don't call this function with '" + ALL_LOCALES + "' - " + localeList);
1977         }
1978         if ((localeList == null) || ((localeList = localeList.trim()).length() == 0)) {
1979             // System.err.println("TKL: null input");
1980             return new String[0];
1981         }
1982         return localeList.trim().split(LOCALE_PATTERN);
1983     }
1984 
1985     /**
1986      * Tokenize a string, but return an array of CLDRLocales
1987      *
1988      * @param localeList
1989      * @return
1990      */
tokenizeCLDRLocale(String localeList)1991     static CLDRLocale[] tokenizeCLDRLocale(String localeList) {
1992         if (isAllLocales(localeList)) {
1993             throw new IllegalArgumentException("Don't call this function with '" + ALL_LOCALES + "' - " + localeList);
1994         }
1995         if ((localeList == null) || ((localeList = localeList.trim()).length() == 0)) {
1996             // System.err.println("TKL: null input");
1997             return new CLDRLocale[0];
1998         }
1999 
2000         String s[] = tokenizeLocale(localeList);
2001         CLDRLocale l[] = new CLDRLocale[s.length];
2002         for (int j = 0; j < s.length; j++) {
2003             l[j] = CLDRLocale.getInstance(s[j]);
2004         }
2005         return l;
2006     }
2007 
2008     /**
2009      * Tokenize a list, and validate it against actual locales
2010      *
2011      * @param localeList
2012      * @return
2013      */
tokenizeValidCLDRLocale(String localeList)2014     static Set<CLDRLocale> tokenizeValidCLDRLocale(String localeList) {
2015         if (isAllLocales(localeList)) {
2016             throw new IllegalArgumentException("Don't call this function with '" + ALL_LOCALES + "' - " + localeList);
2017         }
2018         Set<CLDRLocale> s = new TreeSet<>();
2019         if (localeList == null || isAllLocales(localeList))
2020             return s; // empty
2021 
2022         Set<CLDRLocale> allLocs = SurveyMain.getLocalesSet();
2023         CLDRLocale locs[] = tokenizeCLDRLocale(localeList);
2024         for (CLDRLocale l : locs) {
2025             if (!allLocs.contains(l)) {
2026                 continue;
2027             }
2028             s.add(l);
2029         }
2030         if (s.isEmpty() && localeList.trim().length() > 0) {
2031             s.add(CLDRLocale.getInstance("und")); // don't set it to 'all'
2032         }
2033         return s;
2034     }
2035 
2036     /**
2037      * take a locale string and convert it to HTML.
2038      */
prettyPrintLocale(String localeList)2039     static String prettyPrintLocale(String localeList) {
2040         if (isAllLocales(localeList)) {
2041             return ("* (<i title='" + localeList + "'>all locales</i>)");
2042         }
2043         // System.err.println("TKL: ppl - " + localeList);
2044         Set<CLDRLocale> localeArray = tokenizeValidCLDRLocale(localeList);
2045         String ret = "";
2046         if ((localeList == null) || (localeList.isEmpty())) {
2047             // System.err.println("TKL: null output");
2048             ret = ("<i title='" + localeList + "'>all locales</i>");
2049         } else if (localeArray.isEmpty()) {
2050             if (localeList.equals("all")) {
2051                 ret = ("<i title='" + localeList + "'>all locales</i>");
2052             } else {
2053                 ret = ("<i style='font-size: smaller' title='" + localeList + "'>no locales</i>");
2054             }
2055         } else {
2056             for (CLDRLocale l : localeArray) {
2057                 ret = ret + " <tt class='codebox' title='" + l.getDisplayName() + "'>" + l.getBaseName() + "</tt> ";
2058             }
2059         }
2060         // return ret + " [" + localeList + "]";
2061         return ret;
2062     }
2063 
2064     Set<User> specialUsers = null;
2065 
2066     /*
2067      * TODO: getSpecialUsers is unused (with or without boolean arg)?
2068      * Delete if the code is obsolete.
2069      */
2070 
getSpecialUsers()2071     public Set<User> getSpecialUsers() {
2072         return getSpecialUsers(false);
2073     }
2074 
getSpecialUsers(boolean reread)2075     public synchronized Set<User> getSpecialUsers(boolean reread) {
2076         if (specialUsers == null) {
2077             reread = true;
2078         }
2079         if (reread == true) {
2080             doReadSpecialUsers();
2081         }
2082         return specialUsers;
2083     }
2084 
doReadSpecialUsers()2085     private synchronized boolean doReadSpecialUsers() {
2086         String externalErrorName = SurveyMain.getSurveyHome() + "/" + "specialusers.txt";
2087 
2088         try {
2089             File extFile = new File(externalErrorName);
2090 
2091             if (!extFile.isFile() && !extFile.canRead()) {
2092                 System.err.println("Can't read special user file: " + externalErrorName);
2093                 return false;
2094             }
2095 
2096             // ok, now read it
2097             BufferedReader in = new BufferedReader(new FileReader(extFile));
2098             String line;
2099             int lines = 0;
2100             Set<User> newSet = new HashSet<>();
2101             System.err.println("* Reading special user file: " + externalErrorName);
2102             while ((line = in.readLine()) != null) {
2103                 lines++;
2104                 line = line.trim();
2105                 if ((line.length() <= 0) || (line.charAt(0) == '#')) {
2106                     continue;
2107                 }
2108                 try {
2109                     int theirId = new Integer(line).intValue();
2110                     User u = getInfo(theirId);
2111                     if (u == null) {
2112                         System.err.println("Could not find user: " + line);
2113                         continue;
2114                     }
2115                     newSet.add(u);
2116                     System.err.println("*+ User: " + u.toString());
2117 
2118                 } catch (Throwable t) {
2119                     System.err.println("** " + externalErrorName + ":" + lines + " -  " + t.toString());
2120                     t.printStackTrace();
2121                     in.close();
2122                     return false;
2123                 }
2124             }
2125             in.close();
2126             System.err.println(externalErrorName + " - " + lines + " and " + newSet.size() + " users loaded.");
2127 
2128             specialUsers = newSet;
2129             return true;
2130         } catch (IOException ioe) {
2131             System.err.println("Reading externalErrorFile: " + "specialusers.txt - " + ioe.toString());
2132             ioe.printStackTrace();
2133             return false;
2134         }
2135     }
2136 
getVoterToInfo(int userid)2137     public VoterInfo getVoterToInfo(int userid) {
2138         return getVoterToInfo().get(userid);
2139     }
2140 
2141     // Interface for VoteResolver interface
2142     /**
2143      * Fetch the user map in VoterInfo format.
2144      *
2145      * @see #userModified()
2146      */
getVoterToInfo()2147     public synchronized Map<Integer, VoterInfo> getVoterToInfo() {
2148         if (voterInfo == null) {
2149             Map<Integer, VoterInfo> map = new TreeMap<>();
2150 
2151             ResultSet rs = null;
2152             Connection conn = null;
2153             try {
2154                 conn = DBUtils.getInstance().getDBConnection();
2155                 rs = list(null, conn);
2156                 // id,userlevel,name,email,org,locales,intlocs,lastlogin
2157                 while (rs.next()) {
2158                     // We don't go through the cache, because not all users may
2159                     // be loaded.
2160 
2161                     User u = new UserRegistry.User(rs.getInt(1));
2162                     // from params:
2163                     u.userlevel = rs.getInt(2);
2164                     u.name = DBUtils.getStringUTF8(rs, 3);
2165                     u.email = rs.getString(4);
2166                     u.org = rs.getString(5);
2167                     u.locales = rs.getString(6);
2168                     if (isAllLocales(u.locales)) {
2169                         u.locales = ALL_LOCALES;
2170                     }
2171                     u.intlocs = rs.getString(7);
2172                     u.last_connect = rs.getTimestamp(8);
2173 
2174                     // now, map it to a UserInfo
2175                     VoterInfo v = u.createVoterInfo();
2176 
2177                     map.put(u.id, v);
2178                 }
2179                 voterInfo = map;
2180             } catch (SQLException se) {
2181                 logger.log(java.util.logging.Level.SEVERE,
2182                     "UserRegistry: SQL error trying to  update VoterInfo - " + DBUtils.unchainSqlException(se), se);
2183             } catch (Throwable t) {
2184                 logger.log(java.util.logging.Level.SEVERE,
2185                     "UserRegistry: some error trying to update VoterInfo - " + t.toString(), t);
2186             } finally {
2187                 // close out the RS
2188                 DBUtils.close(rs, conn);
2189             } // end try
2190         }
2191         return voterInfo;
2192     }
2193 
2194     /**
2195      * VoterInfo map
2196      */
2197     private Map<Integer, VoterInfo> voterInfo = null;
2198 
2199     /**
2200      * Not yet implemented.
2201      *
2202      * TODO: get rid of this code, or document its purpose, referencing a ticket.
2203      *
2204      * @return
2205      */
2206     private static String[] orgList = new String[0];
2207 
getOrgList()2208     public static String[] getOrgList() {
2209         return orgList;
2210     }
2211 
2212     /**
2213      * Update the organization list.
2214      */
setOrgList()2215     public synchronized void setOrgList() {
2216         if (orgList.length > 0) {
2217             return; // already set.
2218         }
2219         resetOrgList();
2220     }
2221 
2222     /**
2223      * TODO: obsolete logic.
2224      */
resetOrgList()2225     private void resetOrgList() {
2226         // get all orgs in use...
2227         Set<String> orgs = new TreeSet<>();
2228         Connection conn = null;
2229         Statement s = null;
2230         try {
2231             conn = DBUtils.getInstance().getDBConnection();
2232             s = conn.createStatement();
2233             ResultSet rs = s.executeQuery("SELECT distinct org FROM " + CLDR_USERS + " order by org");
2234             // System.err.println("Adding orgs...");
2235             while (rs.next()) {
2236                 String org = rs.getString(1);
2237                 // System.err.println("Adding org: "+ org);
2238                 orgs.add(org);
2239             }
2240         } catch (SQLException se) {
2241             /* logger.severe */System.err.println(/*
2242                                                          * java.util.logging.Level.SEVERE
2243                                                          * ,
2244                                                          */"UserRegistry: SQL error trying to get orgs resultset for: VI " + " - "
2245                 + DBUtils.unchainSqlException(se)/* ,se */);
2246         } finally {
2247             // close out the RS
2248             try {
2249                 if (s != null) {
2250                     s.close();
2251                 }
2252                 if (conn != null) {
2253                     DBUtils.closeDBConnection(conn);
2254                 }
2255             } catch (SQLException se) {
2256                 /* logger.severe */System.err.println(/*
2257                                                              * java.util.logging.Level.
2258                                                              * SEVERE,
2259                                                              */"UserRegistry: SQL error trying to close out: "
2260                     + DBUtils.unchainSqlException(se)/* ,se */);
2261             }
2262         } // end try
2263 
2264         // get all possible VR orgs..
2265         Set<Organization> allvr = new HashSet<>();
2266         for (Organization org : Organization.values()) {
2267             allvr.add(org);
2268         }
2269         // Subtract out ones already in use
2270         for (String org : orgs) {
2271             allvr.remove(UserRegistry.computeVROrganization(org));
2272         }
2273         // Add back any ones not yet in use
2274         for (Organization org : allvr) {
2275             String orgName = org.name();
2276             orgName = UCharacter.toTitleCase(orgName, null);
2277             orgs.add(orgName);
2278         }
2279 
2280         orgList = orgs.toArray(orgList);
2281     }
2282 
2283     /**
2284      * Read back an XML file
2285      *
2286      * @param sm
2287      * @param inFile
2288      * @return
2289      */
readUserFile(SurveyMain sm, final File inFile)2290     public int readUserFile(SurveyMain sm, final File inFile) {
2291         int nusers = 0;
2292         if (CLDRConfig.getInstance().getEnvironment() != Environment.SMOKETEST) {
2293             throw new InternalError("Error: can only do this in SMOKETEST"); // insanity
2294             // check
2295         }
2296 
2297         Connection conn = null;
2298         PreparedStatement ps = null;
2299         PreparedStatement ps2 = null;
2300         PreparedStatement ps3 = null;
2301         try { // do this in 1 transaction. just in case.
2302             conn = DBUtils.getInstance().getDBConnection();
2303 
2304             ps = DBUtils.prepareStatementWithArgs(conn, "drop table " + CLDR_USERS);
2305 
2306             int del = ps.executeUpdate();
2307             System.err.println("DELETED " + del + "users.. reading from " + inFile.getAbsolutePath());
2308 
2309             createUserTable(conn);
2310 
2311             XMLFileReader myReader = new XMLFileReader();
2312             final Map<String, String> attrs = new TreeMap<>();
2313 
2314             // <user id="10" email="u_10@apple.example.com" level="vetter"
2315             // name="Apple#10" org="apple" locales="nl nl_BE nl_NL"/>
2316             // >>
2317             // //users/user[@id="10"][@email="__"][@level="vetter"][@name="Apple"][@org="apple"][@locales="nl.. "]
2318             final PreparedStatement myInsert = ps2 = DBUtils.prepareStatementForwardReadOnly(conn, "myInser", "UPDATE "
2319                 + CLDR_USERS + " SET userlevel=?,name=?,org=?,email=?,password=?,locales=?, lastlogin=NULL where id=?");
2320             final PreparedStatement myAdder = ps3 = DBUtils.prepareStatementForwardReadOnly(conn, "myAdder", SQL_insertStmt);
2321             final Connection myConn = conn; // for committing
2322             myReader.setHandler(new XMLFileReader.SimpleHandler() {
2323                 int maxUserId = 1;
2324 
2325                 @Override
2326                 public void handlePathValue(String path, String value) {
2327                     XPathParts xpp = XPathParts.getFrozenInstance(path);
2328                     attrs.clear();
2329                     for (String k : xpp.getAttributeKeys(-1)) {
2330                         attrs.put(k, xpp.getAttributeValue(-1, k));
2331                     }
2332                     String elem = xpp.getElement(-1);
2333                     System.err.println("* <" + elem + " " + attrs.toString() + ">" + value + "</" + elem + ">");
2334 
2335                     try {
2336 
2337                         String xpath = attrs.get("xpath");
2338                         if (xpath != null) {
2339                             xpath = xpath.trim().replace("'", "\"");
2340                         }
2341                         if (elem.equals("user")) {
2342                             int id = Integer.parseInt(attrs.get("id"));
2343                             if (id <= 1) {
2344                                 return; // skip user 1
2345                             }
2346                             while (id > maxUserId) { // loop, until we get to
2347                                 // that ID.
2348                                 ++maxUserId;
2349                                 // userlevel,name,org,email,password,locales,lastlogin
2350                                 myAdder.setInt(1, LOCKED);
2351                                 myAdder.setString(2, "Deleted User " + maxUserId);
2352                                 myAdder.setString(3, "n/a");
2353                                 myAdder.setString(4, "deleted-" + maxUserId + "@nobody.example.com");
2354                                 myAdder.setString(5, makePassword(""));
2355                                 myAdder.setString(6, "");
2356                                 int add = myAdder.executeUpdate();
2357                                 if (add != 1) {
2358                                     throw new InternalError("Trying to add dummy user " + maxUserId);
2359                                 }
2360                             }
2361 
2362                             String name = attrs.get("name");
2363                             String org = attrs.get("org");
2364                             String email = normalizeEmail(attrs.get("email"));
2365                             Level level = Level.valueOf(attrs.get("level"));
2366                             String locales = attrs.get("locales");
2367 
2368                             if (name == null) {
2369                                 name = org + "#" + id;
2370                             }
2371                             if (email == null) {
2372                                 email = org.toLowerCase() + id + "@" + org.toLowerCase() + ".example.com";
2373                             }
2374                             myInsert.setInt(1, level.getSTLevel());
2375                             DBUtils.setStringUTF8(myInsert, 2, name);
2376                             myInsert.setString(3, org);
2377                             myInsert.setString(4, email);
2378                             myInsert.setString(5, makePassword(email));
2379                             myInsert.setString(6, locales);
2380                             myInsert.setInt(7, id);
2381                             int set = myInsert.executeUpdate();
2382                             if (set != 1) {
2383                                 myConn.commit();
2384                                 throw new InternalError("Could not add user " + id + " - max user supposedly " + maxUserId
2385                                     + " :: " + path);
2386                             }
2387                         } else {
2388                             throw new IllegalArgumentException("Unknown test element type " + elem);
2389                         }
2390                     } catch (SQLException e) {
2391                         SurveyLog.logException(e, "importing from " + inFile.getAbsolutePath() + " - " + "* <" + elem + " "
2392                             + attrs.toString() + ">" + value + "</" + elem + ">");
2393                         throw new IllegalArgumentException(e);
2394                     }
2395                 }
2396             });
2397             myReader.read(inFile.getAbsolutePath(), -1, false);
2398             nusers++;
2399             conn.commit();
2400         } catch (SQLException e) {
2401             SurveyLog.logException(e, "importing users from " + inFile.getAbsolutePath());
2402         } finally {
2403             DBUtils.close(ps3, ps2, ps, conn);
2404         }
2405         return nusers;
2406     }
2407 
2408     /*
2409      * <user id="460" email="?@??.??"> > <level n="5"/> > <org>IBM</org> >
2410      * <locales type="edit"> > <locale id="sq"/> > </locales> > </user>
2411      *
2412      * It's probably better to just give VETTER, seems more portable than '5'.
2413      *
2414      * > If it is real info, make it an element. If not (and I think not, for >
2415      * "ibm"), omit it.
2416      *
2417      * In the comments are the VoteResolver enum value. I'll probably just use
2418      * that value.
2419      *
2420      * > 5. More issues with that. The structure is inconsistent, with some >
2421      * info in attributes and some in elements. Should be one or the other. > >
2422      * all attributes: > > <user id="460" email="?@??.??" level="5" org="IBM"
2423      * edit="sq de"/> > > all elements > > <user id="460"> >
2424      * <email>?@??.??</email> > <level/>5</level> > <org>IBM</org> >
2425      * <edit>sq</edit> > <edit>de</edit> > </user> >
2426      */
2427 
2428     /**
2429      * @param sm
2430      *            TODO
2431      * @param ourDate
2432      * @param obscured
2433      * @param outFile
2434      * @throws UnsupportedEncodingException
2435      * @throws FileNotFoundException
2436      */
writeUserFile(SurveyMain sm, String ourDate, boolean obscured, File outFile)2437     int writeUserFile(SurveyMain sm, String ourDate, boolean obscured, File outFile) throws UnsupportedEncodingException,
2438         FileNotFoundException {
2439         PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF8"));
2440         // } catch (UnsupportedEncodingException e) {
2441         // throw new InternalError("UTF8 unsupported?").setCause(e);
2442         out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
2443         // ctx.println("<!DOCTYPE ldml SYSTEM \"http://.../.../stusers.dtd\">");
2444         out.println("<users generated=\"" + ourDate + "\" obscured=\"" + obscured + "\">");
2445         String org = null;
2446         Connection conn = null;
2447         try {
2448             conn = DBUtils.getInstance().getDBConnection();
2449             synchronized (this) {
2450                 java.sql.ResultSet rs = list(org, conn);
2451                 if (rs == null) {
2452                     out.println("\t<!-- No results -->");
2453                     return 0;
2454                 }
2455                 Organization lastOrg = null;
2456                 while (rs.next()) {
2457                     int theirId = rs.getInt(1);
2458                     int theirLevel = rs.getInt(2);
2459                     String theirName = obscured ? ("#" + theirId) : DBUtils.getStringUTF8(rs, 3).trim();// rs.getString(3);
2460                     String theirEmail = obscured ? /* "?@??.??" */"" : rs.getString(4).trim();
2461                     String theirOrg = rs.getString(5);
2462                     String theirLocales = rs.getString(6);
2463 
2464                     Organization theOrg = computeVROrganization(theirOrg);
2465                     if (theOrg == null) {
2466                         SurveyLog.warnOnce("UserRegistry: Writing Illegal/unknown org: " + theirOrg);
2467                     }
2468                     if (!theOrg.equals(lastOrg)) {
2469                         out.println("<!-- " + SurveyMain.xmlescape(theirOrg) + " -->");
2470                         lastOrg = theOrg;
2471                     }
2472                     out.print("\t<user id=\"" + theirId + "\" ");
2473                     if (theirEmail.length() > 0)
2474                         out.print("email=\"" + theirEmail + "\" ");
2475                     out.print("level=\"" + UserRegistry.levelAsStr(theirLevel).toLowerCase() + "\"");
2476                     if (theirEmail.length() > 0)
2477                         out.print(" name=\"" + SurveyMain.xmlescape(theirName) + "\"");
2478                     out.print(" " + "org=\"" + theOrg + "\" locales=\"");
2479                     if (UserRegistry.isAllLocales(theirLocales)) {
2480                         out.print('*');
2481                     } else {
2482                         String theirLocalesList[] = UserRegistry.tokenizeLocale(theirLocales);
2483                         for (int i = 0; i < theirLocalesList.length; i++) {
2484                             if (i > 0)
2485                                 out.print(" ");
2486                             out.print(theirLocalesList[i]);
2487                         }
2488                     }
2489                     out.println("\"/>");
2490                 }
2491             } /* end synchronized(reg) */
2492         } catch (SQLException se) {
2493             SurveyLog.logger.log(java.util.logging.Level.WARNING,
2494                 "Query for org " + org + " failed: " + DBUtils.unchainSqlException(se), se);
2495             out.println("<!-- Failure: " + DBUtils.unchainSqlException(se) + " -->");
2496         } finally {
2497             DBUtils.close(conn);
2498         }
2499         out.println("</users>");
2500         out.close();
2501         return 1;
2502     }
2503 
2504     /**
2505      * Cache of the set of anonymous users
2506      */
2507     private Set<User> anonymousUsers = null;
2508 
2509     /**
2510      * Get the set of anonymous users, employing a cache.
2511      * If there aren't as many as there should be (ANONYMOUS_USER_COUNT), create some.
2512      * An "anonymous user" is one whose userlevel is ANONYMOUS.
2513      *
2514      * @return the Set.
2515      */
getAnonymousUsers()2516     public Set<User> getAnonymousUsers() {
2517         if (anonymousUsers == null) {
2518             anonymousUsers = getAnonymousUsersFromDb();
2519             int existingCount = anonymousUsers.size();
2520             if (existingCount < ANONYMOUS_USER_COUNT) {
2521                 createAnonymousUsers(existingCount, ANONYMOUS_USER_COUNT);
2522                 /*
2523                  * After createAnonymousUsers, call userModified to clear voterInfo, so it will
2524                  * be reloaded and include the new anonymous users. Otherwise, we would get an
2525                  * "Unknown voter" exception in OrganizationToValueAndVote.add when trying to
2526                  * add a vote for one of the new anonymous users.
2527                  */
2528                 userModified();
2529                 anonymousUsers = getAnonymousUsersFromDb();
2530             }
2531         }
2532         return anonymousUsers;
2533     }
2534 
2535     /**
2536      * Get the set of anonymous users by running a database query.
2537      *
2538      * @return the Set.
2539      */
getAnonymousUsersFromDb()2540     private Set<User> getAnonymousUsersFromDb() {
2541         Set<User> set = new HashSet<>();
2542         ResultSet rs = null;
2543         Connection conn = null;
2544         try {
2545             conn = DBUtils.getInstance().getDBConnection();
2546             rs = list(null, conn);
2547             // id,userlevel,name,email,org,locales,intlocs,lastlogin
2548             while (rs.next()) {
2549                 int userlevel = rs.getInt(2);
2550                 if (userlevel == ANONYMOUS) {
2551                     User u = new User(rs.getInt(1));
2552                     u.userlevel = userlevel;
2553                     u.name = DBUtils.getStringUTF8(rs, 3);
2554                     u.email = rs.getString(4);
2555                     u.org = rs.getString(5);
2556                     u.locales = rs.getString(6);
2557                     if (isAllLocales(u.locales)) {
2558                         u.locales = ALL_LOCALES;
2559                     }
2560                     u.intlocs = rs.getString(7);
2561                     u.last_connect = rs.getTimestamp(8);
2562                     set.add(u);
2563                 }
2564             }
2565         } catch (SQLException se) {
2566             logger.log(java.util.logging.Level.SEVERE,
2567                 "UserRegistry: SQL error getting anonymous users - " + DBUtils.unchainSqlException(se), se);
2568         } catch (Throwable t) {
2569             logger.log(java.util.logging.Level.SEVERE,
2570                 "UserRegistry: some error getting anonymous users - " + t.toString(), t);
2571         } finally {
2572             DBUtils.close(rs, conn);
2573         }
2574         return set;
2575     }
2576 
2577     /**
2578      * Given that there aren't enough anonymous users in the database yet, create some.
2579      *
2580      * @param existingCount the number of anonymous users that already exist
2581      * @param desiredCount the desired total number of anonymous users
2582      */
createAnonymousUsers(int existingCount, int desiredCount)2583     private void createAnonymousUsers(int existingCount, int desiredCount) {
2584         Connection conn = null;
2585         Statement s = null;
2586         try {
2587             conn = DBUtils.getInstance().getDBConnection();
2588             s = conn.createStatement();
2589             for (int i = existingCount + 1; i <= desiredCount; i++) {
2590                 /*
2591                  * Don't specify the user id; a new unique id will be assigned automatically.
2592                  * Names are like "anon#3"; emails are like "anon3@example.org".
2593                  */
2594                 String sql = "INSERT INTO " + CLDR_USERS
2595                     + "(userlevel, name, email, org, password, locales) VALUES("
2596                     + ANONYMOUS + ","                // userlevel
2597                     + "'anon#" + i + "',"            // name
2598                     + "'anon" + i + "@example.org'," // email
2599                     + "'cldr',"                      // org
2600                     + "'',"                          // password
2601                     + "'" + ALL_LOCALES + "')";      // locales
2602                 s.execute(sql);
2603             }
2604             conn.commit();
2605         } catch (SQLException se) {
2606             logger.log(java.util.logging.Level.SEVERE,
2607                 "UserRegistry: SQL error creating anonymous users - " + DBUtils.unchainSqlException(se), se);
2608         } catch (Throwable t) {
2609             logger.log(java.util.logging.Level.SEVERE,
2610                 "UserRegistry: some error creating anonymous users - " + t.toString(), t);
2611         } finally {
2612             DBUtils.close(s, conn);
2613         }
2614     }
2615 }
2616