Changeset 2479 in products
- Timestamp:
- Jun 4, 2010 12:29:17 PM (14 years ago)
- Location:
- quintagroup.plonecaptchas/branches/plone4/quintagroup/plonecaptchas/browser
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
quintagroup.plonecaptchas/branches/plone4/quintagroup/plonecaptchas/browser/configure.zcml
r2465 r2479 9 9 name="register" 10 10 for="plone.app.layout.navigation.interfaces.INavigationRoot" 11 class=".register. RegistrationForm"11 class=".register.CaptchaRegistrationForm" 12 12 layer="..interfaces.IQGPloneCaptchas" 13 13 permission="cmf.AddPortalMember" … … 17 17 name="new-user" 18 18 for="plone.app.layout.navigation.interfaces.INavigationRoot" 19 class=".register. AddUserForm"19 class=".register.CaptchaAddUserForm" 20 20 layer="..interfaces.IQGPloneCaptchas" 21 21 permission="zope2.ManageUsers" -
quintagroup.plonecaptchas/branches/plone4/quintagroup/plonecaptchas/browser/register.py
r2465 r2479 1 from zope.interface import Interface2 from zope.component import getUtility1 from plone.app.users.browser.register import RegistrationForm 2 from plone.app.users.browser.register import AddUserForm 3 3 4 from zope import schema 5 from zope.formlib import form 6 from zope.app.form.browser import TextWidget, CheckBoxWidget, ASCIIWidget 7 from zope.app.form.interfaces import WidgetInputError, InputErrors 8 from zope.component import getMultiAdapter 4 class CaptchaRegistrationForm(RegistrationForm): 5 """Registration form with captacha.""" 9 6 10 from AccessControl import getSecurityManager 11 from Products.CMFCore.interfaces import ISiteRoot 12 from Products.CMFCore.utils import getToolByName 13 from Products.CMFPlone import PloneMessageFactory as _ 14 15 from Products.Five.formlib.formbase import PageForm 16 from ZODB.POSException import ConflictError 17 18 from Products.statusmessages.interfaces import IStatusMessage 19 20 from plone.app.users.userdataschema import IUserDataSchemaProvider 21 22 from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile 23 24 from plone.app.controlpanel.widgets import MultiCheckBoxVocabularyWidget 25 26 from zope.schema.vocabulary import SimpleVocabulary 27 from zope.site.hooks import getSite 28 from plone.protect import CheckAuthenticator 29 30 # Define constants from the Join schema that should be added to the 31 # vocab of the join fields setting in usergroupssettings controlpanel. 32 JOIN_CONST = ['username', 'password', 'email', 'mail_me'] 33 34 35 class IRegisterSchema(Interface): 36 37 username = schema.ASCIILine( 38 title=_(u'label_user_name', default=u'User Name'), 39 description=_(u'help_user_name_creation_casesensitive', 40 default=u"Enter a user name, usually something " 41 "like 'jsmith'. " 42 "No spaces or special characters. " 43 "Usernames and passwords are case sensitive, " 44 "make sure the caps lock key is not enabled. " 45 "This is the name used to log in.")) 46 47 password = schema.Password( 48 title=_(u'label_password', default=u'Password'), 49 description=_(u'help_password_creation', 50 default=u'Minimum 5 characters.')) 51 52 password_ctl = schema.Password( 53 title=_(u'label_confirm_password', 54 default=u'Confirm password'), 55 description=_(u'help_confirm_password', 56 default=u"Re-enter the password. " 57 "Make sure the passwords are identical.")) 58 59 mail_me = schema.Bool( 60 title=_(u'label_mail_password', 61 default=u"Send a confirmation mail with a link to set the password"), 62 default=False) 63 64 65 class IAddUserSchema(Interface): 66 67 groups = schema.List( 68 title=_(u'label_add_to_groups', 69 default=u'Add to the following groups:'), 70 description=u'', 71 required=False, 72 value_type=schema.Choice(vocabulary='Group Ids')) 73 74 75 def FullNameWidget(field, request): 76 """Widget for fullname field. 77 """ 78 field.description = _( 79 u'help_full_name_creation', 80 default=u"Enter full name, e.g. John Smith.") 81 widget = TextWidget(field, request) 82 return widget 83 84 85 def EmailWidget(field, request): 86 """Widget for email field. 87 88 Note that the email regular expression that is used for validation 89 only allows ascii, so we also use the ASCIIWidget here. 90 """ 91 field.description = _( 92 u'help_email_creation', 93 default=u"Enter an email address. " 94 "This is necessary in case the password is lost. " 95 "We respect your privacy, and will not give the address " 96 "away to any third parties or expose it anywhere.") 97 widget = ASCIIWidget(field, request) 98 return widget 99 100 101 def EmailAsLoginWidget(field, request): 102 """Widget for email field when emails are used as login names. 103 """ 104 field.description = _( 105 u'help_email_creation_for_login', 106 default=u"Enter an email address. This will be your login name. " 107 "We respect your privacy, and will not give the address away to any " 108 "third parties or expose it anywhere.") 109 widget = ASCIIWidget(field, request) 110 return widget 111 112 113 class NoCheckBoxWidget(CheckBoxWidget): 114 """ A widget used for _not_ displaying the checkbox. 115 """ 116 117 def __call__(self): 118 """Render the widget to HTML.""" 119 return "" 120 121 122 def CantChoosePasswordWidget(field, request): 123 """ Change the mail_me field widget so it doesn't display the checkbox """ 124 125 field.title = u'' 126 field.readonly = True 127 field.description = _( 128 u'label_password_change_mail', 129 default=u"A URL will be generated and e-mailed to you; " 130 "follow the link to reach a page where you can change your " 131 "password and complete the registration process.") 132 widget = NoCheckBoxWidget(field, request) 133 return widget 134 135 136 def CantSendMailWidget(field, request): 137 """ Change the mail_me field widget so it displays a warning. 138 139 This is meant for use in the 'add new user' form for admins to 140 tell them that a confirmation email with a password reset link 141 cannot be sent because the mailhost has not been setup. 142 """ 143 144 field.title = u'' 145 field.readonly = True 146 field.description = _( 147 u'label_cant_mail_password_reset', 148 default=u"Normally we would offer to send the user an email with " 149 "instructions to set a password on completion of this form. But this " 150 "site does not have a valid email setup. You can fix this in the " 151 "Mail settings.") 152 widget = NoCheckBoxWidget(field, request) 153 return widget 154 155 156 def getGroupIds(context): 157 site = getSite() 158 groups_tool = getToolByName(site, 'portal_groups') 159 groups = groups_tool.listGroups() 160 # Get group id, title tuples for each, omitting virtual group 'AuthenticatedUsers' 161 groupData = [] 162 for g in groups: 163 if g.id != 'AuthenticatedUsers': 164 groupData.append(('%s (%s)' % (g.getGroupTitleOrName(), g.id), g.id)) 165 # Sort by title 166 groupData.sort(key=lambda x: x[0].lower()) 167 return SimpleVocabulary.fromItems(groupData) 168 169 170 class BaseRegistrationForm(PageForm): 171 label = u"" 172 description = u"" 173 174 @property 175 def form_fields(self): 176 """ form_fields is dynamic in this form, to be able to handle 177 different join styles. 178 """ 179 portal_props = getToolByName(self.context, 'portal_properties') 180 props = portal_props.site_properties 181 use_email_as_login = props.getProperty('use_email_as_login') 182 registration_fields = list(props.getProperty('user_registration_fields')) 183 184 # Check on required join fields 185 if not 'username' in registration_fields and not use_email_as_login: 186 registration_fields.insert(0, 'username') 187 188 if 'username' in registration_fields and use_email_as_login: 189 registration_fields.remove('username') 190 191 if not 'email' in registration_fields: 192 # Perhaps only when use_email_as_login is true, but also 193 # for some other cases; the email field has always been 194 # required. 195 registration_fields.append('email') 196 197 if not 'password' in registration_fields: 198 if 'username' in registration_fields: 199 base = registration_fields.index('username') 200 else: 201 base = registration_fields.index('email') 202 registration_fields.insert(base + 1, 'password') 203 204 # Add password_ctl after password 205 if not 'password_ctl' in registration_fields: 206 registration_fields.insert( 207 registration_fields.index('password') + 1, 'password_ctl') 208 209 # Add email_me after password_ctl 210 if not 'mail_me' in registration_fields: 211 registration_fields.insert( 212 registration_fields.index('password_ctl') + 1, 'mail_me') 213 214 # We need fields from both schemata here. 215 util = getUtility(IUserDataSchemaProvider) 216 schema = util.getSchema() 217 218 all_fields = form.Fields(schema) + form.Fields(IRegisterSchema) 219 220 all_fields['fullname'].custom_widget = FullNameWidget 221 if use_email_as_login: 222 all_fields['email'].custom_widget = EmailAsLoginWidget 223 else: 224 all_fields['email'].custom_widget = EmailWidget 225 226 # Make sure some fields are really required; a previous call 227 # might have changed the default. 228 for name in ('password', 'password_ctl'): 229 all_fields[name].field.required = True 230 231 # Pass the list of join form fields as a reference to the 232 # Fields constructor, and return. 233 return form.Fields(*[all_fields[id] for id in registration_fields]) 234 235 # Actions validators 236 def validate_registration(self, action, data): 237 """ 238 specific business logic for this join form 239 note: all this logic was taken directly from the old 240 validate_registration.py script in 241 Products/CMFPlone/skins/plone_login/join_form_validate.vpy 242 """ 243 244 # CSRF protection 245 CheckAuthenticator(self.request) 246 247 registration = getToolByName(self.context, 'portal_registration') 248 249 errors = super(BaseRegistrationForm, self).validate(action, data) 250 # ConversionErrors have no field_name attribute... :-( 251 error_keys = [error.field_name for error in errors 252 if hasattr(error, 'field_name')] 253 254 form_field_names = [f.field.getName() for f in self.form_fields] 255 256 portal = getUtility(ISiteRoot) 257 portal_props = getToolByName(self.context, 'portal_properties') 258 props = portal_props.site_properties 259 use_email_as_login = props.getProperty('use_email_as_login') 260 261 # passwords should match 262 if 'password' in form_field_names: 263 assert('password_ctl' in form_field_names) 264 # Skip this check if password fields already have an error 265 if not ('password' in error_keys or \ 266 'password_ctl' in error_keys): 267 password = self.widgets['password'].getInputValue() 268 password_ctl = self.widgets['password_ctl'].getInputValue() 269 if password != password_ctl: 270 err_str = _(u'Passwords do not match.') 271 errors.append(WidgetInputError('password', 272 u'label_password', err_str)) 273 errors.append(WidgetInputError('password_ctl', 274 u'label_password', err_str)) 275 self.widgets['password'].error = err_str 276 self.widgets['password_ctl'].error = err_str 277 278 # Password field should have a minimum length of 5 279 if 'password' in form_field_names: 280 # Skip this check if password fields already have an error 281 if not 'password' in error_keys: 282 password = self.widgets['password'].getInputValue() 283 if password and len(password) < 5: 284 err_str = _(u'Passwords must contain at least 5 letters.') 285 errors.append(WidgetInputError( 286 'password', u'label_password', err_str)) 287 self.widgets['password'].error = err_str 288 289 username = '' 290 email = '' 291 try: 292 email = self.widgets['email'].getInputValue() 293 except InputErrors, exc: 294 # WrongType? 295 errors.append(exc) 296 if use_email_as_login: 297 username_field = 'email' 298 username = email 299 else: 300 username_field = 'username' 301 try: 302 username = self.widgets['username'].getInputValue() 303 except InputErrors, exc: 304 errors.append(exc) 305 306 # check if username is valid 307 # Skip this check if username was already in error list 308 if not username_field in error_keys: 309 portal = getUtility(ISiteRoot) 310 if username == portal.getId(): 311 err_str = _(u"This username is reserved. Please choose a " 312 "different name.") 313 errors.append(WidgetInputError( 314 username_field, u'label_username', err_str)) 315 self.widgets[username_field].error = err_str 316 317 318 # check if username is allowed 319 if not username_field in error_keys: 320 if not registration.isMemberIdAllowed(username): 321 err_str = _(u"The login name you selected is already in use " 322 "or is not valid. Please choose another.") 323 errors.append(WidgetInputError( 324 username_field, u'label_username', err_str)) 325 self.widgets[username_field].error = err_str 326 327 328 # Skip this check if email was already in error list 329 if not 'email' in error_keys: 330 if 'email' in form_field_names: 331 if not registration.isValidEmail(email): 332 err_str = _(u'You must enter a valid email address.') 333 errors.append(WidgetInputError( 334 'email', u'label_email', err_str)) 335 self.widgets['email'].error = err_str 336 337 if 'password' in form_field_names and not 'password' in error_keys: 338 # Admin can either set a password or mail the user (or both). 339 if not (self.widgets['password'].getInputValue() or 340 self.widgets['mail_me'].getInputValue()): 341 err_str = _('msg_no_password_no_mail_me', 342 default=u"You must set a password or choose to " 343 "send an email.") 344 errors.append(WidgetInputError( 345 'password', u'label_password', err_str)) 346 self.widgets['password'].error = err_str 347 errors.append(WidgetInputError( 348 'mail_me', u'label_mail_me', err_str)) 349 self.widgets['mail_me'].error = err_str 350 return errors 351 352 @form.action(_(u'label_register', default=u'Register'), 353 validator='validate_registration', name=u'register') 354 def action_join(self, action, data): 355 result = self.handle_join_success(data) 356 # XXX Return somewhere else, depending on what 357 # handle_join_success returns? 358 return self.context.unrestrictedTraverse('registered')() 359 360 def handle_join_success(self, data): 361 portal = getUtility(ISiteRoot) 362 registration = getToolByName(self.context, 'portal_registration') 363 portal_props = getToolByName(self.context, 'portal_properties') 364 props = portal_props.site_properties 365 use_email_as_login = props.getProperty('use_email_as_login') 366 367 if use_email_as_login: 368 # The username field is not shown as the email is going to 369 # be the username, but the field *is* needed further down 370 # the line. 371 data['username'] = data['email'] 372 # Set username in the form; at least needed for logging in 373 # immediately when password reset is bypassed. 374 self.request.form['form.username'] = data['email'] 375 376 username = data['username'] 377 password = data.get('password') or registration.generatePassword() 378 379 try: 380 registration.addMember(username, password, properties=data, 381 REQUEST=self.request) 382 except (AttributeError, ValueError), err: 383 IStatusMessage(self.request).addStatusMessage(err, type="error") 384 return 385 386 if portal.validate_email or data.get('mail_me'): 387 # We want to validate the email address (users cannot 388 # select their own passwords on the register form) or the 389 # admin has explicitly requested to send an email on the 390 # 'add new user' form. 391 try: 392 # When all goes well, this call actually returns the 393 # rendered mail_password_response template. As a side 394 # effect, this removes any status messages added to 395 # the request so far, as they are already shown in 396 # this template. 397 response = registration.registeredNotify(username) 398 return response 399 except ConflictError: 400 # Let Zope handle this exception. 401 raise 402 except Exception: 403 ctrlOverview = getMultiAdapter((self.context, self.request), 404 name='overview-controlpanel') 405 mail_settings_correct = not ctrlOverview.mailhost_warning() 406 if mail_settings_correct: 407 # The email settings are correct, so the most 408 # likely cause of an error is a wrong email 409 # address. We remove the account: 410 # Remove the account: 411 self.context.acl_users.userFolderDelUsers( 412 [username], REQUEST=self.request) 413 IStatusMessage(self.request).addStatusMessage( 414 _(u'status_fatal_password_mail', 415 default=u"Failed to create your account: we were " 416 "unable to send instructions for setting a password " 417 "to your email address: ${address}", 418 mapping={u'address': data.get('email', '')}), 419 type='error') 420 return 421 else: 422 # This should only happen when an admin registers 423 # a user. The admin should have seen a warning 424 # already, but we warn again for clarity. 425 IStatusMessage(self.request).addStatusMessage( 426 _(u'status_nonfatal_password_mail', 427 default=u"This account has been created, but we " 428 "were unable to send instructions for setting a " 429 "password to this email address: ${address}", 430 mapping={u'address': data.get('email', '')}), 431 type='warning') 432 return 433 434 return 435 436 437 class RegistrationForm(BaseRegistrationForm): 438 """ Dynamically get fields from user data, through admin 439 config settings. 440 """ 441 label = _(u'heading_registration_form', default=u'Registration form') 442 description = u"" 443 template = ViewPageTemplateFile('register_form.pt') 444 445 @property 446 def showForm(self): 447 """The form should not be displayed to the user if the system is 448 incapable of sending emails and email validation is switched on 449 (users are not allowed to select their own passwords). 450 """ 451 ctrlOverview = getMultiAdapter((self.context, self.request), name='overview-controlpanel') 452 portal = getUtility(ISiteRoot) 453 454 # hide form iff mailhost_warning == True and validate_email == True 455 return not (ctrlOverview.mailhost_warning() and portal.getProperty('validate_email', True)) 456 457 @property 458 def form_fields(self): 459 if not self.showForm: 460 # We do not want to spend time calculating fields that 461 # will never get displayed. 462 return [] 463 portal = getUtility(ISiteRoot) 464 defaultFields = super(RegistrationForm, self).form_fields 465 # Can the user actually set his/her own password? 466 if portal.getProperty('validate_email', True): 467 # No? Remove the password fields. 468 defaultFields = defaultFields.omit('password', 'password_ctl') 469 # Show a message indicating that a password reset link 470 # will be mailed to the user. 471 defaultFields['mail_me'].custom_widget = CantChoosePasswordWidget 472 else: 473 # The portal is not interested in validating emails, and 474 # the user is not interested in getting an email with a 475 # link to set his password if he can set this password in 476 # the current form already. 477 defaultFields = defaultFields.omit('mail_me') 478 479 return defaultFields 480 481 482 class AddUserForm(BaseRegistrationForm): 483 484 label = _(u'heading_add_user_form', default=u'Add New User') 485 description = u"" 486 template = ViewPageTemplateFile('newuser_form.pt') 487 488 @property 489 def form_fields(self): 490 defaultFields = super(AddUserForm, self).form_fields 491 492 # The mail_me field needs special handling depending on the 493 # validate_email property and on the correctness of the mail 494 # settings. 495 ctrlOverview = getMultiAdapter((self.context, self.request), 496 name='overview-controlpanel') 497 mail_settings_correct = not ctrlOverview.mailhost_warning() 498 if not mail_settings_correct: 499 defaultFields['mail_me'].custom_widget = CantSendMailWidget 500 else: 501 # Make the password fields optional: either specify a 502 # password or mail the user (or both). The validation 503 # will check that at least one of the options is chosen. 504 defaultFields['password'].field.required = False 505 defaultFields['password_ctl'].field.required = False 506 portal = getUtility(ISiteRoot) 507 if portal.getProperty('validate_email', True): 508 defaultFields['mail_me'].field.default = True 509 else: 510 defaultFields['mail_me'].field.default = False 511 512 # Append the manager-focused fields 513 allFields = defaultFields + form.Fields(IAddUserSchema) 514 515 allFields['groups'].custom_widget = MultiCheckBoxVocabularyWidget 516 517 return allFields 518 519 @form.action(_(u'label_register', default=u'Register'), 520 validator='validate_registration', name=u'register') 521 def action_join(self, action, data): 522 errors = super(AddUserForm, self).handle_join_success(data) 523 portal_groups = getToolByName(self.context, 'portal_groups') 524 525 securityManager = getSecurityManager() 526 canManageUsers = securityManager.checkPermission('Manage users', 527 self.context) 528 username = data['username'] 529 530 try: 531 # Add user to the selected group(s) 532 if 'groups' in data.keys() and canManageUsers: 533 for groupname in data['groups']: 534 group = portal_groups.getGroupById(groupname) 535 group.addMember(username, REQUEST=self.request) 536 except (AttributeError, ValueError), err: 537 IStatusMessage(self.request).addStatusMessage(err, type="error") 538 return 539 540 IStatusMessage(self.request).addStatusMessage( 541 _(u"User added."), type='info') 542 self.request.response.redirect( 543 self.context.absolute_url() + 544 '/@@usergroup-userprefs?searchstring=' + username) 7 class CaptchaAddUserForm(RegistrationForm): 8 """Add user form with captacha."""
Note: See TracChangeset
for help on using the changeset viewer.