1 //
  2 // The Antville Project
  3 // http://code.google.com/p/antville
  4 //
  5 // Copyright 2001-2007 by The Antville People
  6 //
  7 // Licensed under the Apache License, Version 2.0 (the ``License'');
  8 // you may not use this file except in compliance with the License.
  9 // You may obtain a copy of the License at
 10 //
 11 //    http://www.apache.org/licenses/LICENSE-2.0
 12 //
 13 // Unless required by applicable law or agreed to in writing, software
 14 // distributed under the License is distributed on an ``AS IS'' BASIS,
 15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 16 // See the License for the specific language governing permissions and
 17 // limitations under the License.
 18 //
 19 // $Revision$
 20 // $LastChangedBy$
 21 // $LastChangedDate$
 22 // $URL$
 23 //
 24 
 25 /**
 26  * @fileOverview Defines the User prototype.
 27  */
 28 
 29 markgettext("User");
 30 markgettext("user");
 31 
 32 disableMacro(User, "hash");
 33 disableMacro(User, "salt");
 34 
 35 this.handleMetadata("hash");
 36 this.handleMetadata("salt");
 37 this.handleMetadata("url");
 38 
 39 /** @constant */
 40 User.COOKIE = getProperty("userCookie", "antvilleUser");
 41 
 42 /** @constant */
 43 User.HASHCOOKIE = getProperty("hashCookie", "antvilleHash");
 44 
 45 /**
 46  * 
 47  * @param {String} name
 48  * @returns {User}
 49  */
 50 User.getByName = function(name) {
 51    return root.users.get(name);
 52 }
 53 
 54 /**
 55  * @function
 56  * @returns {String[]}
 57  * @see defineConstants
 58  */
 59 User.getStatus = defineConstants(User, markgettext("Blocked"), 
 60       markgettext("Regular"), markgettext("Trusted"), 
 61       markgettext("Privileged"));
 62 
 63 /**
 64  * @returns {String}
 65  */
 66 User.getSalt = function() {
 67    var salt = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 8);
 68    var random = java.security.SecureRandom.getInstance("SHA1PRNG");
 69    random.nextBytes(salt);
 70    return Packages.sun.misc.BASE64Encoder().encode(salt);
 71 }
 72 
 73 /**
 74  * 
 75  * @param {Object} data
 76  * @throws {Error}
 77  * @returns {User}
 78  */
 79 User.register = function(data) {
 80    var name = stripTags(data.name);
 81    if (!data.name) {
 82       throw Error(gettext("Please enter a username."));
 83    } else if (data.name.length > 30) {
 84       throw Error(gettext("Sorry, the username you entered is too long. Please choose a shorter one."));
 85    } else if (data.name !== name || NAMEPATTERN.test(name)) {
 86       throw Error(gettext("Please avoid special characters or HTML code in the name field."));
 87    } else if (name !== root.users.getAccessName(name)) {
 88       throw Error(gettext("Sorry, the user name you entered already exists. Please enter a different one."));
 89    }
 90 
 91    if (!validateEmail(data.email)) {
 92       throw Error(gettext("Please enter a valid e-mail address"));
 93    }
 94 
 95    // Create hash from password for JavaScript-disabled browsers
 96    if (!data.hash) {
 97       // Check if passwords match
 98       if (!data.password || !data.passwordConfirm) {
 99          throw Error(gettext("Could not verify your password. Please repeat your input."))
100       } else if (data.password !== data.passwordConfirm) {
101          throw Error(gettext("Unfortunately, your passwords did not match. Please repeat your input."));
102       }
103       data.hash = (data.password + session.data.token).md5();
104    }
105 
106    if (User.getByName(data.name)) {
107       throw Error(gettext("Sorry, the user name you entered already exists. Please enter a different one."));
108    }
109    var user = new User(data);
110    // grant trust and sysadmin-rights if there's no sysadmin 'til now
111    if (root.admins.size() < 1) {
112       user.status = User.PRIVILEGED;
113    }
114    root.users.add(user);
115    session.login(user);
116    return user;
117 }
118 
119 /**
120  * 
121  */
122 User.autoLogin = function() {
123    if (session.user) {
124       return;
125    }
126    var name = req.cookies[User.COOKIE];
127    var hash = req.cookies[User.HASHCOOKIE];
128    if (!name || !hash) {
129       return;
130    }
131    var user = User.getByName(name);
132    if (!user) {
133       return;
134    }
135    var ip = req.data.http_remotehost.clip(getProperty("cookieLevel", "4"),
136          "", "\\.");
137    if ((user.hash + ip).md5() !== hash) {
138       return;
139    }
140    session.login(user);
141    user.touch();
142    res.message = gettext('Welcome to {0}, {1}. Have fun!',
143          res.handlers.site.title, user.name);
144    return;
145 }
146 
147 /**
148  * 
149  * @param {Object} data
150  * @returns {User}
151  */
152 User.login = function(data) {
153    var user = User.getByName(data.name);
154    if (!user) {
155       throw Error(gettext("Unfortunately, your login failed. Maybe a typo?"));
156    }
157    var digest = data.digest;
158    // Calculate digest for JavaScript-disabled browsers
159    if (!digest) {
160       app.logger.warn("Received clear text password from " + req.data.http_referer);
161       digest = ((data.password + user.salt).md5() + session.data.token).md5();
162    }
163    // Check if login is correct
164    if (digest !== user.getDigest(session.data.token)) {
165       throw Error(gettext("Unfortunately, your login failed. Maybe a typo?"))
166    }
167    if (data.remember) {
168       // Set long running cookies for automatic login
169       res.setCookie(User.COOKIE, user.name, 365);
170       var ip = req.data.http_remotehost.clip(getProperty("cookieLevel", "4"), 
171             "", "\\.");
172       res.setCookie(User.HASHCOOKIE, (user.hash + ip).md5(), 365);   
173    }
174    user.touch();
175    session.login(user);
176    return user;
177 }
178 
179 /**
180  * 
181  */
182 User.logout = function() {
183   session.logout();
184   res.setCookie(User.COOKIE, String.EMPTY);
185   res.setCookie(User.HASHCOOKIE, String.EMPTY);
186   User.getLocation();
187   return;
188 }
189 
190 /**
191  * 
192  * @param {String} requiredStatus
193  * @returns {Boolean}
194  */
195 User.require = function(requiredStatus) {
196    var status = [User.BLOCKED, User.REGULAR, User.TRUSTED, User.PRIVILEGED];
197    if (requiredStatus && session.user) {
198       return status.indexOf(session.user.status) >= status.indexOf(requiredStatus);
199    }
200    return false;
201 }
202 
203 /**
204  * @returns {String}
205  */
206 User.getCurrentStatus = function() {
207    if (session.user) {
208       return session.user.status;
209    }
210    return null;
211 }
212 
213 /**
214  * @returns {Membership}
215  */
216 User.getMembership = function() {
217    var membership;
218    if (session.user) {
219       membership = Membership.getByName(session.user.name);
220    }
221    return membership || new Membership;
222 }
223 
224 /**
225  * 
226  * @param {String} url
227  */
228 User.setLocation = function(url) {
229    session.data.location = url || req.data.http_referer;
230    //app.debug("Pushed location " + session.data.location);
231    return;
232 }
233 
234 /**
235  * @returns {String}
236  */
237 User.getLocation = function() {
238    var url = session.data.location;
239    delete session.data.location;
240    //app.debug("Popped location " + url);
241    return url;
242 }
243 
244 /**
245  * Rename a user account.
246  * @param {String} currentName The current name of the user account.
247  * @param {String} newName The desired name of the user account.
248  */
249 User.rename = function(currentName, newName) {
250    var user = User.getByName(currentName);
251    if (user) {
252       user.forEach(function() {
253          this.name = newName;
254       });
255       user.name = newName;
256    }
257    return;
258 }
259 
260 /**
261  * A User object represents a login to Antville.
262  * @name User
263  * @constructor
264  * @param {Object} data
265  * @extends HopObject
266  * @property {Membership[]} _children
267  * @property {Date} created
268  * @property {Comment[]} comments
269  * @property {String} email
270  * @property {File[]} files
271  * @property {String} hash
272  * @property {Image[]} images
273  * @property {Membership[]} memberships
274  * @property {Metadata} metadata
275  * @property {Date} modified
276  * @property {String} name 
277  * @property {String} salt
278  * @property {Site[]} sites
279  * @property {Membership[]} subscriptions
280  * @property {String} status
281  * @property {Story[]} stories
282  * @extends HopObject
283  */
284 User.prototype.constructor = function(data) {
285    var now = new Date;
286    this.map({
287       name: data.name,
288       hash: data.hash,
289       salt: session.data.token,
290       email: data.email,
291       status: User.REGULAR,
292       created: now,
293       modified: now
294    });
295    return this;
296 }
297 
298 /**
299  * 
300  */
301 User.prototype.onLogout = function() { /* ... */ }
302 
303 /**
304  * 
305  * @param {String} action
306  * @returns {Boolean}
307  */
308 User.prototype.getPermission = function(action) {
309    return User.require(User.PRIVILEGED);
310 }
311 
312 /**
313  * 
314  * @param {Object} data
315  */
316 User.prototype.update = function(data) {
317    if (!data.digest && data.password) {
318       data.digest = ((data.password + this.salt).md5() + 
319             session.data.token).md5();
320    }
321    if (data.digest) {
322       if (data.digest !== this.getDigest(session.data.token)) {
323          throw Error(gettext("Oops, your old password is incorrect. Please re-enter it."));
324       }
325       if (!data.hash) {
326          if (!data.newPassword || !data.newPasswordConfirm) {
327             throw Error(gettext("Please specify a new password."));
328          } else if (data.newPassword !== data.newPasswordConfirm) {
329             throw Error(gettext("Unfortunately, your passwords did not match. Please repeat your input."));
330          }
331          data.hash = (data.newPassword + session.data.token).md5();
332       }
333       this.map({
334          hash: data.hash,
335          salt: session.data.token         
336       });
337    }
338    if (!(this.email = validateEmail(data.email))) {
339       throw Error(gettext("Please enter a valid e-mail address"));
340    }   
341    if (data.url && !(this.url = validateUrl(data.url))) {
342       throw Error(gettext("Please enter a valid URL"));
343    }
344    return this;
345 }
346 
347 /**
348  * 
349  */
350 User.prototype.touch = function() {
351    this.modified = new Date;
352    return;
353 }
354 
355 /**
356  * 
357  * @param {String} token
358  * @returns {String}
359  */
360 User.prototype.getDigest = function(token) {
361    token || (token = String.EMPTY);
362    return (this.hash + token).md5();
363 }
364 
365 /**
366  * 
367  * @param {String} name
368  * @returns {Object}
369  */
370 User.prototype.getFormOptions = function(name) {
371    switch (name) {
372       case "status":
373       return User.getStatus();
374    }
375 }
376 
377 /**
378  * Enable <% user.email %> macro for privileged users only
379  */
380 User.prototype.email_macro = function() {
381    if (User.require(User.PRIVILEGED)) {
382       res.write(this.email);
383    }
384    return;
385 }
386 
387 /**
388  * 
389  * @param {Object} param
390  * @param {String} type
391  */
392 User.prototype.list_macro = function(param, type) {
393    switch (type) {
394       case "sites":
395       var memberships = session.user.list();
396       memberships.sort(function(a, b) {
397          return b.site.modified - a.site.modified;
398       });
399       memberships.forEach(function(membership) {
400          var site;
401          if (site = membership.get("site")) {
402             site.renderSkin("$Site#listItem");
403          }
404          return;
405       });
406    }
407    return;
408 }
409