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:3329 $
 20 // $LastChangedBy:piefke3000 $
 21 // $LastChangedDate:2007-09-14 15:18:09 +0200 (Fri, 14 Sep 2007) $
 22 // $URL$
 23 //
 24 
 25 /**
 26  * @fileOverview Defines the Members prototype.
 27  */
 28 
 29 markgettext("Members");
 30 markgettext("members");
 31 
 32 /**
 33  * @name Members
 34  * @constructor
 35  * @property {Membership[]} _children
 36  * @property {Membership[]} contributors
 37  * @property {Membership[]} managers
 38  * @property {Membership[]} owners
 39  * @property {Membership[]} subscribers
 40  * @extends HopObject
 41  */
 42 
 43 /**
 44  * 
 45  * @param {String} action
 46  * @returns {Boolean}
 47  */
 48 Members.prototype.getPermission = function(action) {
 49    switch (action) {
 50       case "login":
 51       case "logout":
 52       case "register":
 53       case "reset":
 54       case "salt.js":
 55       return true;
 56    }
 57       
 58    if (!this._parent.getPermission("main")) {
 59       return false;
 60    }
 61 
 62    switch (action) {
 63       case "edit":
 64       case "privileges":
 65       case "subscriptions":
 66       case "updated":
 67       return !!session.user;
 68       
 69       case ".":
 70       case "main":
 71       case "add":
 72       case "owners":
 73       case "managers":
 74       case "contributors":
 75       case "subscribers":
 76       return Membership.require(Membership.OWNER) ||
 77             User.require(User.PRIVILEGED);
 78    }
 79    return false;
 80 }
 81 
 82 Members.prototype.main_action = function() {
 83    res.data.title = gettext("Site Members");
 84    res.data.list = renderList(this, "$Membership#member", 
 85          10, req.queryParams.page);
 86    res.data.pager = renderPager(this, this.href(req.action), 
 87          10, req.queryParams.page);
 88    res.data.body = this.renderSkinAsString("$Members#main");
 89    res.handlers.site.renderSkin("Site#page");
 90    return;
 91 }
 92 
 93 Members.prototype.register_action = function() {
 94    if (req.postParams.register) {
 95       if (req.postParams.activation) {
 96          app.log("Detected form submit with completed honeypot field: " + req.data);
 97          res.redirect(root.href());
 98       }
 99       try {
100          var title = res.handlers.site.title;
101          var user = User.register(req.postParams);
102          var membership = new Membership(user, Membership.SUBSCRIBER);
103          this.add(membership);
104          membership.notify(req.action, user.email, 
105                gettext('[{0}] Welcome to {1}!', root.title, title));
106          res.message = gettext('Welcome to “{0}”, {1}. Have fun!',
107                title, user.name);
108          res.redirect(User.getLocation() || this._parent.href());
109       } catch (ex) {
110          res.message = ex;
111       }
112    }
113 
114    session.data.token = User.getSalt();
115    res.data.action = this.href(req.action);
116    res.data.title = gettext("Register");
117    res.data.body = this.renderSkinAsString("$Members#register");
118    this._parent.renderSkin("Site#page");
119    return;
120 }
121 
122 Members.prototype.reset_action = function() {
123    if (req.postParams.reset) {
124       try {
125          if (!req.postParams.name || !req.postParams.email) {
126             throw Error(gettext("Please enter a user name and e-mail address."));
127          }
128          var user = User.getByName(req.postParams.name);
129          if (!user || user.email !== req.postParams.email) {
130             throw Error(gettext("User name and e-mail address do not match."))
131          }
132          var token = User.getSalt();
133          user.metadata.set("resetToken", token);
134          sendMail(user.email, gettext("[{0}] Password reset confirmation", 
135                root.title), user.renderSkinAsString("$User#notify_reset", {
136                   href: this.href("reset"),
137                   token: token
138                }));
139          res.message = gettext("A confirmation mail was sent to your e-mail address.");
140          res.redirect(this._parent.href());
141       } catch(ex) {
142          res.message = ex;
143       }
144    } else if (req.data.user && req.data.token) {
145       var user = User.getById(req.data.user);
146       if (user) {
147          var token = user.metadata.get("resetToken");
148          if (token) {
149             session.login(user);
150             if (req.postParams.save) {
151                var password = req.postParams.password;
152                if (!password) {
153                   res.message = gettext("Please enter a new password.");
154                } else if (password !== req.postParams.passwordConfirm) {
155                   res.message = gettext("The passwords do not match.");
156                } else {
157                   user.hash = (password + user.salt).md5();
158                   user.metadata.remove("resetToken");
159                   res.message = gettext("Your password was changed.");
160                   res.redirect(this._parent.href());
161                }
162             }
163             res.data.title = gettext("Reset Password");
164             res.data.body = this.renderSkinAsString("$Members#password");
165             this._parent.renderSkin("Site#page");
166             return;
167          }
168       }
169       res.message = gettext("This URL is not valid for resetting your password.");
170       res.redirect(this.href(req.action));
171    }
172    res.data.action = this.href(req.action);
173    res.data.title = gettext("Request Password Reset");
174    res.data.body = this.renderSkinAsString("$Members#reset");
175    this._parent.renderSkin("Site#page");
176    return;
177 }
178 
179 Members.prototype.login_action = function() {
180    if (req.postParams.login) {
181       if (req.postParams.activation) {
182          app.log("Detected form submit with completed honeypot field: " + req.data);
183          res.redirect(root.href());
184       }
185       try {
186          var user = User.login(req.postParams);
187          res.message = gettext('Welcome to {0}, {1}. Have fun!',
188                res.handlers.site.getTitle(), user.name);
189          res.redirect(User.getLocation() || this._parent.href());
190       } catch (ex) {
191          res.message = ex;
192       }
193    }
194    session.data.token = User.getSalt();
195    res.data.action = this.href(req.action);
196    res.data.title = gettext("Login");
197    res.data.body = this.renderSkinAsString("$Members#login");
198    this._parent.renderSkin("Site#page");
199    return;
200 }
201 
202 Members.prototype.logout_action = function() {
203    if (session.user) {
204       res.message = gettext("Good bye, {0}! Looking forward to seeing you again!", 
205             session.user.name);
206       User.logout();
207    }
208    res.redirect(this._parent.href());
209    return;
210 }
211 
212 Members.prototype.edit_action = function() {
213    if (req.postParams.save) {
214       try {
215          session.user.update(req.postParams);
216          res.message = gettext("The changes were saved successfully.");
217          res.redirect(this._parent.href());
218       } catch (err) {
219          res.message = err.toString();
220       }
221    }
222 
223    session.data.token = User.getSalt();
224    session.data.salt = session.user.salt; // FIXME
225    res.data.title = gettext("User Profile");
226    res.data.body = session.user.renderSkinAsString("$User#edit");
227    this._parent.renderSkin("Site#page");
228    return;
229 }
230 
231 Members.prototype.salt_js_action = function() {
232    res.contentType = "text/javascript";
233    var user;
234    if (user = User.getByName(req.queryParams.user)) {
235       res.write((user.salt || String.EMPTY).toSource());
236    }
237    return;
238 }
239 
240 Members.prototype.owners_action = function() {
241    res.data.title = gettext("Site Owners");
242    res.data.list = renderList(this.owners, 
243          "$Membership#member", 10, req.queryParams.page);
244    res.data.pager = renderPager(this.owners, 
245          this.href(req.action), 10, req.queryParams.page);
246    res.data.body = this.renderSkinAsString("$Members#main");
247    res.handlers.site.renderSkin("Site#page");
248    return;
249 }
250 
251 Members.prototype.managers_action = function() {
252    res.data.title = gettext("Site Managers");
253    res.data.list = renderList(this.managers, 
254          "$Membership#member", 10, req.queryParams.page); 
255    res.data.pager = renderPager(this.managers, 
256          this.href(req.action), 10, req.queryParams.page);
257    res.data.body = this.renderSkinAsString("$Members#main");
258    res.handlers.site.renderSkin("Site#page");
259    return;
260 }
261 
262 Members.prototype.contributors_action = function() {
263    res.data.title = gettext("Site Contributors");
264    res.data.list = renderList(this.contributors, 
265          "$Membership#member", 10, req.queryParams.page);
266    res.data.pager = renderPager(this.contributors, 
267          this.href(req.action), 10, req.data.page);
268    res.data.body = this.renderSkinAsString("$Members#main");
269    res.handlers.site.renderSkin("Site#page");
270    return;
271 }
272 
273 Members.prototype.subscribers_action = function() {
274    res.data.title = gettext("Site Subscribers");
275    res.data.list = renderList(this.subscribers, 
276          "$Membership#member", 10, req.queryParams.page);
277    res.data.pager = renderPager(this.subscribers, 
278          this.href(req.action), 10, req.queryParams.page);
279    res.data.body = this.renderSkinAsString("$Members#main");
280    res.handlers.site.renderSkin("Site#page");
281    return;
282 }
283 
284 Members.prototype.updated_action = function() {
285    res.data.title = gettext("Updates");
286    res.data.list = session.user.renderSkinAsString("$User#sites");
287    res.data.body = session.user.renderSkinAsString("$User#subscriptions");
288    res.handlers.site.renderSkin("Site#page");
289    return;
290 }
291 
292 Members.prototype.privileges_action = function() {
293    var site = res.handlers.site;
294    res.data.title = gettext("Privileges");
295    res.data.list = renderList(session.user.memberships, function(item) {
296       res.handlers.subscription = item.site;
297       item.renderSkin("$Membership#subscription");
298       return;
299    });
300    res.handlers.site = site;
301    res.data.body = session.user.renderSkinAsString("$User#subscriptions");
302    res.handlers.site.renderSkin("Site#page");
303    return;
304 }
305 
306 Members.prototype.subscriptions_action = function() {
307    var site = res.handlers.site;
308    res.data.title = gettext("Subscriptions");
309    res.data.list = renderList(session.user.subscriptions, function(item) {
310       res.handlers.subscription = item.site;
311       item.renderSkin("$Membership#subscription");
312       return;
313    });
314    res.handlers.site = site;
315    res.data.body = session.user.renderSkinAsString("$User#subscriptions");
316    res.handlers.site.renderSkin("Site#page");
317    return;
318 }
319 
320 Members.prototype.add_action = function() {
321    if (req.postParams.term) {
322       try {
323          var result = this.search(req.postParams.term);
324          if (result.length < 1) {
325             res.message = gettext("No user found to add as member.");
326          } else {
327             if (result.length >= 100) {
328                res.message = gettext("Too many users found, displaying the first {0} matches only.", 
329                      result.length);
330             } else {
331                res.message = ngettext("One user found.", "{0} users found.", 
332                       result.length);
333             }
334             res.data.result = this.renderSkinAsString("$Members#results", result);
335          }
336       } catch (ex) {
337          res.message = ex;
338          app.log(ex);
339       }
340    } else if (req.postParams.add) {
341       try {
342          res.handlers.sender = User.getMembership();
343          var membership = this.addMembership(req.postParams);
344          membership.notify(req.action, membership.creator.email,  
345                gettext('[{0}] Notification of membership change', root.title));
346          res.message = gettext("Successfully added {0} to the list of members.", 
347                req.postParams.name);
348          res.redirect(membership.href("edit"));
349       } catch (ex) {
350          res.message = ex;
351          app.log(ex);
352       }
353       res.redirect(this.href());
354    }
355    res.data.action = this.href(req.action);
356    res.data.title = gettext('Add Member');
357    res.data.body = this.renderSkinAsString("$Members#add");
358    res.handlers.site.renderSkin("Site#page");
359    return;
360 }
361 
362 /**
363  * 
364  * @param {String} searchString
365  * @returns {Object}
366  */
367 Members.prototype.search = function(searchString) {
368    var self = this;
369    var mode = "=";
370    if (searchString.contains("*")) {
371       searchString = searchString.replace(/\*/g, "%");
372       mode = "like";
373    }
374    var sql = new Sql;
375    sql.retrieve(Sql.MEMBERSEARCH, mode, searchString, 100);
376    var counter = 0, name;
377    res.push();
378    sql.traverse(function() {
379       // Check if the user is not already a member
380       if (!self.get(this.name)) {
381          self.renderSkin("$Members#result", {name: this.name});
382          counter += 1;
383       }
384    });
385    return {
386       result: res.pop(),
387       length: counter
388    };
389 }
390 
391 /**
392  * 
393  * @param {Object} data
394  * @returns {Membership}
395  */
396 Members.prototype.addMembership = function(data) {
397    var user = root.users.get(data.name);
398    if (!user) {
399       throw Error(gettext("Sorry, your input did not match any registered user."));
400    } else if (this.get(data.name)) {
401       throw Error(gettext("This user is already a member of this site."));
402    }
403    var membership = new Membership(user, Membership.SUBSCRIBER);
404    this.add(membership);
405    return membership;
406 }
407 
408 Members.prototype.modSorua_action = function() {
409    if (!app.data.modSorua) app.data.modSorua = new Array();
410    var returnUrl = req.data["sorua-return-url"];
411    var failUrl   = req.data["sorua-fail-url"];
412    var userID    = req.data["sorua-user"];
413    var action    = req.data["sorua-action"];
414    if (action == "authenticate") {    // authenticate-action
415       if (session.user && (userID == null || userID == "" || session.user.name == userID)) {
416          // store returnUrl + timestamp + userID
417          app.data.modSorua[returnUrl] = {time: new Date(), userID: session.user.name}; 
418          res.redirect(returnUrl);
419       } else if (failUrl) {
420          res.redirect(failUrl);
421       } else {
422          session.data.modSorua = {returnUrl: returnUrl,
423                                  userID: userID};
424          res.redirect(this.href("modSoruaLoginForm"));
425       }
426 
427    } else if (action == "verify") {
428       // first remove outdated entries
429       var now = new Date();
430       var arr = new Array();
431       for (var i in app.data.modSorua) {
432          if (app.data.modSorua[i] && app.data.modSorua[i].time &&
433             now.valueOf() - app.data.modSorua[i].time.valueOf() < 1000 * 60)
434             arr[i] = app.data.modSorua[i];
435       }
436       app.data.modSorua = arr;
437       // now check whether returnUrl has been used recently
438       if (app.data.modSorua[returnUrl]) {
439          res.status = 200;
440          res.write("user:" + app.data.modSorua[returnUrl].userID);
441          return;
442       } else {
443          res.status = 403;
444          return;
445       }
446 
447    } else { // handle wrong call of AuthURI
448      res.redirect(root.href("main"));
449 
450    }   
451 }
452 
453 Members.prototype.modSoruaLoginForm_action = function() {
454    if (!session.data.modSorua || !session.data.modSorua.returnUrl) 
455       res.redirect(root.href()); // should not happen anyways
456    if (req.data.login) {
457       try {
458          res.message = this.evalLogin(req.data.name, req.data.password);
459          var returnUrl = session.data.modSorua.returnUrl;
460          app.data.modSorua[returnUrl] = {time: new Date(), userID: req.data.name};
461          res.redirect(returnUrl);
462       } catch (err) {
463          res.message = err.toString();
464       }      
465    }
466    res.data.action = this.href("modSoruaLoginForm");
467    this.renderSkin("modSorua");
468 }
469