Changeset 6756 for main/waeup.sirp/trunk/src/waeup/sirp/students
- Timestamp:
- 14 Sep 2011, 01:26:25 (13 years ago)
- Location:
- main/waeup.sirp/trunk
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.sirp/trunk
-
Property
svn:mergeinfo
set to
/main/waeup.sirp/branches/uli-studentpw merged eligible
-
Property
svn:mergeinfo
set to
-
main/waeup.sirp/trunk/src/waeup/sirp/students/authentication.py
r6680 r6756 26 26 from zope.component import getUtility 27 27 from zope.password.interfaces import IPasswordManager 28 from zope.pluggableauth.interfaces import IAuthenticatorPlugin 28 from zope.pluggableauth.interfaces import ( 29 IAuthenticatorPlugin, ICredentialsPlugin) 30 from zope.pluggableauth.plugins.session import ( 31 SessionCredentialsPlugin, SessionCredentials) 32 from zope.publisher.interfaces.http import IHTTPRequest 33 from zope.session.interfaces import ISession 29 34 from waeup.sirp.authentication import PrincipalInfo, get_principal_role_manager 30 35 from waeup.sirp.interfaces import IAuthPluginUtility, IUserAccount … … 157 162 return IUserAccount(student) 158 163 164 class PasswordChangeCredentialsPlugin(grok.GlobalUtility, 165 SessionCredentialsPlugin): 166 """A session credentials plugin that handles the case of a user 167 changing his/her own password. 168 169 When users change their own password they might find themselves 170 logged out on next request. 171 172 To avoid this, we support to use a 'change password' page a bit 173 like a regular login page. That means, on each request we lookup 174 the sent data for a login field called 'student_id' and a 175 password. 176 177 If both exist, this means someone sent new credentials. 178 179 We then look for the old credentials stored in the user session. 180 If the new credentials' login (the student_id) matches the old 181 one's, we set the new credentials in session _but_ we return the 182 old credentials for the authentication plugins to check as for the 183 current request (and for the last time) the old credentials apply. 184 185 No valid credentials are returned by this plugin if one of the 186 follwing circumstances is true 187 188 - the sent request is not a regular IHTTPRequest 189 190 - the credentials to set do not match the old ones 191 192 - no credentials are sent with the request 193 194 - no credentials were set before (i.e. the user has no session 195 with credentials set before) 196 197 Therefore it is mandatory to put this plugin in the line of all 198 credentials plugins _before_ other plugins, so that the regular 199 credentials plugins can drop in as a 'fallback'. 200 201 This plugin was designed for students to change their passwords, 202 but might be used to allow password resets for other types of 203 accounts as well. 204 """ 205 grok.provides(ICredentialsPlugin) 206 grok.name('student_pw_change') 207 208 loginpagename = 'login' 209 loginfield = 'student_id' 210 passwordfield = 'form.password' 211 212 def extractCredentials(self, request): 213 if not IHTTPRequest.providedBy(request): 214 return None 215 login = request.get(self.loginfield, None) 216 password = request.get(self.passwordfield, None) 217 if not login or not password: 218 return None 219 session = ISession(request) 220 sessionData = session.get( 221 'zope.pluggableauth.browserplugins') 222 old_credentials = sessionData.get('credentials', None) 223 if old_credentials is None: 224 # Password changes for already authenticated users only! 225 return None 226 if old_credentials.getLogin() != login: 227 # Special treatment only for users that change their own pw. 228 return None 229 old_credentials = { 230 'login': old_credentials.getLogin(), 231 'password': old_credentials.getPassword()} 232 233 # Set new credentials in session. These will be active on next request 234 new_credentials = SessionCredentials(login, password) 235 sessionData['credentials'] = new_credentials 236 237 # Return old credentials for this one request only 238 return old_credentials 239 159 240 class StudentsAuthenticatorSetup(grok.GlobalUtility): 160 241 """Register or unregister student authentication for a PAU. … … 165 246 166 247 def register(self, pau): 248 plugins = list(pau.credentialsPlugins) 249 # this plugin must come before the regular credentials plugins 250 plugins.insert(0, 'student_pw_change') 251 pau.credentialsPlugins = tuple(plugins) 167 252 plugins = list(pau.authenticatorPlugins) 168 253 plugins.append('students') -
main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py
r6750 r6756 63 63 WAeUPObjectWidget, WAeUPObjectDisplayWidget) 64 64 from waeup.sirp.students.interfaces import ( 65 IStudentsContainer, IStudent, IStudentClearance, 65 IStudentsContainer, IStudent, IStudentClearance, IStudentPasswordSetting, 66 66 IStudentPersonal, IStudentBase, IStudentStudyCourse, 67 67 IStudentPayments, IStudentAccommodation, IStudentNavigation, … … 160 160 self.acseries = self.request.form.get('form.acseries', None) 161 161 self.acnumber = self.request.form.get('form.acnumber', None) 162 162 163 163 if SUBMIT is None: 164 164 return … … 294 294 grok.require('waeup.viewStudent') 295 295 grok.template('basepage') 296 form_fields = grok.AutoFields(IStudentBase) #.omit('password')296 form_fields = grok.AutoFields(IStudentBase).omit('password') 297 297 pnav = 4 298 298 title = 'Base Data' … … 541 541 target = 'bedit' 542 542 543 class StudentPasswordSetting(grok.Adapter): 544 """Adapt IStudent to data needed for password settings. 545 546 We provide password getters/setters for the attached context (an 547 IStudent object) that cooperate seamless with the usual 548 formlib/form techniques. 549 """ 550 grok.context(IStudent) 551 grok.provides(IStudentPasswordSetting) 552 553 def __init__(self, context): 554 self.name = context.name 555 self.password_repeat = context.password 556 self.context = context 557 return 558 559 def getPassword(self): 560 return self.context.password 561 562 def setPassword(self, password): 563 IUserAccount(self.context).setPassword(password) 564 return 565 566 password = property(getPassword, setPassword) 567 543 568 class StudentBaseEditFormPage(WAeUPEditFormPage): 544 569 """ View to edit student base data by student … … 547 572 grok.name('bedit') 548 573 grok.require('waeup.handleStudent') 549 form_fields = grok.AutoFields(IStudentBaseEdit).omit( 550 'student_id', 'reg_number', 'matric_number') 574 #form_fields = grok.AutoFields(IStudentBaseEdit).omit( 575 # 'student_id', 'reg_number', 'matric_number') 576 form_fields = grok.AutoFields(IStudentPasswordSetting) 551 577 grok.template('baseeditpage') 552 578 label = 'Change password' … … 555 581 556 582 def update(self): 557 datepicker.need() # Enable jQuery datepicker in date fields.558 583 super(StudentBaseEditFormPage, self).update() 559 584 self.wf_info = IWorkflowInfo(self.context) 560 585 return 561 586 562 @grok.action('Save') 587 def onFailure(self, action, data, errors): 588 new_status = [] 589 other_errors = False 590 for error in errors: 591 msg = getattr(error, 'message', '') 592 if isinstance(msg, basestring) and msg != '': 593 new_status.append(msg) 594 else: 595 other_errors = True 596 if other_errors: 597 if new_status: 598 new_status.append('see below for further errors') 599 else: 600 new_status.append('See below for details.') 601 if new_status: 602 self.status = u'There were errors: %s' % ', '.join(new_status) 603 return 604 605 @grok.action('Save', failure=onFailure) 563 606 def save(self, **data): 564 607 form = self.request.form 565 608 ob_class = self.__implemented__.__name__.replace('waeup.sirp.','') 566 if form.has_key('password') and form['password']: 567 if form['password'] != form['control_password']: 568 self.flash('Passwords do not match.') 569 return 570 IUserAccount(self.context).setPassword(form['password']) 609 changed_fields = self.applyData(self.context, **data) 610 # Turn list of lists into single list 611 changed_fields = reduce(lambda x,y: x+y, changed_fields.values()) 612 changed_fields = [x for x in changed_fields 613 if not x.startswith('password')] 614 if form.get('form.password', u'') != u'': 571 615 self.context.loggerInfo(ob_class, 'password changed') 572 changed_fields = self.applyData(self.context, **data) 573 changed_fields = changed_fields.values() 574 fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields) 575 self.context._p_changed = True 576 self.flash('Form has been saved.') 616 self.flash('Form has been saved.') 617 fields_string = ' + '.join(changed_fields) 577 618 if fields_string: 578 619 self.context.loggerInfo(ob_class, 'saved: % s' % fields_string) … … 614 655 grok.name('cedit') 615 656 grok.require('waeup.handleStudent') 616 form_fields = grok.AutoFields(IStudentClearanceEdit).omit('clearance_locked') 657 form_fields = grok.AutoFields( 658 IStudentClearanceEdit).omit('clearance_locked') 617 659 #grok.template('clearanceeditpage') 618 660 label = 'Edit clearance data' … … 667 709 pnav = 4 668 710 buttonname = 'Start' 669 670 711 671 712 def update(self, SUBMIT=None): 672 # We must not use form.ac_series and form.ac_number in forms since these 673 # are interpreted as applicant credentials in the applicants package 713 # We must not use form.ac_series and form.ac_number in forms 714 # since these are interpreted as applicant credentials in the 715 # applicants package 674 716 self.acseries = self.request.form.get('form.acseries', None) 675 717 self.acnumber = self.request.form.get('form.acnumber', None) -
main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/baseeditpage.pt
r6701 r6756 9 9 tal:define="status view/status" 10 10 tal:condition="status"> 11 Form Status:12 11 <span i18n:translate="" tal:content="view/status"> 13 12 Form status summary 14 13 </span> 15 14 </div> 15 16 <input type="hidden" name="student_id" value="" 17 tal:attributes="value context/student_id" /> 16 18 17 19 <table class="form-fields zebra"> … … 38 40 </tr> 39 41 </tal:block> 40 <tr>41 <td class="label"><label>Password:</label></td>42 <td>43 <input name="password" type="password"/>44 </td>45 </tr>46 <tr>47 <td class="label"><label>Retype password:</label></td>48 <td>49 <input name="control_password" type="password" />50 </td>51 </tr>52 42 </tbody> 53 43 </table> -
main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py
r6750 r6756 1 1 ## 2 2 ## interfaces.py 3 from zope.interface import Interface, Attribute 3 from zope.interface import Interface, Attribute, invariant 4 from zope.interface.exceptions import Invalid 4 5 from zope import schema 5 6 from waeup.sirp.interfaces import IWAeUPObject … … 58 59 """ 59 60 60 class IStudentBase(IWAeUPObject): 61 """Representation of student base data. 62 """ 63 history = Attribute('Object history, a list of messages.') 64 state = Attribute('Returns the registration state of a student') 65 #student_id = Attribute('Randomly generated id') 66 password = Attribute('Encrypted password of a student') 67 adm_code = Attribute('Admission checking access code') 68 69 def loggerInfo(ob_class, comment): 70 """Adds an INFO message to the log file 71 """ 72 73 student_id = schema.TextLine( 74 title = u'Student ID', 75 required = True, 76 ) 77 61 class IStudentPasswordSetting(IWAeUPObject): 62 """Data needed for password setting. 63 """ 78 64 name = schema.TextLine( 79 65 title = u'Full Name', … … 82 68 ) 83 69 70 password = schema.Password( 71 title = u'New password', 72 required = False, 73 ) 74 75 password_repeat = schema.Password( 76 title = u'Retype new password', 77 required = False, 78 ) 79 80 @invariant 81 def passwords_match(obj): 82 if obj.password == obj.password_repeat: 83 return 84 raise Invalid('passwords do not match') 85 86 class IStudentBase(IWAeUPObject): 87 """Representation of student base data. 88 """ 89 history = Attribute('Object history, a list of messages.') 90 state = Attribute('Returns the registration state of a student') 91 password = Attribute('Encrypted password of a student') 92 adm_code = Attribute('Admission checking access code') 93 94 password = schema.Password( 95 title = u'Password', 96 required = False, 97 ) 98 99 def loggerInfo(ob_class, comment): 100 """Adds an INFO message to the log file 101 """ 102 103 student_id = schema.TextLine( 104 title = u'Student ID', 105 required = True, 106 ) 107 108 name = schema.TextLine( 109 title = u'Full Name', 110 default = u'Nobody', 111 required = True, 112 ) 113 84 114 reg_number = schema.TextLine( 85 115 title = u'Registration Number', -
main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_authentication.py
r6680 r6756 27 27 from zope.password.interfaces import IPasswordManager 28 28 from zope.pluggableauth import PluggableAuthentication 29 from zope.security.interfaces import Unauthorized 29 30 from zope.securitypolicy.role import Role 30 31 from zope.securitypolicy.interfaces import IRole, Allow … … 33 34 from waeup.sirp.students.authentication import ( 34 35 StudentsAuthenticatorSetup, StudentAccount) 35 36 from waeup.sirp.students.tests.test_browser import StudentsFullSetup 37 from waeup.sirp.testing import FunctionalLayer 36 38 37 39 class StudentsAuthenticatorSetupTests(unittest.TestCase): … … 152 154 self.assertEqual(self.account.roles, ['waeup.test.Role']) 153 155 return 156 157 158 159 class FunctionalStudentAuthTests(StudentsFullSetup): 160 161 layer = FunctionalLayer 162 163 def setUp(self): 164 super(FunctionalStudentAuthTests, self).setUp() 165 return 166 167 def tearDown(self): 168 super(FunctionalStudentAuthTests, self).tearDown() 169 return 170 171 def test_reset_protected_anonymous(self): 172 # anonymous users cannot reset others passwords 173 self.assertRaises( 174 Unauthorized, 175 self.browser.open, self.student_path + '/bedit') 176 return -
main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py
r6753 r6756 279 279 # Change password 280 280 self.browser.getLink("Change password").click() 281 self.browser.getControl(name="password").value = 'new_password' 282 self.browser.getControl(name="control_password").value = 'new_password' 281 self.browser.getControl(name="form.password").value = 'new_password' 282 self.browser.getControl( 283 name="form.password_repeat").value = 'new_password' 283 284 self.browser.getControl("Save").click() 284 285 self.assertTrue('Form has been saved' in self.browser.contents) … … 294 295 self.browser.open(self.login_path) 295 296 self.browser.getControl(name="form.login").value = self.test_student_id 296 self.browser.getControl(name=" password").value = 'new_password'297 self.browser.getControl(name="form.password").value = 'new_password' 297 298 self.browser.getControl("Login").click() 298 299 self.assertEqual(self.browser.url, self.student_path)
Note: See TracChangeset for help on using the changeset viewer.