def locale_negotiator(request): # Import here to avoid circular dependencies from eduiddashboard.utils import sanitize_cookies_get settings = request.registry.settings available_languages = settings['available_languages'].keys() cookie_name = settings['lang_cookie_name'] cookie_lang = sanitize_cookies_get(request, cookie_name, None) if cookie_lang and cookie_lang in available_languages: return cookie_lang user = get_session_user(request, legacy_user=False, raise_on_not_logged_in=False) if user: preferredLanguage = user.language if preferredLanguage: return preferredLanguage locale_name = request.accept_language.best_match(available_languages) if locale_name not in available_languages: locale_name = settings.get('default_locale_name', 'sv') return locale_name
def setprimary_action(self, index, post_data): self.user = get_session_user(self.request) try: mail = self.user.mail_addresses.to_list()[index] except IndexError: return self.sync_user() if not mail.is_verified: message = _('You need to confirm your email address ' 'before it can become primary') return { 'result': 'bad', 'message': get_localizer(self.request).translate(message), } self.user.mail_addresses.primary = mail.email try: self.context.save_dashboard_user(self.user) except UserOutOfSync: return self.sync_user() self.request.stats.count('email_set_primary') message = _('Your primary email address was ' 'successfully changed') return { 'result': 'success', 'message': get_localizer(self.request).translate(message) }
def add_success(self, emailform): newemail = self.schema.serialize(emailform) new_email = MailAddress(email=newemail['mail'], application='dashboard', verified=False, primary=False) self.user = get_session_user(self.request) self.user.mail_addresses.add(new_email) try: self.context.save_dashboard_user(self.user) except UserOutOfSync: self.sync_user() else: message = _('Changes saved') self.request.session.flash(get_localizer( self.request).translate(message), queue='forms') send_verification_mail(self.request, newemail['mail']) second_msg = _( 'A confirmation email has been sent to your email ' 'address. Please enter your confirmation code ' '<a href="#" class="verifycode" ' 'data-identifier="${id}">here</a>.', mapping={'id': self.user.mail_addresses.count}) self.request.session.flash(get_localizer( self.request).translate(second_msg), queue='forms')
def remove_action(self, index, post_data): self.user = get_session_user(self.request) emails = self.user.mail_addresses.to_list() if len(emails) == 1: message = _('Error: You only have one email address and it ' 'can not be removed') return { 'result': 'error', 'message': get_localizer(self.request).translate(message), } try: remove_email = emails[index].email except IndexError: return self.sync_user() try: self.user.mail_addresses.remove(remove_email) except PrimaryElementViolation: new_index = 0 if index != 0 else 1 self.user.mail_addresses.primary = emails[new_index].email self.user.mail_addresses.remove(remove_email) try: self.context.save_dashboard_user(self.user) except UserOutOfSync: return self.sync_user() self.request.stats.count('email_removed') message = _('Email address was successfully removed') return { 'result': 'success', 'message': get_localizer(self.request).translate(message), }
def verify_action(self, index, post_data): """ Common action to verify some given data. You can override in subclasses """ self.user = get_session_user(self.request) # Catch the unlikely event when the user have e.g. removed all entries # in a separate tab, or one in the middle and then tries to resend the # code for a non-existing entry. # This is an incomplete fix since it is better if we can get the list # from the UI and then check that the entry which the client want to # resend the code for corresponds to the same entry we get from # data[index]. try: _data = self.user.to_dict().get(self.data_attribute, []) data_to_verify = _data[index] except IndexError: log.warning('Index error in verify_action, user {!s}'.format( self.user)) message = self.verify_messages['out_of_sync'] return { 'result': 'out_of_sync', 'message': get_localizer(self.request).translate(message), } data_id = self.get_verification_data_id(data_to_verify) return self._verify_action(data_id, post_data)
def __call__(self, node, value): """ Validator which makes sure that: 1. the NiN has not already been added by the user 2. the user does not already have a confirmed NiN. """ from eduiddashboard.models import normalize_nin value = normalize_nin(copy(value)) request = node.bindings.get('request') user = get_session_user(request) user_nins = user.nins unverified_user_nins = request.db.verifications.find({ 'obj_id': value, 'model_name': 'norEduPersonNIN', 'user_oid': user.user_id, 'verified': False }) # Search the request.POST for any post that starts with "add". for post_value in request.POST: if post_value.startswith('add') and (user_nins.find(value) or unverified_user_nins.count() > 0): err = _('National identity number already added') raise colander.Invalid(node, get_localizer(request).translate(err)) elif post_value.startswith('add') and user_nins.count > 0: err = _('You already have a confirmed national identity number') raise colander.Invalid(node, get_localizer(request).translate(err))
def verify_action(self, index, post_data): """ Common action to verify some given data. You can override in subclasses """ self.user = get_session_user(self.request) # Catch the unlikely event when the user have e.g. removed all entries # in a separate tab, or one in the middle and then tries to resend the # code for a non-existing entry. # This is an incomplete fix since it is better if we can get the list # from the UI and then check that the entry which the client want to # resend the code for corresponds to the same entry we get from # data[index]. try: _data = self.user.to_dict().get(self.data_attribute, []) data_to_verify = _data[index] except IndexError: log.warning('Index error in verify_action, user {!s}'.format(self.user)) message = self.verify_messages['out_of_sync'] return { 'result': 'out_of_sync', 'message': get_localizer(self.request).translate(message), } data_id = self.get_verification_data_id(data_to_verify) return self._verify_action(data_id, post_data)
def add_success(self, emailform): newemail = self.schema.serialize(emailform) new_email = MailAddress(email=newemail['mail'], application='dashboard', verified=False, primary=False) self.user = get_session_user(self.request) self.user.mail_addresses.add(new_email) try: self.context.save_dashboard_user(self.user) except UserOutOfSync: self.sync_user() else: message = _('Changes saved') self.request.session.flash(get_localizer(self.request).translate(message), queue='forms') send_verification_mail(self.request, newemail['mail']) second_msg = _('A confirmation email has been sent to your email ' 'address. Please enter your confirmation code ' '<a href="#" class="verifycode" ' 'data-identifier="${id}">here</a>.', mapping={'id': self.user.mail_addresses.count}) self.request.session.flash(get_localizer(self.request).translate(second_msg), queue='forms')
def setprimary_action(self, index, post_data): self.user = get_session_user(self.request) try: mail = self.user.mail_addresses.to_list()[index] except IndexError: return self.sync_user() if not mail.is_verified: message = _('You need to confirm your email address ' 'before it can become primary') return { 'result': 'bad', 'message': get_localizer(self.request).translate(message), } self.user.mail_addresses.primary = mail.email try: self.context.save_dashboard_user(self.user) except UserOutOfSync: return self.sync_user() self.request.stats.count('dashboard/email_set_primary', 1) message = _('Your primary email address was ' 'successfully changed') return {'result': 'success', 'message': get_localizer(self.request).translate(message)}
def remove_action(self, index, post_data): self.user = get_session_user(self.request) emails = self.user.mail_addresses.to_list() if len(emails) == 1: message = _('Error: You only have one email address and it ' 'can not be removed') return { 'result': 'error', 'message': get_localizer(self.request).translate(message), } try: remove_email = emails[index].email except IndexError: return self.sync_user() try: self.user.mail_addresses.remove(remove_email) except PrimaryElementViolation: new_index = 0 if index != 0 else 1 self.user.mail_addresses.primary = emails[new_index].email self.user.mail_addresses.remove(remove_email) try: self.context.save_dashboard_user(self.user) except UserOutOfSync: return self.sync_user() self.request.stats.count('dashboard/email_removed', 1) message = _('Email address was successfully removed') return { 'result': 'success', 'message': get_localizer(self.request).translate(message), }
def add_success(self, mobileform): mobile_number = self.schema.serialize(mobileform)['mobile'] mobile_number = normalize_to_e_164(self.request, mobile_number) mobile = PhoneNumber( data={ 'number': mobile_number, 'verified': False, 'primary': False, 'created_ts': datetime.utcnow() }) self.user = get_session_user(self.request) self.user.phone_numbers.add(mobile) try: self.context.save_dashboard_user(self.user) except UserOutOfSync: self.sync_user() else: send_verification_code(self.request, self.user, mobile_number) self.request.session.flash(_('Changes saved'), queue='forms') msg = _( 'A confirmation code has been sent to your mobile phone. ' 'Please click on the "Pending confirmation" link below and enter your confirmation code.' ) msg = get_localizer(self.request).translate(msg) self.request.session.flash(msg, queue='forms')
def get_available_tabs(context, request): from eduiddashboard.views import (emails, personal, mobiles, nins, permissions, get_dummy_status) default_tabs = [ personal.get_tab(request), nins.get_tab(request), emails.get_tab(request), permissions.get_tab(request), mobiles.get_tab(request), # postal_address.get_tab(request), { 'label': _('Security'), 'status': get_dummy_status, 'id': 'security', }, ] if context.workmode == 'personal': tabs = filter_tabs(default_tabs, ['permissions']) elif context.workmode == 'helpdesk': tabs = filter_tabs(default_tabs, ['passwords', 'authorization']) else: tabs = default_tabs # Import here to avoid circular import from eduiddashboard.session import get_session_user user = get_session_user(request, legacy_user = False, raise_on_not_logged_in = False) for tab in tabs: tab['status'] = tab['status'](request, user) return tabs
def add_success(self, mobileform): mobile_number = self.schema.serialize(mobileform)['mobile'] mobile_number = normalize_to_e_164(self.request, mobile_number) mobile = PhoneNumber(data={'number': mobile_number, 'verified': False, 'primary': False, 'created_ts': datetime.utcnow() }) self.user = get_session_user(self.request) self.user.phone_numbers.add(mobile) try: self.context.save_dashboard_user(self.user) except UserOutOfSync: self.sync_user() else: send_verification_code(self.request, self.user, mobile_number) self.request.session.flash(_('Changes saved'), queue='forms') msg = _('A confirmation code has been sent to your mobile phone. ' 'Please click on the "Pending confirmation" link below and enter your confirmation code.') msg = get_localizer(self.request).translate(msg) self.request.session.flash(msg, queue='forms')
def setprimary_action(self, index, post_data): self.user = get_session_user(self.request) mobiles = self.user.phone_numbers.to_list() try: mobile = mobiles[index] except IndexError: return self.sync_user() if not mobile.is_verified: message = _('You need to confirm your mobile number ' 'before it can become primary') return { 'result': 'bad', 'message': get_localizer(self.request).translate(message), } self.user.phone_numbers.primary = mobile.number try: self.context.save_dashboard_user(self.user) except UserOutOfSync: return self.sync_user() self.request.stats.count('mobile_number_set_primary') message = _('Mobile phone number was successfully made primary') return { 'result': 'success', 'message': get_localizer(self.request).translate(message), }
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 }] """ user = get_session_user(request) authninfo = [] for credential in user.credentials.filter(Password).to_list(): auth_entry = request.authninfodb.authn_info.find_one( {'_id': ObjectId(credential.credential_id)}) log.debug("get_authn_info {!s}: cred id: {!r} auth entry: {!r}".format( user, credential.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 save_success(self, user_modified): person = self.schema.serialize(user_modified) del (person['csrf']) # Don't save the CSRF token in the user database self.user = get_session_user(self.request) new_preferred_language = person.get('preferredLanguage') old_preferred_language = self.user.language # Insert the new/updated user object self.user.given_name = person.get('givenName') self.user.display_name = person.get('displayName') self.user.surname = person.get('surname') self.user.language = person.get('preferredLanguage') try: self.context.save_dashboard_user(self.user) except UserOutOfSync: self.sync_user() else: message = _('Changes saved') self.request.session.flash(get_localizer( self.request).translate(message), queue='forms') self.request.stats.count('personal_data_saved') if new_preferred_language != old_preferred_language: self.full_page_reload()
def verify_action(self, data, post_data): """ Only the active (the last one) NIN can be verified """ nin, index = data.split() index = int(index) self.user = get_session_user(self.request, raise_on_not_logged_in = False) nins = get_not_verified_nins_list(self.request, self.user) if len(nins) > index: new_nin = nins[index] if new_nin != nin: return self.sync_user() else: return self.sync_user() # if index != len(nins) - 1: # message = _("The provided nin can't be verified. You only " # 'can verify the last one') # return { # 'result': 'bad', # 'message': get_localizer(self.request).translate(message), # } return self._verify_action(new_nin, post_data)
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 __init__(self, context, request): super(BaseFormView, self).__init__(request) self.user = get_session_user(self.request) self.context = context self.response = request.response # Stop Internet Explorer from caching view fragments self.response.headers['Cache-Control'] = 'no-cache' self.classname = self.__class__.__name__.lower() self.ajax_options = json.dumps({ 'replaceTarget': True, 'url': context.route_url(self.route), 'target': "div.{classname}-form-container".format(classname=self.classname), }) self.form_options = { 'formid': "{classname}-form".format(classname=self.classname), 'action': context.route_url(self.route), } bootstrap_form_style = getattr(self, 'bootstrap_form_style', None) if bootstrap_form_style is not None: self.form_options['bootstrap_form_style'] = bootstrap_form_style
def resend_code_action(self, index, post_data): self.user = get_session_user(self.request) data = self.user.to_dict().get(self.data_attribute, []) # Catch the unlikely event when the user have e.g. removed all entries # in a separate tab, or one in the middle and then tries to resend the # code for a non-existing entry. # This is an incomplete fix since it is better if we can get the list # from the UI and then check that the entry which the client want to # resend the code for corresponds to the same entry we get from # data[index]. try: data_to_resend = data[index] except IndexError: log.warning('Index error in resend_code_action, user {!s}'.format(self.user)) message = self.verify_messages['out_of_sync'] return { 'result': 'out_of_sync', 'message': get_localizer(self.request).translate(message), } data_id = self.get_verification_data_id(data_to_resend) reference, code = new_verification_code( self.request, self.data_attribute, data_id, self.user, hasher=get_short_hash, ) self.send_verification_code(data_id, reference, code) msg = self.verify_messages['new_code_sent'] return { 'result': 'success', 'message': msg, }
def save_success(self, user_modified): person = self.schema.serialize(user_modified) del(person['csrf']) # Don't save the CSRF token in the user database self.user = get_session_user(self.request) new_preferred_language = person.get('preferredLanguage') old_preferred_language = self.user.language # Insert the new/updated user object self.user.given_name = person.get('givenName') self.user.display_name = person.get('displayName') self.user.surname = person.get('surname') self.user.language = person.get('preferredLanguage') try: self.context.save_dashboard_user(self.user) except UserOutOfSync: self.sync_user() else: message = _('Changes saved') self.request.session.flash( get_localizer(self.request).translate(message), queue='forms') self.request.stats.count('personal_data_saved') if new_preferred_language != old_preferred_language: self.full_page_reload()
def get_user(self): """ Get the current user. In 'personal' mode, this is the logged in user. In 'admin' mode, it is the 'edit-user' user from the session, or the user indicated by the matchdict 'userid'. Matchdict is a part of the URL extracted by Pyramid with routes like '/users/{userid}/'. :return: User object :rtype: DashboardUser """ if self.workmode == 'personal': user = get_logged_in_user(self.request, legacy_user = False, raise_on_not_logged_in = False) self._logged_in_user = user elif self.workmode == 'admin' or self.workmode == 'helpdesk': if not has_edit_user(self.request): user = None userid = self.request.matchdict.get('userid', '') logger.debug('get_user() looking for user matching {!r}'.format(userid)) if EMAIL_RE.match(userid): user = self.request.userdb_new.get_user_by_mail(userid) elif OID_RE.match(userid): user = self.request.userdb_new.get_user_by_id(userid) if not user: raise HTTPNotFound() logger.debug('get_user() storing user {!r}'.format(user)) store_session_user(self.request, user, edit_user = True) user = get_session_user(self.request) else: raise NotImplementedError("Unknown workmode: {!s}".format(self.workmode)) return user
def __init__(self, context, request): super(BaseFormView, self).__init__(request) self.user = get_session_user(self.request) self.context = context self.response = request.response # Stop Internet Explorer from caching view fragments self.response.headers['Cache-Control'] = 'no-cache' self.classname = self.__class__.__name__.lower() self.ajax_options = json.dumps({ 'replaceTarget': True, 'url': context.route_url(self.route), 'target': "div.{classname}-form-container".format( classname=self.classname), }) self.form_options = { 'formid': "{classname}-form".format(classname=self.classname), 'action': context.route_url(self.route), } bootstrap_form_style = getattr(self, 'bootstrap_form_style', None) if bootstrap_form_style is not None: self.form_options['bootstrap_form_style'] = bootstrap_form_style
def __call__(self, node, value): request = node.bindings.get('request') localizer = get_localizer(request) settings = request.registry.settings value = value.replace(" ", "") password_min_entropy = int(settings.get('password_entropy', 60)) # We accept a 10% of variance in password_min_entropy because # we have calculated the entropy by javascript too and the results # may vary. password_min_entropy = (0.90 * password_min_entropy) generated_password = request.session.get('last_generated_password', '') if len(generated_password) > 0 and generated_password == value: # Don't validate the password if it is the generated password # That is, the user has filled out the form with the suggested # password return # Get a users e-mail addresses to make sure a user does not use one of those as password user = get_session_user(request, raise_on_not_logged_in = False) if not user: # User is resetting a forgotten password hash_code = request.matchdict['code'] password_reset = request.db.reset_passwords.find_one({'hash_code': hash_code}) user = request.userdb_new.get_user_by_mail(password_reset['email']) mail_addresses = [item.email for item in user.mail_addresses.to_list()] veredict = zxcvbn.password_strength(value, user_inputs=mail_addresses) if veredict.get('entropy', 0) < password_min_entropy: err = _('The password complexity is too weak.') raise colander.Invalid(node, localizer.translate(err))
def remove_action(self, data, post_data): """ Only not verified nins can be removed """ raise HTTPNotImplemented # Temporary remove the functionality self.user = get_session_user(self.request) nin, index = data.split() index = int(index) nins = get_not_verified_nins_list(self.request, self.user) if len(nins) > index: remove_nin = nins[index] if remove_nin != nin: return self.sync_user() else: return self.sync_user() verifications = self.request.db.verifications verifications.remove({ 'model_name': self.data_attribute, 'obj_id': remove_nin, 'user_oid': self.user.user_id, 'verified': False, }) self.request.stats.count('dashboard/nin_remove', 1) message = _('National identity number has been removed') return { 'result': 'success', 'message': get_localizer(self.request).translate(message), }
def remove_action(self, data, post_data): """ Only not verified nins can be removed """ raise HTTPNotImplemented # Temporary remove the functionality self.user = get_session_user(self.request) nin, index = data.split() index = int(index) nins = get_not_verified_nins_list(self.request, self.user) if len(nins) > index: remove_nin = nins[index] if remove_nin != nin: return self.sync_user() else: return self.sync_user() verifications = self.request.db.verifications verifications.remove({ 'model_name': self.data_attribute, 'obj_id': remove_nin, 'user_oid': self.user.user_id, 'verified': False, }) self.request.stats.count('nin_remove') message = _('National identity number has been removed') return { 'result': 'success', 'message': get_localizer(self.request).translate(message), }
def get_template_context(self): tempcontext = super(PermissionsView, self).get_template_context() session_user = get_session_user(self.request, raise_on_not_logged_in = False) if self.context.user.eppn == session_user.eppn: tempcontext['confirmation_required'] = True else: tempcontext['confirmation_required'] = False return tempcontext
def get_template_context(self): context = {'intro_message': self.intro_message} # Collect the users mail addresses for use with zxcvbn user = get_session_user(self.request, raise_on_not_logged_in=False) if user: mail_addresses = [] for item in user.mail_addresses.to_list(): mail_addresses.append(item.email) context['user_input'] = json.dumps(mail_addresses) return context
def save_success(self, passwordform): authn_ts = self.request.session.get('reauthn-for-chpass', None) if authn_ts is None: raise HTTPBadRequest(_('No authentication info')) now = datetime.utcnow() delta = now - datetime.fromtimestamp(authn_ts) # XXX put the reauthn timeout in the settings 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')) user = get_session_user(self.request) log.debug('Removing Authn ts for user {!r} before' ' changing the password'.format(user)) del self.request.session['reauthn-for-chpass'] passwords_data = self.schema.serialize(passwordform) 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)) 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)) 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_new.get_user_by_id(user.user_id) log.debug("Refreshed user {!s} from {!s}".format( user, self.request.userdb_new)) retrieve_modified_ts(user, self.request.dashboard_userdb) 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 __call__(self, node, value): request = node.bindings.get('request') settings = request.registry.settings user = get_session_user(request) result = validate_nin_by_mobile(request, user, value) if not result['success']: localizer = get_localizer(request) raise colander.Invalid(node, localizer.translate(result['message'], mapping={ 'service_name': settings.get('mobile_service_name', 'TeleAdress'), }))
def get_template_context(self): context = { 'intro_message': self.intro_message } # Collect the users mail addresses for use with zxcvbn user = get_session_user(self.request, raise_on_not_logged_in = False) if user: mail_addresses = [] for item in user.mail_addresses.to_list(): mail_addresses.append(item.email) context['user_input'] = json.dumps(mail_addresses) return context
def save_success(self, permform): data = self.schema.serialize(permform) self.user = get_session_user(self.request, raise_on_not_logged_in = False) self.user.entitlements = data[self.attribute] try: self.context.save_dashboard_user(self.user) except UserOutOfSync: self.sync_user() else: message = _('Changes saved.') self.request.session.flash( get_localizer(self.request).translate(message), queue='forms')
def verify_mb_action(self, data, post_data): """ Verify a users identity using their mobile phone subscriber records. Only the active (the last one) NIN can be verified """ nin, index = data.split() index = int(index) nins = get_not_verified_nins_list(self.request, self.user) if len(nins) > index: new_nin = nins[index] if new_nin != nin: return self.sync_user() else: return self.sync_user() # if index != len(nins) - 1: # message = _("The provided nin can't be verified. You only " # 'can verify the last one') # return { # 'result': 'bad', # 'message': get_localizer(self.request).translate(message), # } validation = validate_nin_by_mobile(self.request, self.user, nin) result = validation['success'] and 'success' or 'error' model_name = 'norEduPersonNIN' if result == 'success': session_user = get_session_user(self.request, legacy_user = False) retrieve_modified_ts(session_user, self.request.dashboard_userdb) set_nin_verified(self.request, session_user, nin) try: #self.user.save(self.request) self.request.context.save_dashboard_user(session_user) logger.info("Verified by mobile, {!s} saved for user {!r}.".format(model_name, session_user)) # Save the state in the verifications collection save_as_verified(self.request, 'norEduPersonNIN', session_user, nin) except UserOutOfSync: logger.info("Verified {!s} NOT saved for user {!r}. User out of sync.".format(model_name, session_user)) raise settings = self.request.registry.settings msg = get_localizer(self.request).translate(validation['message'], mapping={ 'service_name': settings.get('mobile_service_name', 'Navet'), }) return { 'result': result, 'message': msg, }
def add_nin_external(self, data): try: validated = self.validate_post_data() except deform.exception.ValidationFailure as e: return { 'status': 'failure', 'data': e.error.asdict() } self.user = get_session_user(self.request) self.addition_with_code_validation(validated) self.request.stats.count('nin_add_external') return { 'status': 'success' }
def add_nin_external(self, data): try: validated = self.validate_post_data() except deform.exception.ValidationFailure as e: return { 'status': 'failure', 'data': e.error.asdict() } self.user = get_session_user(self.request) self.addition_with_code_validation(validated) self.request.stats.count('dashboard/nin_add_external', 1) return { 'status': 'success' }
def get_template_context(self): context = super(PasswordsView, self).get_template_context() # Collect the users mail addresses for use with zxcvbn mail_addresses = [] user = get_session_user(self.request) for item in user.mail_addresses.to_list(): mail_addresses.append(item.email) context.update({ 'message': getattr(self, 'message', ''), 'changed': getattr(self, 'changed', False), 'authninfo': get_authn_info(self.request), 'user_input': json.dumps(mail_addresses) }) return context
def account_terminated(context, request): """ The account termination action, removes all credentials for the terminated account from the VCCS service, flags the account as terminated, sends an email to the address in the terminated account, and logs out the session. :type user: eduid_userdb.dashboard.DashboardLegacyUser """ settings = request.registry.settings authn_ts = request.session.get('reauthn-for-termination', None) if authn_ts is None: raise HTTPBadRequest(_('No authentication info')) now = datetime.utcnow() delta = now - datetime.fromtimestamp(authn_ts) # XXX put the reauthn timeout in the settings if int(delta.total_seconds()) > 600: msg = _('Stale authentication info. Please try again.') request.session.flash('error|' + msg) raise HTTPFound(context.route_url('profile-editor')) user = get_session_user(request) logger.debug('Removing Authn ts for user {!r} before' ' changing the password'.format(user)) del request.session['reauthn-for-termination'] logged_user = get_logged_in_user(request, legacy_user = False) if logged_user.user_id != user.user_id: raise HTTPUnauthorized("Wrong user") logger.info("Terminating user {!s}".format(user)) # revoke all user credentials revoke_all_credentials(settings.get('vccs_url'), user) for p in logged_user.credentials.filter(Password).to_list(): logged_user.passwords.remove(p.key) # flag account as terminated logged_user.terminated = True request.context.save_dashboard_user(logged_user) # email the user send_termination_mail(request, logged_user) return {}
def __call__(self, node, value): request = node.bindings.get('request') settings = request.registry.settings user = get_session_user(request) result = validate_nin_by_mobile(request, user, value) if not result['success']: localizer = get_localizer(request) raise colander.Invalid( node, localizer.translate(result['message'], mapping={ 'service_name': settings.get('mobile_service_name', 'TeleAdress'), }))
def save_success(self, passwordform): authn_ts = self.request.session.get('reauthn-for-chpass', None) if authn_ts is None: raise HTTPBadRequest(_('No authentication info')) now = datetime.utcnow() delta = now - datetime.fromtimestamp(authn_ts) # XXX put the reauthn timeout in the settings 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')) user = get_session_user(self.request) log.debug('Removing Authn ts for user {!r} before' ' changing the password'.format(user)) del self.request.session['reauthn-for-chpass'] passwords_data = self.schema.serialize(passwordform) 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)) 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)) 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_new.get_user_by_id(user.user_id) log.debug("Refreshed user {!s} from {!s}".format(user, self.request.userdb_new)) retrieve_modified_ts(user, self.request.dashboard_userdb) 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 verify_lp_action(self, data, post_data): ''' verify by letter ''' nin, index = data.split() index = int(index) self.user = get_session_user(self.request) nins = get_not_verified_nins_list(self.request, self.user) if len(nins) > index: new_nin = nins[index] if new_nin != nin: return self.sync_user() else: return self.sync_user() return letter_status(self.request, self.user, nin)
def __call__(self, node, value): request = node.bindings.get('request') if not request.registry.settings.get('use_vccs', True): return old_password = value.replace(" ", "") user = get_session_user(request) vccs_url = request.registry.settings.get('vccs_url') password = check_password(vccs_url, old_password, user) if not password: err = _('Current password is incorrect') localizer = get_localizer(request) raise colander.Invalid(node, localizer.translate(err))
def __call__(self, node, value): request = node.bindings.get('request') user = get_session_user(request) user_emails = [e.email for e in user.mail_addresses.to_list()] if user.mail_addresses.primary: user_emails.append(user.mail_addresses.primary.email) localizer = get_localizer(request) if sanitize_post_key(request, 'add') is not None: if value in user_emails: err = _("You already have this email address") raise colander.Invalid(node, localizer.translate(err)) elif set(['verify', 'setprimary', 'remove']).intersection(set(request.POST)): if value not in user_emails: err = _("This email address is unavailable") raise colander.Invalid(node, localizer.translate(err))
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 __call__(self, node, value): request = node.bindings.get('request') localizer = get_localizer(request) settings = request.registry.settings value = value.replace(" ", "") password_min_entropy = int(settings.get('password_entropy', 60)) # We accept a 10% of variance in password_min_entropy because # we have calculated the entropy by javascript too and the results # may vary. password_min_entropy = (0.90 * password_min_entropy) generated_password = request.session.get('last_generated_password', '') if len(generated_password) > 0 and generated_password == value: # Don't validate the password if it is the generated password # That is, the user has filled out the form with the suggested # password return # Get a users e-mail addresses to make sure a user does not use one of those as password user = get_session_user(request, raise_on_not_logged_in=False) if not user: # User is resetting a forgotten password hash_code = request.matchdict['code'] password_reset = request.db.reset_passwords.find_one( {'hash_code': hash_code}) if password_reset.get('eppn'): user = request.userdb_new.get_user_by_eppn( password_reset['eppn']) # Legacy password reset codes were connected to the user by email elif password_reset.get('email'): user = request.userdb_new.get_user_by_mail( password_reset['email']) mail_addresses = [item.email for item in user.mail_addresses.to_list()] veredict = zxcvbn.password_strength(value, user_inputs=mail_addresses) if veredict.get('entropy', 0) < password_min_entropy: err = _('The password complexity is too weak.') raise colander.Invalid(node, localizer.translate(err))
def resend_code_action(self, data, post_data): self.user = get_session_user(self.request) nin, index = data.split() index = int(index) nins = get_not_verified_nins_list(self.request, self.user) if len(nins) > index: nin = nins[index] else: raise HTTPNotFound(_("No pending national identity numbers found.")) send_verification_code(self.request, self.context.user, nin) self.request.stats.count('nin_code_resend') message = self.verify_messages['new_code_sent'] return { 'result': 'success', 'message': message, }
def resend_code_action(self, data, post_data): self.user = get_session_user(self.request) nin, index = data.split() index = int(index) nins = get_not_verified_nins_list(self.request, self.user) if len(nins) > index: nin = nins[index] else: raise HTTPNotFound(_("No pending national identity numbers found.")) send_verification_code(self.request, self.context.user, nin) self.request.stats.count('dashboard/nin_code_resend', 1) message = self.verify_messages['new_code_sent'] return { 'result': 'success', 'message': message, }
def add_by_mobile_success(self, ninform): """ This method is bounded to the "add_by_mobile"-button by it's name """ newnin = self.schema.serialize(ninform) newnin = newnin['norEduPersonNIN'] newnin = normalize_nin(newnin) self.user = get_session_user(self.request) message = set_nin_verified(self.request, self.user, newnin) try: self.request.context.save_dashboard_user(self.user) except UserOutOfSync: log.info("Failed to save user {!r} after mobile phone vetting. User out of sync.".format(self.user)) raise log.info("Saved user {!r} after NIN vetting using mobile phone".format(self.user)) self.request.session.flash( get_localizer(self.request).translate(message), queue='forms') self.request.stats.count('nin_add_mobile')
def add_by_letter_success(self, ninform): """ This method is bound to the "add_by_letter"-button by it's name """ form = self.schema.serialize(ninform) nin = normalize_nin(form['norEduPersonNIN']) session_user = get_session_user(self.request) # self.user needs to be a new user in get_template_context self.user = session_user result = letter_status(self.request, session_user, nin) if result['result'] == 'success': result2 = send_letter(self.request, session_user, nin) if result2['result'] == 'success': new_verification_code(self.request, 'norEduPersonNIN', nin, session_user) msg = result2['message'] else: msg = result['message'] self.request.session.flash(msg, queue='forms')
def get_user(self): """ Get the current user. In 'personal' mode, this is the logged in user. In 'admin' mode, it is the 'edit-user' user from the session, or the user indicated by the matchdict 'userid'. Matchdict is a part of the URL extracted by Pyramid with routes like '/users/{userid}/'. :return: User object :rtype: DashboardUser """ if self.workmode == 'personal': user = get_logged_in_user(self.request, legacy_user=False, raise_on_not_logged_in=False) self._logged_in_user = user elif self.workmode == 'admin' or self.workmode == 'helpdesk': if not has_edit_user(self.request): user = None userid = self.request.matchdict.get('userid', '') logger.debug( 'get_user() looking for user matching {!r}'.format(userid)) if EMAIL_RE.match(userid): user = self.request.userdb_new.get_user_by_mail(userid) elif OID_RE.match(userid): user = self.request.userdb_new.get_user_by_id(userid) if not user: raise HTTPNotFound() logger.debug('get_user() storing user {!r}'.format(user)) store_session_user(self.request, user, edit_user=True) user = get_session_user(self.request) else: raise NotImplementedError("Unknown workmode: {!s}".format( self.workmode)) return user
def remove_action(self, index, post_data): self.user = get_session_user(self.request) mobiles = self.user.phone_numbers.to_list() try: mobile_to_remove = mobiles[index] except IndexError: return self.sync_user() try: self.user.phone_numbers.remove(mobile_to_remove.number) except PrimaryElementViolation: # The exception was raised because it would result in zero # primary mobiles while there still exits verified ones. # Therefore we first we have to set one of the other # verified mobiles as primary before we can try to # remove the previous one again. verified_mobiles = self.user.phone_numbers.verified.to_list() for mobile in verified_mobiles: if mobile.number != mobile_to_remove.number: self.user.phone_numbers.primary = mobile.number break self.user.phone_numbers.remove(mobile_to_remove.number) try: self.context.save_dashboard_user(self.user) except UserOutOfSync: return self.sync_user() self.request.stats.count('mobile_number_removed') message = _('Mobile phone number was successfully removed') return { 'result': 'success', 'message': get_localizer(self.request).translate(message), }
def resend_code_action(self, index, post_data): self.user = get_session_user(self.request) data = self.user.to_dict().get(self.data_attribute, []) # Catch the unlikely event when the user have e.g. removed all entries # in a separate tab, or one in the middle and then tries to resend the # code for a non-existing entry. # This is an incomplete fix since it is better if we can get the list # from the UI and then check that the entry which the client want to # resend the code for corresponds to the same entry we get from # data[index]. try: data_to_resend = data[index] except IndexError: log.warning('Index error in resend_code_action, user {!s}'.format( self.user)) message = self.verify_messages['out_of_sync'] return { 'result': 'out_of_sync', 'message': get_localizer(self.request).translate(message), } data_id = self.get_verification_data_id(data_to_resend) reference, code = new_verification_code( self.request, self.data_attribute, data_id, self.user, hasher=get_short_hash, ) self.send_verification_code(data_id, reference, code) msg = self.verify_messages['new_code_sent'] return { 'result': 'success', 'message': msg, }
def add_success_personal(self, ninform, msg): self.user = get_session_user(self.request) self.addition_with_code_validation(ninform) msg = get_localizer(self.request).translate(msg) self.request.session.flash(msg, queue='forms')
def send_letter_action(self, data, post_data): self.user = get_session_user(self.request) nin, index = data.split() return send_letter(self.request, self.user, nin)
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'))