def test_basic(self): """ Test generating and recovering a token. """ svc = TokenService() TEST_STR = "Hello World" token = svc.issue(TEST_STR) res = svc.check(token) self.assertEqual(TEST_STR, res) # Check bad token case self.assertRaises(ValueError, svc.check, "BadToken")
def test_unpack(self): """ Test that the unpacker without verification works correctly. """ TEST_OBJ = {"a": "field1", "b": "ASD"} svc = TokenService() token = svc.issue(TEST_OBJ) retval = TokenService.unpack(token) self.assertDictEqual(TEST_OBJ, retval) # Check the error handling self.assertRaises(ValueError, svc.unpack, "not_a_token")
def setUp(self): # self._mocked_facade = mocked_facade # self._mocked_facade.return_value = MockTransferClientFacade("anything") self._parser = argparse.ArgumentParser() subparsers = self._parser.add_subparsers() UserCommand(subparsers) self._tmp_file = tempfile.NamedTemporaryFile(dir='/tmp') future_date = (datetime.timedelta(0, 600) + datetime.datetime.utcnow()).isoformat() plain = {'id': 44, 'expiry': future_date} svc = TokenService() validtoken = svc.issue(plain) self._tmp_file.write(validtoken) self._tmp_file.flush()
def setUp(self): self.__future_date = (datetime.timedelta(0, 600) + datetime.datetime.utcnow()).isoformat() plain = {'id': 44, 'expiry': self.__future_date, 'email': '*****@*****.**'} svc = TokenService() self._validtoken = svc.issue(plain) __past_date = (-datetime.timedelta(0, 60) + datetime.datetime.utcnow()).isoformat() plain_expired = {'id': 44, 'expiry': __past_date} self._expiredtoken = svc.issue(plain_expired) plain_incomplete = {'id': 44} self._incomplete = svc.issue(plain_incomplete)
def __init__(self, server_name, logger=logging.getLogger(), debug=False, token_key=None): """ Constructs the server. logger - The main logger to use. debug - If set to true, enable flask debug mode (Which includes far more details in returned errors, etc...) """ Flask.__init__(self, server_name) self.debug = debug self.before_request(self.__init_handler) self.after_request(self.__access_log) self.__acl_manager = ACLManager(logger) self.__update_dbctx(None) self.__db_classes = [] self.__db_insts = [] self.__startup_funcs = [] self.__test_funcs = [] self.__logger = logger if not token_key: token_key = os.urandom(16) self.secret_key = token_key + "flask" self.token_svc = TokenService(token_key, "pdmwebsvc") # We override the test client class from Flask with our # custom one which is more similar to RESTClient self.test_client_class = FlaskClientWrapper with self.app_context(): current_app.log = logger current_app.acl_manager = self.__acl_manager current_app.token_svc = self.token_svc
def setUp(self, site_mock): self.__site_mock = site_mock conf = { 'token_validity': '01:00:00', 'smtp_server': 'localhost', 'verification_url': 'https://pdm.grid.hep.ph.ic.ac.uk:5443/web/verify', 'smtp_server_login': '******', 'smtp_starttls': 'OPTIONAL', 'smtp_login_req': 'OPTIONAL', 'display_from_address': 'PDM mailer <centos@localhost>', 'mail_subject': 'PDM registration - please verify your email address.', 'mail_expiry': '12:00:00', 'mail_token_secret': 'somemailsecretstring' } self._conf = copy.deepcopy(conf) self.__service = FlaskServer("pdm.userservicedesk.HRService") self.__service.test_mode(HRService, None) # to skip DB auto build self.__service.fake_auth("ALL") self.__future_date = (datetime.timedelta(0, 600) + datetime.datetime.utcnow()).isoformat() self.__past_date = (-datetime.timedelta(0, 60) + datetime.datetime.utcnow()).isoformat() # database self.__service.build_db() # build manually # db = self.__service.test_db() new_user = db.tables.User(name='John', surname='Smith', email='*****@*****.**', state=HRServiceUserState.VERIFIED, password=hash_pass('very_secret')) db.session.add(new_user) db.session.commit() self.__service.before_startup(conf) # to continue startup # self.__test = self.__service.test_client() # mail token time_struct = time.strptime("12:00:00", "%H:%M:%S") self.token_duration = datetime.timedelta(hours=time_struct.tm_hour, minutes=time_struct.tm_min, seconds=time_struct.tm_sec) self.mail_token_service = TokenService(self._conf['mail_token_secret'])
def test_key(self): """ Check that different keys yield different results. """ TEST_STR = "Key Test" svc1 = TokenService(key="KeyA") token1 = svc1.issue(TEST_STR) svc2 = TokenService(key="KeyB") token2 = svc2.issue(TEST_STR) self.assertNotEqual(token1, token2) self.assertRaises(ValueError, svc1.check, token2) self.assertRaises(ValueError, svc2.check, token1) self.assertEqual(TEST_STR, svc1.check(token1)) self.assertEqual(TEST_STR, svc2.check(token2))
def test_salt(self): """ Check that setting the salt works. """ TEST_STR = "Salt Test" svc1 = TokenService(key="FixedKey", salt="salt1") token1 = svc1.issue(TEST_STR) svc2 = TokenService(key="FixedKey", salt="salt2") token2 = svc2.issue(TEST_STR) self.assertNotEqual(token1, token2) self.assertRaises(ValueError, svc1.check, token2) self.assertRaises(ValueError, svc2.check, token1) self.assertEqual(TEST_STR, svc1.check(token1)) self.assertEqual(TEST_STR, svc2.check(token2))
def get_token_username_insecure(token): """ Get username from a token. :param token: :return: """ unpacked_token = TokenService.unpack(token) username = unpacked_token.get('email') if not username: HRUtils._logger.error("Token does not contain user information") return username
def get_token_expiry_insecure(token): """ Get token expiry date in ISO format, Insecure - token integrity not checked. :param token: token in :return: ISO of the unpacked token """ unpacked_token = TokenService.unpack(token) expiry_iso = unpacked_token.get('expiry') if not expiry_iso: HRUtils._logger.error("Token does not contain expiry information") return expiry_iso
def get_token_userid(token): """ Get the value of the 'key' part of the token to be used to contact the CS The token holds internally: id: user id expiry: expiry info (to be decided) key: hashed key (from pdm.utils.hashing.hash_pass()). :param token: encrypted token :return: the value of the 'key' field of the token dictionary """ unpacked_user_token = TokenService.unpack(token) userid = unpacked_user_token.get('id', None) return userid
def test_inst(self): """ Check that two instances with the same parameters generate compatible tokens. """ TEST_STR = "Inst Test" params = ("KeyStr", "SaltStr") svc1 = TokenService(*params) token1 = svc1.issue(TEST_STR) svc2 = TokenService(*params) token2 = svc2.issue(TEST_STR) # Tokens should be identical self.assertEqual(token1, token2) self.assertEqual(TEST_STR, svc1.check(token2))
class TestHRService(unittest.TestCase): @mock.patch("pdm.userservicedesk.HRService.SiteClient") def setUp(self, site_mock): self.__site_mock = site_mock conf = { 'token_validity': '01:00:00', 'smtp_server': 'localhost', 'verification_url': 'https://pdm.grid.hep.ph.ic.ac.uk:5443/web/verify', 'smtp_server_login': '******', 'smtp_starttls': 'OPTIONAL', 'smtp_login_req': 'OPTIONAL', 'display_from_address': 'PDM mailer <centos@localhost>', 'mail_subject': 'PDM registration - please verify your email address.', 'mail_expiry': '12:00:00', 'mail_token_secret': 'somemailsecretstring' } self._conf = copy.deepcopy(conf) self.__service = FlaskServer("pdm.userservicedesk.HRService") self.__service.test_mode(HRService, None) # to skip DB auto build self.__service.fake_auth("ALL") self.__future_date = (datetime.timedelta(0, 600) + datetime.datetime.utcnow()).isoformat() self.__past_date = (-datetime.timedelta(0, 60) + datetime.datetime.utcnow()).isoformat() # database self.__service.build_db() # build manually # db = self.__service.test_db() new_user = db.tables.User(name='John', surname='Smith', email='*****@*****.**', state=HRServiceUserState.VERIFIED, password=hash_pass('very_secret')) db.session.add(new_user) db.session.commit() self.__service.before_startup(conf) # to continue startup # self.__test = self.__service.test_client() # mail token time_struct = time.strptime("12:00:00", "%H:%M:%S") self.token_duration = datetime.timedelta(hours=time_struct.tm_hour, minutes=time_struct.tm_min, seconds=time_struct.tm_sec) self.mail_token_service = TokenService(self._conf['mail_token_secret']) def test_getUserSelf(self): """ GET operation on users/self :return: """ self.__service.fake_auth("TOKEN", {'id': 1}) res = self.__test.get('/users/api/v1.0/users/self') assert (res.status_code == 500) self.__service.fake_auth("TOKEN", { 'id': 1, 'expiry': self.__past_date }) res = self.__test.get('/users/api/v1.0/users/self') assert (res.status_code == 403) # token expired self.__service.fake_auth("TOKEN", { 'id': 1, 'expiry': self.__future_date, 'key': 'unused' }) res = self.__test.get('/users/api/v1.0/users/self') assert (res.status_code == 200) user = json.loads(res.data) assert ('id' not in user) assert (user['name'] == 'John') assert (user['surname'] == 'Smith') assert (user['email'] == '*****@*****.**') assert (user['state'] == HRServiceUserState.VERIFIED) assert ('password' not in user) # self.__service.fake_auth("TOKEN", { 'id': 2, 'expiry': self.__future_date }) res = self.__test.get('/users/api/v1.0/users/self') assert (res.status_code == 404) @mock.patch("pdm.userservicedesk.HRService.HRService.email_user") def test_addUser(self, email_user_mock): """ Testinf user registration. Ignore emailer at this stage. :return: """ self.__service.fake_auth("ALL") fred = { 'surname': 'Flintstone', 'name': 'Fred', 'email': '*****@*****.**', 'state': 0, 'password': '******' } barney = { 'surname': 'Rubble', 'name': 'Barney', 'email': '*****@*****.**', 'state': 0, 'password': '******' } res = self.__test.post('/users/api/v1.0/users', data=fred) assert (res.status_code == 201) # db db = self.__service.test_db() dbuser = db.tables.User.query.filter_by(email=fred['email']).first() assert (dbuser.name == fred['name']) assert (check_hash(dbuser.password, fred['password'])) assert (dbuser.email == fred['email']) response = json.loads(res.data) assert (response['name'] == fred['name']) assert (response['surname'] == fred['surname']) assert (response['email'] == fred['email']) assert (response['state'] == fred['state']) assert ('password' not in response) # try to duplicate the user: res = self.__test.post('/users/api/v1.0/users', data=fred) assert (res.status_code == 403) # password too short ! res = self.__test.post('/users/api/v1.0/users', data=barney) assert (res.status_code == 400) # barney OK, but verification email sending fails: barney['password'] = '******' email_user_mock.side_effect = RuntimeError res = self.__test.post('/users/api/v1.0/users', data=barney) assert (res.status_code == 500) # b_email = barney.pop('email') res = self.__test.post('/users/api/v1.0/users', data=barney) assert (res.status_code == 400) barney['email'] = b_email password = barney.pop('password') res = self.__test.post('/users/api/v1.0/users', data=barney) assert (res.status_code == 400) def test_change_password(self): """ Test the password changing operation :return: """ self.__service.fake_auth("TOKEN", { 'id': 1, 'expiry': self.__past_date }) new_pass_data = { 'passwd': 'very_secret', 'newpasswd': 'even_more_secret' } res = self.__test.put('/users/api/v1.0/passwd', data=new_pass_data) assert (res.status_code == 403) self.__service.fake_auth("TOKEN", { 'id': 1, 'expiry': self.__future_date }) # fake auth John, which is id=1 new_pass_data = { 'passwd': 'very_secret', 'newpasswd': 'even_more_secret' } res = self.__test.put('/users/api/v1.0/passwd', data=new_pass_data) assert (res.status_code == 200) # check if the password was actually modified: db = self.__service.test_db() dbuser = db.tables.User.query.filter_by( email='*****@*****.**').first() assert (dbuser.name == "John") assert (check_hash(dbuser.password, 'even_more_secret')) # response = json.loads(res.data) assert ('password' not in response) # TODO check last login timestamp later than the time before changing the password. # wrong password wrong_pass_data = { 'passwd': 'very_sercet', 'newpasswd': 'even_more_secret' } res = self.__test.put('/users/api/v1.0/passwd', data=wrong_pass_data) assert (res.status_code == 403) # same pass same_pass_data = { 'passwd': 'even_more_secret', 'newpasswd': 'even_more_secret' } res = self.__test.put('/users/api/v1.0/passwd', data=same_pass_data) assert (res.status_code == 400) # no pass no_pass = {'passwd': None, 'newpasswd': 'even_more_secret'} res = self.__test.put('/users/api/v1.0/passwd', data=no_pass) assert (res.status_code == 400) no_pass = {'newpasswd': 'even_more_secret'} res = self.__test.put('/users/api/v1.0/passwd', data=no_pass) assert (res.status_code == 400) no_pass = {'passwd': 'even_more_secret'} res = self.__test.put('/users/api/v1.0/passwd', data=no_pass) assert (res.status_code == 400) # no_npass = {'passwd': 'even_more_secret', 'newpasswd': None} res = self.__test.put('/users/api/v1.0/passwd', data=no_npass) assert (res.status_code == 400) # weak pass weak_pass = {'passwd': 'even_more_secret', 'newpasswd': 'test'} res = self.__test.put('/users/api/v1.0/passwd', data=weak_pass) assert (res.status_code == 400) # non existing user self.__service.fake_auth("TOKEN", { 'id': 7, 'expiry': self.__future_date }) res = self.__test.put('/users/api/v1.0/passwd', data=new_pass_data) assert (res.status_code == 403) # @mock.patch('pdm.userservicedesk.HRService.SiteClient') def test_delete_user(self): """ Test deleting user data :return: """ # not existing user: self.__service.fake_auth("TOKEN", { 'id': 7, 'expiry': self.__future_date }) res = self.__test.delete('/users/api/v1.0/users/self') assert (res.status_code == 404) assert not self.__site_mock().del_user.called # attempt to delete Johnny with an expired token self.__service.fake_auth("TOKEN", { 'id': 1, 'expiry': self.__past_date, 'key': 'unused' }) # fake auth John, which is id=1 res = self.__test.delete('/users/api/v1.0/users/self') assert (res.status_code == 403) assert not self.__site_mock().del_user.called # delete poor Johnny ;-( self.__service.fake_auth("TOKEN", { 'id': 1, 'expiry': self.__future_date, 'key': 'unused' }) # fake auth John, which is id=1 res = self.__test.delete('/users/api/v1.0/users/self') assert (res.status_code == 200) assert self.__site_mock().del_user.called def test_deleteUser_SiteService_fail(self): """ Test if the user is put back when SiteService fails :param mock_del_user: :return: """ # delete poor Johnny ;-( self.__service.fake_auth("TOKEN", { 'id': 1, 'expiry': self.__future_date }) # fake auth John, which is id=1 self.__site_mock().del_user.side_effect = Exception() res = self.__test.delete('/users/api/v1.0/users/self') assert (res.status_code == 500) assert self.__site_mock().del_user.called # check if we rolled John back ! db = self.__service.test_db() dbuser = db.tables.User.query.filter_by( email='*****@*****.**').first() assert (dbuser is not None) @mock.patch('sqlalchemy.orm.scoping.scoped_session.delete') def test_deleteUser_HR_fail(self, mock_del): self.__service.fake_auth("TOKEN", { 'id': 1, 'expiry': self.__future_date }) # fake auth John, which is id=1 mock_del.side_effect = Exception() res = self.__test.delete('/users/api/v1.0/users/self') assert (res.status_code == 500) assert not self.__site_mock().del_user.called def test_loginUser(self): """ Test the user login procedure :return: """ res = self.__test.post('/users/api/v1.0/login') # empty req. assert (res.status_code == 400) res = self.__test.post('/users/api/v1.0/login', data=('hulagula')) assert (res.status_code == 400) login_creds = {'email': '*****@*****.**', 'passwd': 'very_secret'} res = self.__test.post('/users/api/v1.0/login', data=login_creds) assert (res.status_code == 200) # token_data = self.__service.token_svc.check(json.loads(res.data)) db = self.__service.test_db() dbuser = db.tables.User.query.filter_by( email='*****@*****.**').first() assert token_data['id'] == 1 isoformat = '%Y-%m-%dT%H:%M:%S.%f' expiry_date = datetime.datetime.strptime(token_data['expiry'], isoformat) # conf gives 1h token validity. Check if we are within 10s assert abs( (expiry_date - (datetime.datetime.utcnow() + datetime.timedelta(0, 3600))).total_seconds()) < 10 login_creds_b = {'email': '*****@*****.**'} res = self.__test.post('/users/api/v1.0/login', data=login_creds_b) assert (res.status_code == 400) res = self.__test.post('/users/api/v1.0/login', data={ 'email': '*****@*****.**', 'passwd': 'very_seCret' }) assert (res.status_code == 403) res = self.__test.post('/users/api/v1.0/login', data={ 'email': '*****@*****.**', 'passwd': 'very_secret' }) assert (res.status_code == 403) res = self.__test.post('/users/api/v1.0/login', data={ 'email': '*****@*****.**', 'passwd': None }) assert (res.status_code == 400) # make Johny unverified ;-( dbuser.state = HRServiceUserState.REGISTERED db.session.add(dbuser) db.session.commit() res = self.__test.post('/users/api/v1.0/login', data=login_creds) assert (res.status_code == 401) @mock.patch('smtplib.SMTP') @mock.patch.object(HRService, 'compose_and_send') def test_email_user(self, mcs, smtp_mock): with self.__service.test_request_context(path="/test"): with mock.patch.object(pdm.userservicedesk.HRService.current_app, 'mail_token_service') as m_ts: m_ts.issue = mock.MagicMock(return_value='agfgffsgdf') HRService.email_user("*****@*****.**") assert mcs.call_args[0][0] == '*****@*****.**' assert mcs.call_args[0][1] == 'agfgffsgdf' #(ignore the timestamp passed in as the third arg.) def test_verify_user(self): # isssue a valid mail token expiry = datetime.datetime.utcnow() + self.token_duration plain = {'expiry': expiry.isoformat(), 'email': '*****@*****.**'} token = self.mail_token_service.issue(plain) #body = os.path.join(self._conf['verification_url'],token) # verify takes a token only, not the whole email body at the moment db = self.__service.test_db() dbuser = db.tables.User.query.filter_by( email='*****@*****.**').first() dbuser.state = HRServiceUserState.REGISTERED # unverify ! db.session.add(dbuser) db.session.commit() #token tempered with: res = self.__test.post('/users/api/v1.0/verify', data={'mailtoken': token[1:]}) assert res.status_code == 400 # success ? res = self.__test.post('/users/api/v1.0/verify', data={'mailtoken': token}) assert res.status_code == 201 #repeat a verification attempt dbuser.state = HRServiceUserState.VERIFIED db.session.add(dbuser) db.session.commit() res = self.__test.post('/users/api/v1.0/verify', data={'mailtoken': token}) assert res.status_code == 400 # expired token dbuser.state = HRServiceUserState.REGISTERED # unverify ! db.session.add(dbuser) db.session.commit() expired = datetime.datetime.utcnow() - self.token_duration e_plain = { 'expiry': expired.isoformat(), 'email': '*****@*****.**' } e_token = self.mail_token_service.issue(e_plain) res = self.__test.post('/users/api/v1.0/verify', data={'mailtoken': e_token}) assert res.status_code == 400 # non existent user: plain = {'expiry': expiry.isoformat(), 'email': '*****@*****.**'} token = self.mail_token_service.issue(plain) res = self.__test.post('/users/api/v1.0/verify', data={'mailtoken': token}) assert res.status_code == 400 @mock.patch('smtplib.SMTP') def test_resend_email(self, smtp_mock): db = self.__service.test_db() dbuser = db.tables.User.query.filter_by( email='*****@*****.**').first() dbuser.state = HRServiceUserState.REGISTERED # unverify ! db.session.add(dbuser) db.session.commit() email = '*****@*****.**' data = {'email': email} res = self.__test.post('/users/api/v1.0/resend', data=data) assert res.status_code == 200 dbuser.state = HRServiceUserState.VERIFIED db.session.add(dbuser) db.session.commit() res = self.__test.post('/users/api/v1.0/resend', data=data) assert res.status_code == 400 res = self.__test.post('/users/api/v1.0/resend', data={'email': 'hula@gula'}) assert res.status_code == 400 res = self.__test.post('/users/api/v1.0/resend', data={'Email': 'hula@gula'}) assert res.status_code == 400 @mock.patch('email.MIMEMultipart.MIMEMultipart') @mock.patch.object(smtplib.SMTP, 'connect') @mock.patch.object(smtplib.SMTP, 'close') def test_compose_and_send(self, close_mock, connect_mock, mail_mock): with self.__service.test_request_context(path="/test"): # force connect to raise the SMTPException derived class. HRService wraps it into # RuntimeError connect_mock.return_value = (400, 'cannot connect message' ) # 220 is the success code with self.assertRaises(RuntimeError): HRService.compose_and_send("centos@localhost", 'mytoken_abc', datetime.datetime.utcnow() ) # timestamp does not matter here connect_mock.assert_called_with('localhost', None) # from conf{} # now allow for connect() to raise a socket.error import socket connect_mock.side_effect = socket.error with self.assertRaises(RuntimeError): HRService.compose_and_send("centos@localhost", 'mytoken_abc', datetime.datetime.utcnow()) @mock.patch('email.MIMEMultipart.MIMEMultipart') @mock.patch('smtplib.SMTP') def test_compose_and_send_sendmail(self, smtp_mock, mail_mock): with self.__service.test_request_context(path="/test"): # sendmail errors mytoken = 'mytoken_abc' toaddr = "user@remotehost" body = os.path.join(self._conf['verification_url'], mytoken) smtp_mock.return_value.sendmail.side_effect = smtplib.SMTPException with self.assertRaises(RuntimeError): HRService.compose_and_send(toaddr, mytoken, datetime.datetime.utcnow()) args = smtp_mock.return_value.sendmail.call_args assert args[0][0] == self._conf['smtp_server_login'] assert args[0][1] == toaddr assert body in args[0][2] # check the important part of the email def test_hello(self): res = self.__test.get('/users/api/v1.0/hello') assert (res.status_code == 200) res_str = json.loads(res.data) assert (res_str == 'User Service Desk at your service !\n')
def load_userconfig(config): """ Configure the HRService application. Gets the key needed to contact the Credential Service """ current_app.pwd_len = config.pop("pswd_length", 8) # token validity period struct (from: HH:MM:SS) try: time_struct = time.strptime( config.pop("token_validity", "12:00:00"), "%H:%M:%S") current_app.token_duration = datetime.timedelta( hours=time_struct.tm_hour, minutes=time_struct.tm_min, seconds=time_struct.tm_sec) HRService._logger.info( "User login token duration parsed successfully") m_time_struct = time.strptime( config.pop("mail_token_validity", "23:59:00"), "%H:%M:%S") current_app.mail_token_duration = datetime.timedelta( hours=m_time_struct.tm_hour, minutes=m_time_struct.tm_min, seconds=m_time_struct.tm_sec) HRService._logger.info( "User mail token duration parsed successfully") except ValueError as v_err: HRService._logger.error( " Token lifetime provided in the config " "file has wrong format %s. Aborting.", v_err) raise ValueError("Token lifetime incorrect format %s" % v_err) # verification email: current_app.smtp_server = config.pop("smtp_server", None) if current_app.smtp_server is None: HRService._logger.error( " Mail server not provided in the config. Aborting") raise ValueError( " Mail server not provided in the config. Aborting") current_app.smtp_server_port = config.pop("smtp_server_port", None) current_app.smtp_server_login = config.pop("smtp_server_login", None) current_app.mail_display_from = config.pop("display_from_address", None) current_app.smtp_server_pwd = config.pop("smtp_server_pwd", None) current_app.mail_subject = config.pop("mail_subject", None) current_app.mail_expiry = config.pop("mail_expiry", "12:00:00") mail_server_req = ['REQUIRED', 'OPTIONAL', 'OFF'] allowed_opts = ', '.join(mail_server_req) current_app.smtp_server_starttls = config.pop('smtp_starttls', 'UNDEFINED').upper() if current_app.smtp_server_starttls not in mail_server_req: HRService._logger.error( ' Mail server starttls option invalid (%s). Aborting.', current_app.smtp_server_starttls) HRService._logger.error("Allowed option values are: %s.", allowed_opts) raise ValueError( 'Mail server starttls option invalid (%s) . Aborting.' % current_app.smtp_server_starttls) current_app.smtp_server_login_req = config.pop('smtp_login_req', 'UNDEFINED').upper() if current_app.smtp_server_login_req not in mail_server_req: HRService._logger.error( ' Mail server smtp_server_login_req option invalid (%s).' ' Aborting', current_app.smtp_server_login_req) HRService._logger.error("Allowed option values are: %s.", allowed_opts) raise ValueError( 'Mail server smtp_server_login_req option invalid (%s). Aborting.' % current_app.smtp_server_login_req) current_app.verification_url = config.pop("verification_url") if current_app.verification_url is None: HRService._logger.error( " Mail verification URL not provided in the config. Aborting") raise ValueError( " Mail verification URL not provided in the config. Aborting") current_app.mail_token_secret = config.pop("mail_token_secret") # TokenService used to generate a verification email token. current_app.mail_token_service = TokenService( current_app.mail_token_secret) # site client current_app.site_client = SiteClient()