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