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 /** @constant */
 30 User.COOKIE = getProperty("userCookie", "antvilleUser");
 31 /** @constant */
 32 User.HASHCOOKIE = getProperty("hashCookie", "antvilleHash");
 33 
 34 /**
 35  * @function
 36  * @returns {String[]}
 37  * @see defineConstants
 38  */
 39 User.getStatus = defineConstants(User, "blocked", 
 40       "regular", "trusted", "privileged");
 41 
 42 this.handleMetadata("hash");
 43 this.handleMetadata("salt");
 44 this.handleMetadata("url");
 45 
 46 disableMacro(User, "password");
 47 disableMacro(User, "hash");
 48 disableMacro(User, "email");
 49 
 50 /**
 51  * A User object represents a login to Antville.
 52  * @name User
 53  * @constructor
 54  * @param {Object} data
 55  * @extends HopObject
 56  * @property {Membership[]} _children
 57  * @property {Date} created
 58  * @property {Comment[]} comments
 59  * @property {String} email
 60  * @property {File[]} files
 61  * @property {String} hash
 62  * @property {Image[]} images
 63  * @property {Membership[]} memberships
 64  * @property {Metadata} metadata
 65  * @property {Date} modified
 66  * @property {String} name 
 67  * @property {String} salt
 68  * @property {Site[]} sites
 69  * @property {Membership[]} subscriptions
 70  * @property {String} status
 71  * @property {Story[]} stories
 72  * @extends HopObject
 73  */
 74 User.prototype.constructor = function(data) {
 75    var now = new Date;
 76    this.map({
 77       name: data.name,
 78       hash: data.hash,
 79       salt: session.data.token,
 80       email: data.email,
 81       status: User.REGULAR,
 82       created: now,
 83       modified: now
 84    });
 85    return this;
 86 }
 87 
 88 /**
 89  * 
 90  */
 91 User.prototype.onLogout = function() { /* ... */ }
 92 
 93 /**
 94  * 
 95  * @param {String} action
 96  * @returns {Boolean}
 97  */
 98 User.prototype.getPermission = function(action) {
 99    return User.require(User.PRIVILEGED);
100 }
101 
102 /**
103  * 
104  * @param {Object} data
105  */
106 User.prototype.update = function(data) {
107    if (!data.digest && data.password) {
108       data.digest = ((data.password + this.salt).md5() + 
109             session.data.token).md5();
110    }
111    if (data.digest) {
112       if (data.digest !== this.getDigest(session.data.token)) {
113          throw Error(gettext("Oops, your old password is incorrect. Please re-enter it."));
114       }
115       if (!data.hash) {
116          if (!data.newPassword || !data.newPasswordConfirm) {
117             throw Error(gettext("Please specify a new password."));
118          } else if (data.newPassword !== data.newPasswordConfirm) {
119             throw Error(gettext("Unfortunately, your passwords did not match. Please repeat your input."));
120          }
121          data.hash = (data.newPassword + session.data.token).md5();
122       }
123       this.map({
124          hash: data.hash,
125          salt: session.data.token         
126       });
127    }
128    if (!(this.email = validateEmail(data.email))) {
129       throw Error(gettext("Please enter a valid e-mail address"));
130    }
131    
132 	if(data.url != "") {
133       if (!(this.url = validateUrl(data.url))) {
134         throw Error(gettext("Please enter a valid URL"));
135       }
136    }
137    return this;
138 }
139 
140 /**
141  * 
142  */
143 User.prototype.touch = function() {
144    this.modified = new Date;
145    return;
146 }
147 
148 /**
149  * 
150  * @param {String} token
151  * @returns {String}
152  */
153 User.prototype.getDigest = function(token) {
154    token || (token = String.EMPTY);
155    return (this.hash + token).md5();
156 }
157 
158 /**
159  * 
160  * @param {String} name
161  * @returns {Object}
162  */
163 User.prototype.getFormOptions = function(name) {
164    switch (name) {
165       case "status":
166       return User.getStatus();
167    }
168 }
169 
170 /**
171  * 
172  * @param {Object} param
173  * @param {String} type
174  */
175 User.prototype.list_macro = function(param, type) {
176    switch (type) {
177       case "sites":
178       var memberships = session.user.list();
179       memberships.sort(function(a, b) {
180          return b.site.modified - a.site.modified;
181       });
182       memberships.forEach(function(membership) {
183          var site;
184          if (site = membership.get("site")) {
185             site.renderSkin("$Site#listItem");
186          }
187          return;
188       });
189    }
190    return;
191 }
192 
193 /**
194  * 
195  * @param {String} name
196  * @returns {User}
197  */
198 User.getByName = function(name) {
199    return root.users.get(name);
200 }
201 
202 /**
203  * @returns {String}
204  */
205 User.getSalt = function() {
206    var salt = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 8);;
207    var random = java.security.SecureRandom.getInstance("SHA1PRNG");
208    random.nextBytes(salt);
209    return Packages.sun.misc.BASE64Encoder().encode(salt);
210 }
211 
212 /**
213  * 
214  * @param {Object} data
215  * @throws {Error}
216  * @returns {User}
217  */
218 User.register = function(data) {
219    // check if username is existing and is clean
220    // can't use isClean() here because we accept
221    // special characters like . - and space
222    var invalidChar = new RegExp("[^a-zA-Z0-9\\.\\-_ ]");
223    if (!data.name) {
224       throw Error(gettext("Please enter a username."));
225    } else if (data.name.length > 30) {
226       throw Error(gettext("Sorry, the username you entered is too long. Please choose a shorter one."));
227    } else if (invalidChar.exec(data.name)) {
228       throw Error(gettext("Please enter alphanumeric characters only in the username field."));
229    } else if (this[data.name] || this[data.name + "_action"]) {
230       throw Error(gettext("Sorry, the user name you entered already exists. Please enter a different one."));
231    }
232 
233    // check if email-address is valid
234    if (!data.email) {
235       throw Error(gettext("Please enter your e-mail address."));
236    }
237    validateEmail(data.email);
238 
239    // Create hash from password for JavaScript-disabled browsers
240    if (!data.hash) {
241       // Check if passwords match
242       if (!data.password || !data.passwordConfirm) {
243          throw Error(gettext("Could not verify your password. Please repeat your input."))
244       } else if (data.password !== data.passwordConfirm) {
245          throw Error(gettext("Unfortunately, your passwords did not match. Please repeat your input."));
246       }
247       data.hash = (data.password + session.data.token).md5();
248    }
249 
250    if (User.getByName(data.name)) {
251       throw Error(gettext("Sorry, the user name you entered already exists. Please enter a different one."));
252    }
253    var user = new User(data);
254    // grant trust and sysadmin-rights if there's no sysadmin 'til now
255    if (root.admins.size() < 1) {
256       user.status = User.PRIVILEGED;
257    }
258    root.users.add(user);
259    session.login(user);
260    return user;
261 }
262 
263 /**
264  * 
265  */
266 User.autoLogin = function() {
267    if (session.user) {
268       return;
269    }
270    var name = req.cookies[User.COOKIE];
271    var hash = req.cookies[User.HASHCOOKIE];
272    if (!name || !hash) {
273       return;
274    }
275    var user = User.getByName(name);
276    if (!user) {
277       return;
278    }
279    var ip = req.data.http_remotehost.clip(getProperty("cookieLevel", "4"),
280          "", "\\.");
281    if ((user.hash + ip).md5() !== hash) {
282       return;
283    }
284    session.login(user);
285    user.touch();
286    res.message = gettext('Welcome to {0}, {1}. Have fun!',
287          res.handlers.site.title, user.name);
288    return;
289 }
290 
291 /**
292  * 
293  * @param {Object} data
294  * @returns {User}
295  */
296 User.login = function(data) {
297    var user = User.getByName(data.name);
298    if (!user) {
299       throw Error(gettext("Unfortunately, your login failed. Maybe a typo?"));
300    }
301    var digest = data.digest;
302    // Calculate digest for JavaScript-disabled browsers
303    if (!digest) {
304       app.logger.warn("Received clear text password from " + req.data.http_referer);
305       digest = ((data.password + user.salt).md5() + session.data.token).md5();
306    }
307    // Check if login is correct
308    if (digest !== user.getDigest(session.data.token)) {
309       throw Error(gettext("Unfortunately, your login failed. Maybe a typo?"))
310    }
311    if (data.remember) {
312       // Set long running cookies for automatic login
313       res.setCookie(User.COOKIE, user.name, 365);
314       var ip = req.data.http_remotehost.clip(getProperty("cookieLevel", "4"), 
315             "", "\\.");
316       res.setCookie(User.HASHCOOKIE, (user.hash + ip).md5(), 365);   
317    }
318    user.touch();
319    session.login(user);
320    return user;
321 }
322 
323 /**
324  * 
325  */
326 User.logout = function() {
327   session.logout();
328   res.setCookie(User.COOKIE, String.EMPTY);
329   res.setCookie(User.HASHCOOKIE, String.EMPTY);
330   User.getLocation();
331   return;
332 }
333 
334 /**
335  * 
336  * @param {String} requiredStatus
337  * @returns {Boolean}
338  */
339 User.require = function(requiredStatus) {
340    var status = [User.BLOCKED, User.REGULAR, User.TRUSTED, User.PRIVILEGED];
341    if (requiredStatus && session.user) {
342       return status.indexOf(session.user.status) >= status.indexOf(requiredStatus);
343    }
344    return false;
345 }
346 
347 /**
348  * @returns {String}
349  */
350 User.getCurrentStatus = function() {
351    if (session.user) {
352       return session.user.status;
353    }
354    return null;
355 }
356 
357 /**
358  * @returns {Membership}
359  */
360 User.getMembership = function() {
361    var membership;
362    if (session.user) {
363       membership = Membership.getByName(session.user.name);
364    }
365    return membership || new Membership;
366 }
367 
368 /**
369  * 
370  * @param {String} url
371  */
372 User.setLocation = function(url) {
373    session.data.location = url || req.data.http_referer;
374    //app.debug("Pushed location " + session.data.location);
375    return;
376 }
377 
378 /**
379  * @returns {String}
380  */
381 User.getLocation = function() {
382    var url = session.data.location;
383    delete session.data.location;
384    //app.debug("Popped location " + url);
385    return url;
386 }
387