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