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