Source code for collective.smsauthenticator.browser.forms.reset_mobile_number

"""
Reset bar-code. This is the place where the new number is actually set (upon confirmation). First of
all upon landing on the page (GET request) the validity of the URL is checked, since it's signed
and has an expiration limit of 10 minutes. The phone number to be added to users' profile is
taken from the GET request as well, but no worries - it's signed too, thus, in case of data tumpering,
the entire signature would become invalid. Note, that signature used for resetting the mobile number is
written to the users' profile on the moment request is sent. No unauthorised reproduction is possible,
since we match it to the one in users' profile.

If all things mentioned above are in good order, user would see a form where he can fill in the
verification code received by SMS. That code had been stored in users' profile too and is simply
matched the one filled in.

If all checks pass well, the mobile number is definitely set in users' profile.
"""
import logging

from zope.i18nmessageid import MessageFactory

from z3c.form import button, field

from plone.directives import form
from plone import api
from plone.z3cform.layout import wrap_form

from Products.statusmessages.interfaces import IStatusMessage
from zope.schema import TextLine

from collective.smsauthenticator.helpers import validate_mobile_number_reset_code, validate_user_data

logger = logging.getLogger('collective.smsauthenticator')

_ = MessageFactory('collective.smsauthenticator')


class IResetMobileNumberForm(form.Schema):
    """
    Interface for the SMS Authenticator Reset Mobile Number form.
    """
    token = TextLine(
        title=_(u"SMS verification code"),
        description=_(u"Enter the verification code you received on your mobile phone."),
        required=True
    )


[docs]class ResetMobileNumberForm(form.SchemaForm): """ Form for the resetting the mobile number. What happens here is: - Signed user data is validated. If valid, the user is fetched. - Token (`signature` param) is matched to the one obtained from user records. If matched, the mobile number saved in the users' profile. """ fields = field.Fields(IResetMobileNumberForm) ignoreContext = True schema = IResetMobileNumberForm label = _("(Re)set your two-step verification mobile number") description = _(u"You have received (or will shortly receive) an SMS with an verification code.\ <br\>After succesfully submitting this form, you will be automatically logged in.") css_class = "enableAutoFocus"
[docs] def action(self): return "{0}?{1}".format( self.request.getURL(), self.request.get('QUERY_STRING', '') )
@button.buttonAndHandler(_('Verify'))
[docs] def handleSubmit(self, action): data, errors = self.extractData() if errors: return False # Mobile number specified as a new phone number mobile_number = self.request.get('mobile_number', '') # Token entered by user from his mobile device sent to him by SMS token = data.get('token', '') # Signature token generated for resetting the bar code. signature_token = self.request.get('signature', '') valid_until = self.request.get('valid_until', '') username = self.request.get('auth_user', '') user = api.user.get(username=username) if not user: reason = _("User not found {0}.".format(username)) IStatusMessage(self.request).addStatusMessage( _("(Re)setting of the mobile number failed! {0}".format(reason)), 'error' ) return # Validating the SMSAuthenticator app token valid_token = validate_mobile_number_reset_code(token, user=user) reason = None if valid_token: try: # Checking if token generated for resetting the bar code image is equal # to the one taken from current request. mobile_number_reset_token = user.getProperty('mobile_number_reset_token') if mobile_number_reset_token != signature_token: reason = _("Invalid mobile number reset token.") IStatusMessage(self.request).addStatusMessage( _("(Re)setting of the mobile number failed! {0}".format(reason)), 'error' ) return user.setMemberProperties( mapping = { 'enable_two_step_verification': True, 'mobile_number': mobile_number, 'mobile_number_reset_token': '', 'mobile_number_reset_code': '' } ) IStatusMessage(self.request).addStatusMessage( _("You have succesfully changed/set your mobile number.\ You're now logged in."), 'info' ) redirect_url = "{0}".format(self.context.absolute_url()) # Logging user in self.context.acl_users.session._setupSession(username, self.request.response) # Redirect user to home self.request.response.redirect(redirect_url) except Exception as e: reason = _(str(e)) else: reason = _("Invalid token or token expired.") if reason is not None: IStatusMessage(self.request).addStatusMessage(_("Setup failed! {0}".format(reason)), 'error')
[docs] def updateFields(self, *args, **kwargs): """ Here happens the following: - Signed user data is validated. If valid, the user is fetched. - Token (`signature` param) is matched to the one obtained from user records. If matched, the mobile number is reset (and saved in the users' profile). """ username = self.request.get('auth_user', '') token = self.request.get('signature', '') user = api.user.get(username=username) # If valid user if user: # Getting the users' mobile number reset token saved in his profile. mobile_number_reset_token = user.getProperty('mobile_number_reset_token') # Validate the user data user_data_validation_result = validate_user_data(request=self.request, user=user) # If all goes well, save the number in profile (overwrite_secret=True). if not user_data_validation_result.result: IStatusMessage(self.request).addStatusMessage( ' '.join(user_data_validation_result.reason), 'error' ) if mobile_number_reset_token != token: IStatusMessage(self.request).addStatusMessage( _("Invalid mobile number reset token"), 'error' ) return super(ResetMobileNumberForm, self).updateFields(*args, **kwargs) # View for the ``ResetMobileNumberForm``.
ResetMobileNumberFormView = wrap_form(ResetMobileNumberForm)