Source code for collective.smsauthenticator.pas_plugin

"""
The idea of this PAS plugin is quite simple. It should check the user profile for the user being logged in
and if user has enabled two-step verification for his account (``enable_two_step_verification`` is set to
True), then redirect him further to a another page, where he would enter his SMS Authenticator token, after
successful validation of which the user would be definitely logged in.

If user has not enabled the two-step verification for his account (``enable_two_step_verification``
is set to False), then do nothing so that Plone continues logging in the user normal way.
"""
import logging

from Globals import InitializeClass
from AccessControl.SecurityInfo import ClassSecurityInfo

from zope.i18nmessageid import MessageFactory

from plone import api

from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.PluggableAuthService.utils import classImplements
from Products.PluggableAuthService.interfaces.plugins import IAuthenticationPlugin
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.CMFCore.utils import getToolByName
from Products.statusmessages.interfaces import IStatusMessage

from collective.smsauthenticator.adapter import ICameFrom
from collective.smsauthenticator.helpers import (
    sign_user_data, is_whitelisted_client, generate_code, send_login_code_sms
    )

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

_ = MessageFactory('collective.smsauthenticator')

manage_addSMSAuthenticatorPluginForm = PageTemplateFile(
    './www/add_sms_authenticator_form',
    globals(),
    __name__ = 'manage_addSMSAuthenticatorPluginForm'
    )

[docs]def addSMSAuthenticatorPlugin(self, id, title='', REQUEST=None): """ Add a SMS Authenticator PAS Plugin to Plone PAS """ o = SMSAuthenticatorPlugin(id, title) self._setObject(o.getId(), o) if REQUEST is not None: REQUEST['RESPONSE'].redirect( '{0}/manage_main?manage_tabs_message=SMS+Authentiactor+PAS+Plugin+added.'.format(self.absolute_url()) )
[docs]class SMSAuthenticatorPlugin(BasePlugin): """ SMS Authenticator PAS Plugin """ meta_type = 'Collective SMS Authenticator PAS' security = ClassSecurityInfo() def __init__(self, id, title=None): self._setId(id) self.title = title
[docs] def authenticateCredentials(self, credentials): """ Place to actually validate the user credentials specified and return a tuple (login, login) on success or (None, None) on failure. If we find one and two-step verification is not enabled for the account, we consider the authentication passed and log the user in. If two-step verification has been enabled for the account, the first step of authentication is considered to be passed and we go to the next page (having the user and pass remembered), where we check for the token generated by the token generator (SMS Authenticator). If the token is valid too, we log the user in. """ login = credentials['login'] password = credentials['password'] if not login: return None user = api.user.get(username=login) #logger.debug("Found user: {0}".format(user.getProperty('username'))) two_step_verification_enabled = user.getProperty('enable_two_step_verification') #logger.debug("Two-step verification enabled: {0}".format(two_step_verification_enabled)) if two_step_verification_enabled: # First see, if the password is correct. # We fetch the user manager plugin to chekc that. auth_plugins = self._getPAS().plugins.listPlugins( IAuthenticationPlugin ) user_manager = authorized = None for plugid, authplugin in auth_plugins: if 'user' in plugid: user_manager = authplugin break if user_manager: authorized = user_manager.authenticateCredentials(credentials) if authorized is None: return None if is_whitelisted_client(): return None #logger.debug("Hello SMS") # Setting the data in the session doesn't seem to work. That's why we use the `ska` package. # The secret key would be then a combination of username, secret stored in users' profile # and the browser version. request = self.REQUEST response = request['RESPONSE'] response.setCookie('__ac', '', path='/') # Redirect to token thing... signed_url = sign_user_data(request=request, user=user, url='@@sms-authenticator-token') came_from_adapter = ICameFrom(request) # Appending possible `came_from`, but give it another name. came_from = came_from_adapter.getCameFrom() if came_from: signed_url = '{0}&next_url={1}'.format(signed_url, came_from) # **************************************** # Generate the login code and send the SMS uuu = api.user.get(username=login) mobile_number = uuu.getProperty('mobile_number') mobile_number_authentication_code = uuu.getProperty('mobile_number_authentication_code') # If user doesn't have yet a mobile numer specified, redirect him to the reset mobile # number view. if not mobile_number: IStatusMessage(request).addStatusMessage( _("Two-step verification is enabled for your account, but you haven't specified " "a mobile number yet! In order to be able to log in, you have to go through mobile " "number recovery procedure first."), 'warning' ) response.redirect('@@request-mobile-number-reset/?username={0}'.format(login), lock=1) return None # If code was not yet sent, generate a code and send an SMS. if not mobile_number_authentication_code: mobile_number_authentication_code = generate_code(uuu) # Send the SMS sms_sent = send_login_code_sms( mobile_number = mobile_number, code = mobile_number_authentication_code ) if sms_sent: # Save the `signature` value to the `mobile_number_reset_token`. uuu.setMemberProperties( mapping = { 'mobile_number_authentication_code': mobile_number_authentication_code, } ) #else: # IStatusMessage(request).addStatusMessage( # _("An error occured while sending the SMS to the number given."), 'error' # ) # response.redirect('login', lock=1) # return None #logger.debug("mobile number: {0}".format(mobile_number)) # Redirecting user to authentication code validation page. response.redirect(signed_url, lock=1) return None if credentials.get('extractor') != self.getId(): return None return None
classImplements(SMSAuthenticatorPlugin, IAuthenticationPlugin) InitializeClass(SMSAuthenticatorPlugin)