def send_termination_mail(request, user): mailer = get_mailer(request) support_email = request.registry.settings.get('mail.support_email', '*****@*****.**') site_name = request.registry.settings.get("site.name", "eduID") context = { 'support_mail': support_email, 'displayName': user.get_display_name() } message = Message( subject=_("{site_name} account termination").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[user.get_mail()], body=render( "templates/termination_email.txt.jinja2", context, request, ), html=render( "templates/termination_email.html.jinja2", context, request, ), ) # Development if request.registry.settings.get("development", '') == 'true': print message.body else: mailer.send(message) log.debug("Sent termination mail to user {!r} with address {!s}.".format(user, user.get_mail())) request.stats.count('dashboard/email_send_termination_mail', 1)
def get_saml_attribute(session_info, attr_name): """ Get value from a SAML attribute received from the SAML IdP. session_info is a pysaml2 response.session_info(). This is a dictionary like {'mail': ['*****@*****.**'], 'eduPersonPrincipalName': ['*****@*****.**'] } :param session_info: SAML attributes received by pysaml2 client. :param attr_name: The attribute to look up :returns: Attribute values :type session_info: dict() :type attr_name: string() :rtype: [string()] """ if not 'ava' in session_info: raise ValueError('SAML attributes (ava) not found in session_info') attributes = session_info['ava'] log.debug('SAML attributes received: %s' % attributes) attr_name = attr_name.lower() # Look for the canonicalized attribute in the SAML assertion attributes for saml_attr, local_fields in attributes.items(): if saml_attr.lower() == attr_name: return attributes[saml_attr]
def __call__(self, node, value): request = node.bindings.get('request') token = request.session.get_csrf_token() if value != token: log.debug("CSRF token validation failed: Form {!r} != Session {!r}".format(value, token)) raise colander.Invalid(node, _("Invalid CSRF token"))
def save_success(self, passwordform): passwords_data = self.schema.serialize(passwordform) user = self.request.session['user'] if passwords_data.get('use_custom_password') == 'true': # The user has entered his own password and it was verified by # validators log.debug("Password change for user {!r} (custom password).".format(user.get_id())) new_password = passwords_data.get('custom_password') else: # If the user has selected the suggested password, then it should # be in session log.debug("Password change for user {!r} (suggested password).".format(user.get_id())) new_password = self.get_suggested_password() new_password = new_password.replace(' ', '') old_password = passwords_data['old_password'] # Load user from database to ensure we are working on an up-to-date set of credentials. # XXX this refresh is a bit redundant with the same thing being done in OldPasswordValidator. user = self.request.userdb.get_user_by_oid(user.get_id()) self.changed = change_password(self.request, user, old_password, new_password) if self.changed: self.message = _('Your password has been successfully updated') else: self.message = _('An error has occured while updating your password, ' 'please try again or contact support if the problem persists.')
def reset_success(self, passwordform): passwords_data = self.schema.serialize(passwordform) email_or_username = passwords_data['email_or_username'] # If input is a mail address we need to normalize it (ie lower case etc) if validate_email_format(email_or_username): email_or_username = normalize_email(email_or_username) elif email_or_username.startswith(u'0'): email_or_username = normalize_to_e_164(self.request, email_or_username) try: filter_dict = {'$or': []} for field in self.SEARCH_FIELDS: filter_dict['$or'].append({field: email_or_username}) user = self.request.userdb.get_user_by_filter(filter_dict) except self.request.userdb.exceptions.UserDoesNotExist: log.debug("User {!r} does not exist".format(email_or_username)) user = None if user is not None: nin = None nins = user.get_nins() if nins: nin = nins[-1] if nin is not None: reset_password_link = new_reset_password_code(self.request, user, mechanism='govmailbox') send_reset_password_gov_message(self.request, nin, user, reset_password_link) self.request.session['_reset_type'] = _('Myndighetspost') return HTTPFound(location=self.request.route_url('reset-password-sent'))
def _verify_action(self, data_id, post_data): if 'code' in post_data: code_sent = post_data['code'] verification_code = get_verification_code(self.request, self.data_attribute, obj_id=data_id, user=self.user) if verification_code: if code_sent == verification_code['code']: if verification_code['expired']: log.debug("User {!r} verification code has expired".format(self.user)) return { 'result': 'error', 'message': self.verify_messages['expired'], } else: verificate_code(self.request, self.data_attribute, code_sent) return { 'result': 'ok', 'message': self.verify_messages['ok'], } else: log.debug("Incorrect code for user {!r}: {!r}".format(self.user, code_sent)) return { 'result': 'error', 'message': self.verify_messages['error'], } else: message = self.verify_messages['request'].format(data=data_id) return { 'result': 'getcode', 'message': message, 'placeholder': self.verify_messages['placeholder'], }
def get_authn_info(request): """ Get credential information for the current user. :param request: the request object :return: a list of dicts [{'type': string, 'created_ts': timestamp, 'success_ts': timestamp }] """ if 'edit-user' in request.session: user = request.session['edit-user'] else: user = request.session['user'] authninfo = [] for credential in user.get_passwords(): auth_entry = request.authninfodb.authn_info.find_one({'_id': ObjectId(credential['id'])}) log.debug("cred id: {!r} auth entry: {!r}".format(credential['id'], auth_entry)) if auth_entry: created_dt = convert_to_localtime(credential['created_ts']) success_dt = convert_to_localtime(auth_entry['success_ts']) data_type = _('Password') data = {'type': get_localizer(request).translate(data_type), 'created_ts': created_dt.strftime('%Y-%b-%d %H:%M'), 'success_ts': success_dt.strftime('%Y-%b-%d %H:%M')} authninfo.append(data) return authninfo
def get_authn_request(request, came_from, selected_idp, required_loa=None, force_authn=False): # Request the right AuthnContext for workmode # (AL1 for 'personal', AL2 for 'helpdesk' and AL3 for 'admin' by default) if required_loa is None: required_loa = request.registry.settings.get('required_loa', {}) workmode = request.registry.settings.get('workmode') required_loa = required_loa.get(workmode, '') log.debug('Requesting AuthnContext {!r}'.format(required_loa)) kwargs = { "requested_authn_context": RequestedAuthnContext( authn_context_class_ref=AuthnContextClassRef( text=required_loa ) ), "force_authn": str(force_authn).lower(), } client = Saml2Client(request.saml2_config) try: (session_id, info) = client.prepare_for_authenticate( entityid=selected_idp, relay_state=came_from, binding=BINDING_HTTP_REDIRECT, **kwargs ) except TypeError: log.error('Unable to know which IdP to use') raise oq_cache = OutstandingQueriesCache(request.session) oq_cache.set(session_id, came_from) return info
def set_phone_verified(request, user, new_number): """ Mark a phone number as verified on a user. This process also includes *removing* the phone number from any other user that had it as a verified phone number. :param request: The HTTP request :param user: The user :param new_number: The phone number to mark as verified :type request: pyramid.request.Request :type user: User :type new_number: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify phone number for user {!r}.'.format(user)) log.debug('Phone number: {!s}.'.format(new_number)) # Start by removing mobile number from any other user old_user = request.userdb_new.get_user_by_phone(new_number, raise_on_missing=False) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) _remove_phone_from_user(new_number, old_user) request.context.save_dashboard_user(old_user) log.info('Removed phone number from user {!r}.'.format(old_user)) steal_count = 1 # Add the verified mobile number to the requesting user _add_phone_to_user(new_number, user) log.info('Phone number verified for user {!r}.'.format(user)) request.stats.count('dashboard/verify_mobile_stolen', steal_count) request.stats.count('dashboard/verify_mobile_completed', 1) return _('Phone {obj} verified')
def set_email_verified(request, user, new_mail): """ Mark an e-mail address as verified on a user. This process also includes *removing* the e-mail address from any other user that had it as a verified e-mail address. :param request: The HTTP request :param user: The user :param new_mail: The e-mail address to mark as verified :type request: pyramid.request.Request :type user: User :type new_mail: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify mail address for user {!r}.'.format(user)) log.debug('Mail address: {!s}.'.format(new_mail)) # Start by removing the email address from any other user that currently has it (verified) old_user = request.userdb_new.get_user_by_mail(new_mail, raise_on_missing=False) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) old_user = _remove_mail_from_user(new_mail, old_user) request.context.save_dashboard_user(old_user) steal_count = 1 # Add the verified mail address to the requesting user _add_mail_to_user(new_mail, user) log.info('Mail address verified for user {!r}.'.format(user)) request.stats.count('verify_mail_stolen', steal_count) request.stats.count('verify_mail_completed') return _('Email {obj} verified')
def set_phone_verified(request, user, new_number): """ Mark a phone number as verified on a user. This process also includes *removing* the phone number from any other user that had it as a verified phone number. :param request: The HTTP request :param user: The user :param new_number: The phone number to mark as verified :type request: pyramid.request.Request :type user: User :type new_number: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify phone number for user {!r}.'.format(user)) log.debug('Phone number: {!s}.'.format(new_number)) # Start by removing mobile number from any other user old_user = request.userdb_new.get_user_by_phone(new_number, raise_on_missing=False) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) _remove_phone_from_user(new_number, old_user) request.context.save_dashboard_user(old_user) log.info('Removed phone number from user {!r}.'.format(old_user)) steal_count = 1 # Add the verified mobile number to the requesting user _add_phone_to_user(new_number, user) log.info('Phone number verified for user {!r}.'.format(user)) request.stats.count('verify_mobile_stolen', steal_count) request.stats.count('verify_mobile_completed') return _('Phone {obj} verified')
def set_email_verified(request, user, new_mail): """ Mark an e-mail address as verified on a user. This process also includes *removing* the e-mail address from any other user that had it as a verified e-mail address. :param request: The HTTP request :param user: The user :param new_mail: The e-mail address to mark as verified :type request: pyramid.request.Request :type user: User :type new_mail: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify mail address for user {!r}.'.format(user)) log.debug('Mail address: {!s}.'.format(new_mail)) # Start by removing the email address from any other user that currently has it (verified) old_user = request.userdb_new.get_user_by_mail(new_mail, raise_on_missing=False) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) old_user = _remove_mail_from_user(new_mail, old_user) request.context.save_dashboard_user(old_user) steal_count = 1 # Add the verified mail address to the requesting user _add_mail_to_user(new_mail, user) log.info('Mail address verified for user {!r}.'.format(user)) request.stats.count('dashboard/verify_mail_stolen', steal_count) request.stats.count('dashboard/verify_mail_completed', 1) return _('Email {obj} verified')
def login_view(request): login_redirect_url = request.registry.settings.get( 'saml2.login_redirect_url', '/') came_from = sanitize_get(request, 'next', login_redirect_url) if authenticated_userid(request): return HTTPFound(location=came_from) selected_idp = sanitize_get(request, 'idp', None) if selected_idp is not None: request.session['selected_idp'] = selected_idp idps = request.saml2_config.getattr('idp') if selected_idp is None and len(idps) > 1: log.debug('A discovery process is needed') return render_to_response('templates/wayf.jinja2', { 'available_idps': idps.items(), 'came_from': came_from, 'login_url': request.route_url('saml2-login'), }) result = get_authn_request(request.registry.settings, request.session, came_from, selected_idp) schedule_action(request.session, 'login-action') log.debug('Redirecting the user to the IdP') if not request.is_xhr: return HTTPFound(location=get_location(result)) else: loginurl = request.route_url('saml2-login', _query=(('next', request.path),)) return HTTPXRelocate(loginurl)
def forbidden_view(context, request): """ View to trap all Forbidden errors and redirect any not logged in users to the login page. For logged in users, a template is rendered - this template probably won't be seen by the user though since there is Javascript handling 401 errors from form posts showing a small pop-up error message instead. :param context: Some object like HTTPForbidden() :param request: Request() object :return: """ user = authenticated_userid(request) if user: # Return a plain forbbiden page try: reason = context.explanation except AttributeError: reason = 'unknown' log.debug("User {!r} tripped Forbidden view, request {!r}, reason {!r}".format( user, request, reason)) response = Response(render('templates/forbidden.jinja2', {})) response.status_int = 401 return response loginurl = request.route_url('saml2-login', _query=(('next', request.path),)) if not request.is_xhr: return HTTPFound(location=loginurl) else: return HTTPXRelocate(loginurl)
def reset_success(self, passwordform): form_data = self.schema.serialize(passwordform) hash_code = self.request.matchdict['code'] password_reset = self.request.db.reset_passwords.find_one({'hash_code': hash_code}) user = self.request.userdb.get_user_by_email(password_reset['email']) user.retrieve_modified_ts(self.request.db.profiles) if form_data.get('use_custom_password') == 'true': log.debug("Password change for user {!r} (custom password).".format(user.get_id())) new_password = form_data.get('custom_password') self.request.stats.count('dashboard/pwreset_custom_password', 1) else: log.debug("Password change for user {!r} (suggested password).".format(user.get_id())) new_password = self.get_suggested_password() self.request.stats.count('dashboard/pwreset_generated_password', 1) new_password = new_password.replace(' ', '') self.request.db.reset_passwords.remove({'_id': password_reset['_id']}) ok = change_password(self.request, user, '', new_password) if ok: self.request.stats.count('dashboard/pwreset_changed_password', 1) if password_reset['mechanism'] == 'email': # TODO: Re-send verification code in advance? nins = user.get_nins() reset_nin_count = 0 if nins: # XXX shouldn't the downgrade of NIN to unverified be done to *ALL* the user's NINs? nin = nins[-1] if nin is not None: self.request.db.profiles.update({ "_id": user.get_id() }, { "$set": {"norEduPersonNIN": []} }) # Do not remove the verification as we no longer allow users to remove a already verified nin # even if it gets unverified by a e-mail password reset. self.request.db.verifications.update({ "user_oid": user.get_id(), "model_name": "norEduPersonNIN", "obj_id": nin }, { "$set": {"verified": False} }) update_attributes('eduid_dashboard', str(user['_id'])) reset_nin_count += 1 self.request.stats.count('dashboard/pwreset_downgraded_NINs', reset_nin_count) url = self.request.route_url('profile-editor') reset = True else: self.request.stats.count('dashboard/pwreset_password_change_failed', 1) url = self.request.route_url('reset-password') reset = False return { 'url': url, 'reset': reset }
def login_action(request, session_info, user): headers = login(request, session_info, user) _set_name_id(request.session, session_info['name_id']) # redirect the user to the view where he came from relay_state = sanitize_post_key(request, 'RelayState', '/') log.debug('Redirecting to the RelayState: ' + relay_state) return HTTPFound(location=relay_state, headers=headers)
def __call__(self, node, value): request = node.bindings.get('request') token = request.session.get_csrf_token() if value != token: log.debug( "CSRF token validation failed: Form {!r} != Session {!r}". format(value, token)) err = _("Invalid CSRF token") raise colander.Invalid(node, get_localizer(request).translate(err))
def send_reset_password_mail(request, user, reset_password_link): """ Send an email with the instructions for resetting password """ mailer = get_mailer(request) site_name = request.registry.settings.get("site.name", "eduID") password_reset_timeout = int( request.registry.settings.get("password_reset_timeout", "120")) / 60 if user.mail_addresses.primary is not None: email = user.mail_addresses.primary.email elif user.mail_addresses.count > 0: email = user.mail_addresses.to_list()[0].email else: log.info( 'User {!r} has no email address, not possible to send a message'. format(user)) return context = { "email": email, "reset_password_link": reset_password_link, "password_reset_timeout": password_reset_timeout, "site_url": request.route_url("home"), "site_name": site_name, } message = Message( subject=_("Reset your {site_name} password").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[email], body=render( "templates/reset-password-email.txt.jinja2", context, request, ), html=render( "templates/reset-password-email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug( "Sent reset password mail to user {!r} with address {!s}.".format( user, email)) request.stats.count('email_send_pwreset_mail')
def __call__(self, node, value): request = node.bindings.get('request') user = get_session_user(request) mobile = normalize_to_e_164(request, value) # The debug logs below were added to help figure out how a user # managed to get by past this function with a duplicated phone number, # which was caught by userdb while saving the user. log.debug("User {!r} tried to add a phone number {!r}: " "entering 1st code-section that will verify that it's unique." .format(user.eppn, mobile)) if sanitize_post_key(request, 'add') is not None: log.debug("User {!r} tried to add a phone number {!r}: " "1st code-section evaluated, POST request OK." .format(user.eppn, mobile)) if user.phone_numbers.find(mobile): log.debug("User {!r} tried to add a phone number {!r}: " "2nd code-section evaluated, the user had already " "added the phone number." .format(user.eppn, mobile)) err = _("This mobile phone was already registered") raise colander.Invalid(node, get_localizer(request).translate(err)) else: log.debug("User {!r} tried to add a phone number {!r}: " "2nd code-section evaluated, the phone number " "has not previously been added by the user." .format(user.eppn, mobile))
def __call__(self, node, value): request = node.bindings.get('request') user = request.context.user mobile = normalize_to_e_164(request, value) # The debug logs below were added to help figure out how a user # managed to get by past this function with a duplicated phone number, # which was caught by userdb while saving the user. log.debug( "User {!r} tried to add a phone number {!r}: " "entering 1st code-section that will verify that it's unique.". format(user.eppn, mobile)) if sanitize_post_key(request, 'add') is not None: log.debug("User {!r} tried to add a phone number {!r}: " "1st code-section evaluated, POST request OK.".format( user.eppn, mobile)) if user.phone_numbers.find(mobile): log.debug("User {!r} tried to add a phone number {!r}: " "2nd code-section evaluated, the user had already " "added the phone number.".format(user.eppn, mobile)) err = _("This mobile phone was already registered") raise colander.Invalid(node, get_localizer(request).translate(err)) else: log.debug("User {!r} tried to add a phone number {!r}: " "2nd code-section evaluated, the phone number " "has not previously been added by the user.".format( user.eppn, mobile))
def verify_mobile(request, user, new_mobile): log.info('Trying to verify mobile number for user {!r}.'.format(user)) log.debug('Mobile number: {!s}.'.format(new_mobile)) # Start by removing mobile number from any other user old_user_docs = request.db.profiles.find({ 'mobile': {'$elemMatch': {'mobile': new_mobile, 'verified': True}} }) steal_count = 0 for old_user_doc in old_user_docs: old_user = User(old_user_doc) if old_user: log.debug('Found old user {!r} with mobile number ({!s}) already verified.'.format(old_user, new_mobile)) log.debug('Old user mobile numbers BEFORE: {!r}.'.format(old_user.get_mobiles())) mobiles = [m for m in old_user.get_mobiles() if m['mobile'] != new_mobile] old_user.set_mobiles(mobiles) log.debug('Old user mobile numbers AFTER: {!r}.'.format(old_user.get_mobiles())) old_user.retrieve_modified_ts(request.db.profiles) old_user.save(request) log.info('Removed mobile number from user {!r}.'.format(old_user)) steal_count += 1 # Add the verified mobile number to the requesting user user.add_verified_mobile(new_mobile) log.info('Mobile number verified for user {!r}.'.format(user)) request.stats.count('dashboard/verify_mobile_stolen', steal_count) request.stats.count('dashboard/verify_mobile_completed', 1) return user, _('Mobile {obj} verified')
def revoke_all_credentials(vccs_url, user): vccs = get_vccs_client(vccs_url) passwords = user.credentials.filter(Password).to_list() to_revoke = [] for passwd in passwords: credential_id = str(passwd.credential_id) factor = vccs_client.VCCSRevokeFactor( credential_id, 'subscriber requested termination', reference='dashboard') log.debug("Revoked old credential (account termination)" " {!s} (user {!r})".format(credential_id, user)) to_revoke.append(factor) userid = str(user.user_id) vccs.revoke_credentials(userid, to_revoke)
def _add_phone_to_user(new_number, user): """ Add a phone number to a user. Part of set_phone_verified() above. """ phone = PhoneNumber(data={'number': new_number, 'verified': True, 'primary': False}) log.debug('User had phones BEFORE verification: {!r}'.format(user.phone_numbers.to_list())) if user.phone_numbers.primary is None: log.debug('Setting NEW phone number to primary: {}.'.format(phone)) phone.is_primary = True try: user.phone_numbers.add(phone) except DuplicateElementViolation: user.phone_numbers.find(new_number).is_verified = True
def send_reset_password_mail(request, user, reset_password_link): """ Send an email with the instructions for resetting password """ mailer = get_mailer(request) site_name = request.registry.settings.get("site.name", "eduID") password_reset_timeout = int(request.registry.settings.get("password_reset_timeout", "120")) / 60 if user.mail_addresses.primary is not None: email = user.mail_addresses.primary.email elif user.mail_addresses.count > 0: email = user.mail_addresses.to_list()[0].email else: log.info('User {!r} has no email address, not possible to send a message'.format(user)) return context = { "email": email, "reset_password_link": reset_password_link, "password_reset_timeout": password_reset_timeout, "site_url": request.route_url("home"), "site_name": site_name, } message = Message( subject=_("Reset your {site_name} password").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[email], body=render( "templates/reset-password-email.txt.jinja2", context, request, ), html=render( "templates/reset-password-email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug("Sent reset password mail to user {!r} with address {!s}.".format(user, email)) request.stats.count('email_send_pwreset_mail')
def send_verification_mail(request, email, reference=None, code=None): mailer = get_mailer(request) if code is None or reference is None: reference, code = new_verification_code(request, 'mailAliases', email, request.context.user, hasher=get_short_hash) verification_link = generate_verification_link(request, code, 'mailAliases') site_name = request.registry.settings.get("site.name", "eduID") context = { "email": email, "verification_link": verification_link, "site_url": request.context.safe_route_url("home"), "site_name": site_name, "code": code, } message = Message( subject=_("{site_name} confirmation email").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[email], body=render( "templates/verification_email.txt.jinja2", context, request, ), html=render( "templates/verification_email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug("Sent verification mail to user {!r} with address {!s}.".format( request.context.user, email)) request.stats.count('email_send_verification_code')
def save_success(self, passwordform): authn_ts = self.request.session.get('re-authn-ts', None) if authn_ts is None: raise HTTPBadRequest(_('No authentication info')) else: now = datetime.utcnow() delta = now - authn_ts if int(delta.total_seconds()) > 600: msg = _('Stale authentication info. Please try again.') self.request.session.flash('error|' + msg) raise HTTPFound(self.context.route_url('profile-editor')) del self.request.session['re-authn-ts'] passwords_data = self.schema.serialize(passwordform) if 'edit-user' in self.request.session: user = self.request.session['edit-user'] else: user = self.request.session['user'] if passwords_data.get('use_custom_password') == 'true': # The user has entered his own password and it was verified by # validators log.debug("Password change for user {!r} (custom password).".format(user.get_id())) new_password = passwords_data.get('custom_password') else: # If the user has selected the suggested password, then it should # be in session log.debug("Password change for user {!r} (suggested password).".format(user.get_id())) new_password = self.get_suggested_password() new_password = new_password.replace(' ', '') old_password = passwords_data['old_password'] # Load user from database to ensure we are working on an up-to-date set of credentials. # XXX this refresh is a bit redundant with the same thing being done in OldPasswordValidator. user = self.request.userdb.get_user_by_oid(user.get_id()) user.retrieve_modified_ts(self.request.db.profiles) self.changed = change_password(self.request, user, old_password, new_password) if self.changed: message = 'success|' + _('Your password has been successfully updated') else: message = 'error|' + _('An error has occured while updating your password, ' 'please try again or contact support if the problem persists.') self.request.session.flash(message) raise HTTPFound(self.context.route_url('profile-editor'))
def revoke_all_credentials(vccs_url, user): vccs = get_vccs_client(vccs_url) passwords = user.credentials.filter(Password).to_list() to_revoke = [] for passwd in passwords: credential_id = str(passwd.credential_id) factor = vccs_client.VCCSRevokeFactor( credential_id, 'subscriber requested termination', reference='dashboard' ) log.debug("Revoked old credential (account termination)" " {!s} (user {!r})".format( credential_id, user)) to_revoke.append(factor) userid = str(user.user_id) vccs.revoke_credentials(userid, to_revoke)
def _verify_action(self, data_id, post_data): if 'code' in post_data: code_sent = post_data['code'] verification_code = get_verification_code(self.request, self.data_attribute, obj_id=data_id, code=code_sent, user=self.user) if verification_code: if code_sent == verification_code['code']: if verification_code['expired']: log.debug( "User {!r} verification code has expired".format( self.user)) return { 'result': 'error', 'message': self.verify_messages['expired'], } else: try: verify_code(self.request, self.data_attribute, code_sent) except UserOutOfSync: self.sync_user() return { 'result': 'out_of_sync', 'message': self.verify_messages['out_of_sync'], } return { 'result': 'success', 'message': self.verify_messages['success'], } else: log.debug("Incorrect code for user {!r}: {!r}".format( self.user, code_sent)) return { 'result': 'error', 'message': self.verify_messages['error'], } else: message = self.verify_messages['request'].format(data=data_id) return { 'result': 'getcode', 'message': message, 'placeholder': self.verify_messages['placeholder'], }
def get_verification_code(request, model_name, obj_id=None, code=None, user=None): """ Match a user supplied code (`code') against an actual entry in the database. :param request: The HTTP request :param model_name: 'norEduPersonNIN', 'phone', or 'mailAliases' :param obj_id: The data covered by the verification, like the phone number or nin or ... :param code: User supplied code :param user: The user :type request: pyramid.request.Request :type model_name: str | unicode :type obj_id: str | unicode :type code: str | unicode :type user: User | OldUser :returns: Verification entry from the database :rtype: dict """ assert model_name in ['norEduPersonNIN', 'phone', 'mailAliases'] userid = None if user is not None: try: userid = user.user_id except AttributeError: userid = user.get_id() filters = { 'model_name': model_name, } if obj_id is not None: filters['obj_id'] = obj_id if code is not None: filters['code'] = code if userid is not None: filters['user_oid'] = userid log.debug("Verification code lookup filters : {!r}".format(filters)) result = request.db.verifications.find_one(filters) if result: expiration_timeout = request.registry.settings.get('verification_code_timeout') expire_limit = datetime.now(utc) - timedelta(minutes=int(expiration_timeout)) result['expired'] = result['timestamp'] < expire_limit log.debug("Verification lookup result : {!r}".format(result)) return result
def get_verification_code(request, model_name, obj_id=None, code=None, user=None): filters = { 'model_name': model_name, } if obj_id is not None: filters['obj_id'] = obj_id if code is not None: filters['code'] = code if user is not None: filters['user_oid'] = user.get_id() log.debug("Verification code lookup filters : {!r}".format(filters)) result = request.db.verifications.find_one(filters) if result: expiration_timeout = request.registry.settings.get('verification_code_timeout') expire_limit = datetime.now(utc) - timedelta(minutes=int(expiration_timeout)) result['expired'] = result['timestamp'] < expire_limit log.debug("Verification lookup result : {!r}".format(result)) return result
def new_reset_password_code(request, user, mechanism='email'): hash_code = get_unique_hash() date = datetime.now(pytz.utc) request.db.reset_passwords.remove({ 'email': user.get_mail() }) reference = request.db.reset_passwords.insert({ 'email': user.get_mail(), 'hash_code': hash_code, 'mechanism': mechanism, 'created_at': date, }, safe=True, manipulate=True) log.debug("New reset password code: {!s} via {!s} for user: {!r}.".format(hash_code, mechanism, user)) reset_password_link = request.route_url( "reset-password-step2", code=hash_code, ) return reference, reset_password_link
def authenticate(request, session_info): """ Locate a user using the identity found in the SAML assertion. :param request: Request object :param session_info: Session info received by pysaml2 client :returns: User :type request: Request() :type session_info: dict() :rtype: User or None """ if session_info is None: raise TypeError('Session info is None') user_main_attribute = request.registry.settings.get('saml2.user_main_attribute') attribute_values = get_saml_attribute(session_info, user_main_attribute) if not attribute_values: log.error('Could not find attribute {!r} in the SAML assertion'.format(user_main_attribute)) return None saml_user = attribute_values[0] # If user_main_attribute is eduPersonPrincipalName, there value might be scoped # and the scope (e.g. "@example.com") might have to be removed before looking # for the user in the database. strip_suffix = request.registry.settings.get('saml2.strip_saml_user_suffix') if strip_suffix: if saml_user.endswith(strip_suffix): saml_user = saml_user[:-len(strip_suffix)] log.debug('Looking for user with {!r} == {!r}'.format(user_main_attribute, saml_user)) try: user = request.userdb.get_user(saml_user) except request.userdb.exceptions.UserDoesNotExist: log.error('No user with {!r} = {!r} found'.format(user_main_attribute, saml_user)) except request.userdb.exceptions.MultipleUsersReturned: log.error("There are more than one user with {!r} = {!r}".format(user_main_attribute, saml_user)) else: user.retrieve_modified_ts(request.db.profiles) return user return None
def send_termination_mail(request, user): user = get_session_user(request) # XXX remove when context users are new users mailer = get_mailer(request) support_email = request.registry.settings.get('mail.support_email', '*****@*****.**') site_name = request.registry.settings.get("site.name", "eduID") context = { 'support_mail': support_email, 'displayName': user.display_name } if user.mail_addresses.primary is not None: address = user.mail_addresses.primary.email elif user.mail_addresses.count > 0: for a in user.mail_addresses.to_list(): address = a.email break else: log.info('User {!r} has no email address, not possible to send a message'.format(user)) return message = Message( subject=_("{site_name} account termination").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[address], body=render( "templates/termination_email.txt.jinja2", context, request, ), html=render( "templates/termination_email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug("Sent termination mail to user {!r} with address {!s}.".format(user, address)) request.stats.count('dashboard/email_send_termination_mail', 1)
def assertion_consumer_service(request): ''' ''' action = get_action(request.session) if 'SAMLResponse' not in request.POST: raise HTTPBadRequest("Couldn't find 'SAMLResponse' in POST data.") xmlstr = request.POST['SAMLResponse'] client = Saml2Client(request.saml2_config, identity_cache=IdentityCache(request.session)) oq_cache = OutstandingQueriesCache(request.session) outstanding_queries = oq_cache.outstanding_queries() try: # process the authentication response response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_POST, outstanding_queries) except AssertionError: log.error('SAML response is not verified') raise HTTPBadRequest( """SAML response is not verified. May be caused by the response was not issued at a reasonable time or the SAML status is not ok. Check the IDP datetime setup""") if response is None: log.error('SAML response is None') raise HTTPBadRequest( "SAML response has errors. Please check the logs") session_id = response.session_id() oq_cache.delete(session_id) # authenticate the remote user session_info = response.session_info() log.debug('Trying to locate the user authenticated by the IdP') log.debug('Session info:\n{!s}\n\n'.format(pprint.pformat(session_info))) user = authenticate(request, session_info) if user is None: log.error('Could not find the user identified by the IdP') raise HTTPUnauthorized("Access not authorized") return action(request, session_info, user)
def send_verification_mail(request, email, reference=None, code=None): mailer = get_mailer(request) if code is None or reference is None: reference, code = new_verification_code(request, 'mailAliases', email, request.context.user, hasher=get_short_hash) verification_link = generate_verification_link(request, code, 'mailAliases') site_name = request.registry.settings.get("site.name", "eduID") context = { "email": email, "verification_link": verification_link, "site_url": request.context.safe_route_url("home"), "site_name": site_name, "code": code, } message = Message( subject=_("{site_name} confirmation email").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[email], body=render( "templates/verification_email.txt.jinja2", context, request, ), html=render( "templates/verification_email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug("Sent verification mail to user {!r} with address {!s}.".format(request.context.user, email)) request.stats.count('dashboard/email_send_verification_code', 1)
def assertion_consumer_service(request): ''' ''' action = get_action(request.session) if sanitize_post_key(request, 'SAMLResponse') is None: raise HTTPBadRequest("Couldn't find 'SAMLResponse' in POST data.") xmlstr = request.POST['SAMLResponse'] session_info = get_authn_response(request.registry.settings, request.session, xmlstr) log.debug('Trying to locate the user authenticated by the IdP') user = authenticate(request, session_info) if user is None: log.error('Could not find the user identified by the IdP') raise HTTPUnauthorized("Access not authorized") return action(request, session_info, user)
def new_verification_code(request, model_name, obj_id, user, hasher=None): """ Match a user supplied code (`code') against an actual entry in the database. :param request: The HTTP request :param model_name: 'norEduPersonNIN', 'phone', or 'mailAliases' :param obj_id: The data covered by the verification, like the phone number or nin or ... :param user: The user :param hasher: Callable used to generate the code :type request: pyramid.request.Request :type model_name: str | unicode :type obj_id: str | unicode :type user: User | OldUser :type hasher: callable """ assert model_name in ['norEduPersonNIN', 'phone', 'mailAliases'] try: userid = user.user_id except AttributeError: userid = user.get_id() if hasher is None: hasher = get_unique_hash code = hasher() obj = { 'model_name': model_name, 'obj_id': obj_id, 'user_oid': userid, 'code': code, 'verified': False, 'timestamp': datetime.now(utc), } doc_id = request.db.verifications.insert(obj) reference = unicode(doc_id) session_verifications = request.session.get('verifications', []) session_verifications.append(code) request.session['verifications'] = session_verifications log.info('Created new {!s} verification code for user {!r}.'.format(model_name, user)) log.debug('Verification object id {!s}. Code: {!s}.'.format(obj_id, code)) return reference, code
def send_termination_mail(request, user): mailer = get_mailer(request) support_email = request.registry.settings.get('mail.support_email', '*****@*****.**') site_name = request.registry.settings.get("site.name", "eduID") context = {'support_mail': support_email, 'displayName': user.display_name} if user.mail_addresses.primary is not None: address = user.mail_addresses.primary.email elif user.mail_addresses.count > 0: address = user.mail_addresses.to_list()[0].email else: log.info( 'User {!r} has no email address, not possible to send a message'. format(user)) return message = Message( subject=_("{site_name} account termination").format( site_name=site_name), sender=request.registry.settings.get("mail.default_sender"), recipients=[address], body=render( "templates/termination_email.txt.jinja2", context, request, ), html=render( "templates/termination_email.html.jinja2", context, request, ), ) # DEBUG if request.registry.settings.get('developer_mode', False): log.debug(message.body) else: mailer.send(message) log.debug("Sent termination mail to user {!r} with address {!s}.".format( user, address)) request.stats.count('email_send_termination_mail')
def new_verification_code(request, model_name, obj_id, user, hasher=None): if hasher is None: hasher = get_unique_hash code = hasher() obj = { "model_name": model_name, "obj_id": obj_id, "user_oid": user.get_id(), "code": code, "verified": False, "timestamp": datetime.now(utc), } doc_id = request.db.verifications.insert(obj) reference = unicode(doc_id) session_verifications = request.session.get('verifications', []) session_verifications.append(code) request.session['verifications'] = session_verifications log.info('Created new {!s} verification code for user {!r}.'.format(model_name, user)) log.debug('Verification object id {!s}. Code: {!s}.'.format(obj_id, code)) return reference, code
def verifications(context, request): model_name = request.matchdict['model'] code = request.matchdict['code'] verification = get_verification_code(request, model_name, code=code) if verification and verification['expired']: log.debug("Verification code is expired: {!r}".format(verification)) raise HTTPNotFound() # the code is expired if code not in request.session.get('verifications', []): log.debug("Code {!r} not found in active sessions verifications: {!r}". format(code, request.session.get('verifications', []))) raise HTTPNotFound(_("Can't locate the code in the active session")) try: obj_id = verify_code(request, model_name, code) except UserOutOfSync: msg = _('Your user profile is out of sync. Please ' 'reload the page and try again.') msg = get_localizer(request).translate(msg) request.session.flash(msg), raise HTTPFound(request.context.route_url('profile-editor')) if obj_id is not None: request.stats.count('verification_{!s}_ok'.format(model_name)) return HTTPFound(location=request.route_url('home')) else: log.debug("Incorrect verification code {!r} for model {!r}".format( code, model_name)) request.stats.count('verification_{!s}_fail'.format(model_name)) raise HTTPNotFound()
def set_nin_verified(request, user, new_nin, reference=None): """ Mark a National Identity Number (NIN) as verified on a user. This process also includes *removing* the NIN from any other user that had it as a verified NIN. :param request: The HTTP request :param user: The user :param new_nin: The National Identity Number to mark as verified :param reference: A reference to the verification code - used for audit logging :type request: pyramid.request.Request :type user: User :type new_nin: str | unicode :return: Status message :rtype: str | unicode """ log.info('Trying to verify NIN for user {!r}.'.format(user)) log.debug('NIN: {!s}.'.format(new_nin)) # Start by removing nin from any other user old_user = request.userdb_new.get_user_by_nin(new_nin, raise_on_missing=False) log.debug('Searched for NIN {!r} in {!s}: {!r}'.format(new_nin, request.userdb_new, old_user)) steal_count = 0 if old_user and old_user.user_id != user.user_id: retrieve_modified_ts(old_user, request.dashboard_userdb) _remove_nin_from_user(new_nin, old_user) request.context.save_dashboard_user(old_user) log.info('Removed NIN and associated addresses from user {!r}.'.format(old_user)) steal_count = 1 # Add the verified nin to the requesting user _add_nin_to_user(new_nin, user) _nin_verified_transaction_audit(request, reference) log.info('NIN verified for user {!r}.'.format(user)) request.stats.count('verify_nin_stolen', steal_count) request.stats.count('verify_nin_completed') return _('National identity number {obj} verified')
def _remove_nin_from_user(nin, user): """ Remove a NIN from one user because it is being verified by another user. Part of set_nin_verified() above. """ log.debug('Found old user {!r} with NIN ({!s}) already verified.'.format(user, nin)) log.debug('Old user NINs BEFORE: {!r}.'.format(user.nins.to_list())) if user.nins.primary.number == nin: old_nins = user.nins.verified.to_list() for this in old_nins: if this.number != nin: user.nins.primary = this.number break user.nins.remove(nin) log.debug('Old user NINs AFTER: {!r}.'.format(user.nins.to_list())) return user
def _remove_phone_from_user(number, user): """ Remove a phone number from one user because it is being verified by another user. Part of set_phone_verified() above. """ log.debug('Found old user {!r} with phone number ({!s}) already verified.'.format(user, number)) log.debug('Old user phone numbers BEFORE: {!r}.'.format(user.phone_numbers.to_list())) if user.phone_numbers.primary.number == number: # Promote some other verified phone number to primary for phone in user.phone_numbers.verified.to_list(): if phone.number != number: user.phone_numbers.primary = phone.number break user.phone_numbers.remove(number) log.debug('Old user phone numbers AFTER: {!r}.'.format(user.phone_numbers.to_list())) return user
def finish_letter_action(self, data, post_data): """ Contact the eduid-idproofing-letter service and give it the code the user supplied. If the letter proofing service approves of the code, this code does the following: * Put together some LetterProofing data with information about the user, the vetting, the users registered address etc. (Kantara requirement) * Log what the letter proofing service returned on the user (we put it there for now...) * Upgrade the NIN in question to verified=True * Mark the verification code as used :returns: status, message in a dict :rtype: dict """ nin, index = data.split() index = int(index) settings = self.request.registry.settings letter_url = settings.get('letter_service_url') verify_letter_url = urlappend(letter_url, 'verify-code') code = post_data['verification_code'] self.user = get_session_user(self.request) # small helper function to make rest of the function more readable def make_result(result, msg): return dict(result = result, message = msg) data = {'eppn': self.user.eppn, 'verification_code': code} logger.info("Posting letter verification code for user {!r}.".format(self.user)) response = requests.post(verify_letter_url, data=data) logger.info("Received response from idproofing-letter after posting verification code " "for user {!r}.".format(self.user)) if response.status_code != 200: # Do nothing, just return above error message and log microservice return code logger.info("Received status code {!s} from idproofing-letter after posting verification code " "for user {!r}.".format(response.status_code, self.user)) return make_result('error', _('There was a problem with the letter service. ' 'Please try again later.')) rdata = response.json().get('data', {}) if not (rdata.get('verified', False) and nin == rdata.get('number', None)): log.info('User {!r} supplied wrong letter verification code or nin did not match.'.format( self.user)) log.debug('NIN in dashboard: {!s}, NIN in idproofing-letter: {!s}'.format( nin, rdata.get('number', None))) return make_result('error', _('Your verification code seems to be wrong, please try again.')) # Save data from successful verification call for later addition to user proofing collection. # Convert self.user to a DashboardUser manually instead of letting save_dashboard_user do # it to get access to add_letter_proofing_data(). user = DashboardUser(data = self.user.to_dict()) rdata['created_ts'] = datetime.utcfromtimestamp(int(rdata['created_ts'])) rdata['verified_ts'] = datetime.utcfromtimestamp(int(rdata['verified_ts'])) user.add_letter_proofing_data(rdata) # Look up users official address at the time of verification per Kantara requirements logger.info("Looking up address via Navet for user {!r}.".format(self.user)) user_postal_address = self.request.msgrelay.get_full_postal_address(rdata['number']) logger.info("Finished looking up address via Navet for user {!r}.".format(self.user)) proofing_data = LetterProofing(self.user, rdata['number'], rdata['official_address'], rdata['transaction_id'], user_postal_address) # Log verification event and fail if that goes wrong logger.info("Logging proofing data for user {!r}.".format(self.user)) if not self.request.idproofinglog.log_verification(proofing_data): log.error('Logging of letter proofing data for user {!r} failed.'.format(self.user)) return make_result('error', _('Sorry, we are experiencing temporary technical ' 'problems, please try again later.')) logger.info("Finished logging proofing data for user {!r}.".format(self.user)) # This is a hack to reuse the existing proofing functionality, the users code has # already been verified by the micro service but we decided the dashboard could # continue 'upgrading' the users until we've made the planned proofing consumer set_nin_verified(self.request, user, nin) try: self.request.context.save_dashboard_user(user) except UserOutOfSync: log.error("Verified norEduPersonNIN NOT saved for user {!r}. User out of sync.".format( self.user)) return self.sync_user() self.user = user # Finally mark the verification as used save_as_verified(self.request, 'norEduPersonNIN', self.user, nin) logger.info("Verified NIN by physical letter saved for user {!r}.".format( self.user)) return make_result('success', _('You have successfully verified your identity'))
def validate_nin_by_mobile(request, user, nin): """ :param request: The pyramid request :param user: The eduid user :param nin: The NIN :type request: pyramid.request.Request :type user: eduid_userdb.User :type nin: str :return:dict """ log.info( 'Trying to verify nin via mobile number for user {!r}.'.format(user)) log.debug('NIN: {!s}.'.format(nin)) from eduid_lookup_mobile.utilities import format_NIN # Get list of verified mobile numbers verified_mobiles = [ x.number for x in user.phone_numbers.to_list() if x.is_verified ] national_identity_number = format_NIN(nin) status = 'no_phone' valid_mobile = None registered_to_nin = None age = _get_age(national_identity_number) try: for mobile_number in verified_mobiles: status = 'no_match' # Get the registered owner of the mobile number registered_to_nin = request.lookuprelay.find_NIN_by_mobile( mobile_number) registered_to_nin = format_NIN(registered_to_nin) if registered_to_nin == national_identity_number: # Check if registered nin was the given nin valid_mobile = mobile_number status = 'match' log.info('Mobile number matched for user {!r}.'.format(user)) log.debug('Mobile {!s} registered to NIN: {!s}.'.format( valid_mobile, registered_to_nin)) request.stats.count('validate_nin_by_mobile_exact_match') break elif registered_to_nin is not None and age < 18: # Check if registered nin is related to given nin relation = request.msgrelay.get_relations_to( national_identity_number, registered_to_nin) # TODO All relations? #valid_relations = ['M', 'B', 'FA', 'MO', 'VF'] valid_relations = ['FA', 'MO'] if any(r in relation for r in valid_relations): valid_mobile = mobile_number status = 'match_by_navet' log.info('Mobile number matched for user {!r} via navet.'. format(user)) log.debug('Mobile {!s} registered to NIN: {!s}.'.format( valid_mobile, registered_to_nin)) log.debug( 'Person with NIN {!s} have relation {!s} to user: {!r}.' .format(registered_to_nin, relation, user)) request.stats.count( 'validate_nin_by_mobile_relative_match') break except request.lookuprelay.TaskFailed: status = 'error_lookup' except request.msgrelay.TaskFailed: status = 'error_navet' msg = None if status == 'no_phone': msg = _('You have no confirmed mobile phone') log.info('User {!r} has no verified mobile phone number.'.format(user)) elif status == 'no_match': log.info( 'User {!r} NIN is not associated with any verified mobile phone number.' .format(user)) msg = _( 'A company subscription or protected phone number cannot be used with this service.' ) request.stats.count('validate_nin_by_mobile_no_match') elif status == 'error_lookup' or status == 'error_navet': log.error( 'Validate NIN via mobile failed with status "{!s}" for user {!r}.'. format(status, user)) msg = _('Sorry, we are experiencing temporary technical ' 'problem with ${service_name}, please try again ' 'later.') request.stats.count('validate_nin_by_mobile_error') if status == 'match' or status == 'match_by_navet': log.info( 'Validate NIN via mobile succeeded with status "{!s}" for user {!r}.' .format(status, user)) msg = _('Validate NIN via mobile with succeeded') user_postal_address = request.msgrelay.get_full_postal_address( national_identity_number) if status == 'match': proofing_data = TeleAdressProofing(user, status, national_identity_number, valid_mobile, user_postal_address) else: registered_postal_address = request.msgrelay.get_full_postal_address( registered_to_nin) proofing_data = TeleAdressProofingRelation( user, status, national_identity_number, valid_mobile, user_postal_address, registered_to_nin, relation, registered_postal_address) log.info('Logging of mobile proofing data for user {!r}.'.format(user)) if not request.idproofinglog.log_verification(proofing_data): log.error( 'Logging of mobile proofing data for user {!r} failed.'.format( user)) valid_mobile = None msg = _('Sorry, we are experiencing temporary technical ' 'problem with ${service_name}, please try again ' 'later.') validation_result = { 'success': valid_mobile is not None, 'message': msg, 'mobile': valid_mobile } return validation_result
def _remove_mail_from_user(email, user): """ Remove an email address from one user because it is being verified by another user. Part of set_email_verified() above. """ log.debug('Removing mail address {!s} from user {!s}'.format(email, user)) if user.mail_addresses.primary: # only in the test suite could primary ever be None here log.debug('Old user mail BEFORE: {!s}'.format(user.mail_addresses.primary)) log.debug('Old user mail aliases BEFORE: {!r}'.format(user.mail_addresses.to_list())) if user.mail_addresses.primary and user.mail_addresses.primary.email == email: # Promote some other verified e-mail address to primary for address in user.mail_addresses.to_list(): if address.is_verified and address.email != email: user.mail_addresses.primary = address.email break user.mail_addresses.remove(email) if user.mail_addresses.primary is not None: log.debug('Old user mail AFTER: {!s}.'.format(user.mail_addresses.primary)) if user.mail_addresses.count > 0: log.debug('Old user mail aliases AFTER: {!r}.'.format(user.mail_addresses.to_list())) else: log.debug('Old user has NO mail AFTER.') return user
def add_credentials(vccs_url, old_password, new_password, user): """ Add a new password to a user. Revokes the old one, if one is given. Returns True on success. :param vccs_url: URL to VCCS authentication backend :param old_password: plaintext current password :param new_password: plaintext new password :param user: user object :type vccs_url: string :type old_password: string :type user: OldUser :rtype: bool """ password_id = ObjectId() vccs = get_vccs_client(vccs_url) new_factor = vccs_client.VCCSPasswordFactor(new_password, credential_id=str(password_id)) if isinstance(user, DashboardLegacyUser): user = DashboardUser(data=user._mongo_doc) old_factor = None checked_password = None # remember if an old password was supplied or not, without keeping it in # memory longer than we have to old_password_supplied = bool(old_password) if user.passwords.count > 0 and old_password: # Find the old credential to revoke checked_password = check_password(vccs_url, old_password, user, vccs=vccs) del old_password # don't need it anymore, try to forget it if not checked_password: return False old_factor = vccs_client.VCCSRevokeFactor( str(checked_password.credential_id), 'changing password', reference='dashboard', ) if not vccs.add_credentials(str(user.user_id), [new_factor]): log.warning( "Failed adding password credential {!r} for user {!r}".format( new_factor.credential_id, user)) return False # something failed log.debug("Added password credential {!s} for user {!s}".format( new_factor.credential_id, user)) if old_factor: vccs.revoke_credentials(str(user.user_id), [old_factor]) user.passwords.remove(checked_password.key) log.debug("Revoked old credential {!s} (user {!s})".format( old_factor.credential_id, user)) if not old_password_supplied: # TODO: Revoke all current credentials on password reset for now revoked = [] for password in user.credentials.filter(Password).to_list(): revoked.append( vccs_client.VCCSRevokeFactor(str(password.credential_id), 'reset password', reference='dashboard')) log.debug( "Revoking old credential (password reset) {!s} (user {!r})". format(password.credential_id, user)) user.passwords.remove(password.key) if revoked: try: vccs.revoke_credentials(str(user.user_id), revoked) except vccs_client.VCCSClientHTTPError: # Password already revoked # TODO: vccs backend should be changed to return something more informative than # TODO: VCCSClientHTTPError when the credential is already revoked or just return success. log.warning( "VCCS failed to revoke all passwords for user {!s}".format( user)) pass new_password = Password( credential_id=password_id, salt=new_factor.salt, application='dashboard', ) user.passwords.add(new_password) return True
def dummy_message(request, message): """ This function is only for debugging purposes """ log.debug('[DUMMY_MESSAGE]: {!s}'.format(message))