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  * A User object represents a login to Antville.
246  * @name User
247  * @constructor
248  * @param {Object} data
249  * @extends HopObject
250  * @property {Membership[]} _children
251  * @property {Date} created
252  * @property {Comment[]} comments
253  * @property {String} email
254  * @property {File[]} files
255  * @property {String} hash
256  * @property {Image[]} images
257  * @property {Membership[]} memberships
258  * @property {Metadata} metadata
259  * @property {Date} modified
260  * @property {String} name 
261  * @property {String} salt
262  * @property {Site[]} sites
263  * @property {Membership[]} subscriptions
264  * @property {String} status
265  * @property {Story[]} stories
266  * @extends HopObject
267  */
268 User.prototype.constructor = function(data) {
269    var now = new Date;
270    this.map({
271       name: data.name,
272       hash: data.hash,
273       salt: session.data.token,
274       email: data.email,
275       status: User.REGULAR,
276       created: now,
277       modified: now
278    });
279    return this;
280 }
281 
282 /**
283  * 
284  */
285 User.prototype.onLogout = function() { /* ... */ }
286 
287 /**
288  * 
289  * @param {String} action
290  * @returns {Boolean}
291  */
292 User.prototype.getPermission = function(action) {
293    return User.require(User.PRIVILEGED);
294 }
295 
296 /**
297  * 
298  * @param {Object} data
299  */
300 User.prototype.update = function(data) {
301    if (!data.digest && data.password) {
302       data.digest = ((data.password + this.salt).md5() + 
303             session.data.token).md5();
304    }
305    if (data.digest) {
306       if (data.digest !== this.getDigest(session.data.token)) {
307          throw Error(gettext("Oops, your old password is incorrect. Please re-enter it."));
308       }
309       if (!data.hash) {
310          if (!data.newPassword || !data.newPasswordConfirm) {
311             throw Error(gettext("Please specify a new password."));
312          } else if (data.newPassword !== data.newPasswordConfirm) {
313             throw Error(gettext("Unfortunately, your passwords did not match. Please repeat your input."));
314          }
315          data.hash = (data.newPassword + session.data.token).md5();
316       }
317       this.map({
318          hash: data.hash,
319          salt: session.data.token         
320       });
321    }
322    if (!(this.email = validateEmail(data.email))) {
323       throw Error(gettext("Please enter a valid e-mail address"));
324    }   
325    if (data.url && !(this.url = validateUrl(data.url))) {
326       throw Error(gettext("Please enter a valid URL"));
327    }
328    return this;
329 }
330 
331 /**
332  * 
333  */
334 User.prototype.touch = function() {
335    this.modified = new Date;
336    return;
337 }
338 
339 /**
340  * 
341  * @param {String} token
342  * @returns {String}
343  */
344 User.prototype.getDigest = function(token) {
345    token || (token = String.EMPTY);
346    return (this.hash + token).md5();
347 }
348 
349 /**
350  * 
351  * @param {String} name
352  * @returns {Object}
353  */
354 User.prototype.getFormOptions = function(name) {
355    switch (name) {
356       case "status":
357       return User.getStatus();
358    }
359 }
360 
361 /**
362  * Enable <% user.email %> macro for privileged users only
363  */
364 User.prototype.email_macro = function() {
365    if (User.require(User.PRIVILEGED)) {
366       res.write(this.email);
367    }
368    return;
369 }
370 
371 /**
372  * 
373  * @param {Object} param
374  * @param {String} type
375  */
376 User.prototype.list_macro = function(param, type) {
377    switch (type) {
378       case "sites":
379       var memberships = session.user.list();
380       memberships.sort(function(a, b) {
381          return b.site.modified - a.site.modified;
382       });
383       memberships.forEach(function(membership) {
384          var site;
385          if (site = membership.get("site")) {
386             site.renderSkin("$Site#listItem");
387          }
388          return;
389       });
390    }
391    return;
392 }
393