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