def _user_mod(ipa, form, user, details, redirect_to): with handle_form_errors(form): try: updated_user = User( ipa.user_mod(user.username, **details, all=True)) except python_freeipa.exceptions.BadRequest as e: if e.message == 'no modifications to be performed': raise FormError("non_field_errors", e.message) else: app.logger.error( f'An error happened while editing user {user.username}: {e.message}' ) raise FormError("non_field_errors", e.message) flash( Markup( f'Profile Updated: <a href=\"{url_for("user", username=user.username)}\">' 'view your profile</a>'), 'success', ) messaging.publish( UserUpdateV1({ "msg": { "agent": user.username, "user": user.username, "fields": user.diff_fields(updated_user), } })) return redirect(url_for(redirect_to, username=user.username))
def send_registered_message(sender, **kwargs): user = sender messaging.publish( UserCreateV1({"msg": { "agent": user.username, "user": user.username }}))
def test_publish(): with mock.patch("fedora_messaging.api.publish") as api_publish: messaging.publish( MemberSponsorV1( {"msg": {"agent": "dummy", "user": "******", "group": "dummy-group"}} ) ) api_publish.assert_called_once()
def test_publish_with_errors(): with mock.patch("fedora_messaging.api.publish") as api_publish: api_publish.side_effect = fml_exceptions.ConnectionException() messaging.publish( MemberSponsorV1( {"msg": {"agent": "dummy", "user": "******", "group": "dummy-group"}} ) ) assert api_publish.call_count == 3
def test_publish(app_context, mocker): api_publish = mocker.patch("fedora_messaging.api.publish") messaging.publish( MemberSponsorV1({ "msg": { "agent": "dummy", "user": "******", "group": "dummy-group" } })) api_publish.assert_called_once()
def test_publish_disabled(app_context, mocker): mocker.patch.dict(current_app.config, {"FEDORA_MESSAGING_ENABLED": False}) api_publish = mocker.patch("fedora_messaging.api.publish") messaging.publish( MemberSponsorV1({ "msg": { "agent": "dummy", "user": "******", "group": "dummy-group" } })) api_publish.assert_not_called()
def _validate_change_pw_form(form, username, ipa=None): if ipa is None: ipa = untouched_ipa_client(current_app) current_password = form.current_password.data password = form.password.data otp = form.otp.data res = None try: res = ipa.change_password(username, password, current_password, otp) except python_freeipa.exceptions.PWChangeInvalidPassword: form.current_password.errors.append( _("The old password or username is not correct")) except python_freeipa.exceptions.PWChangePolicyError as e: form.password.errors.append(e.policy_error) except python_freeipa.exceptions.FreeIPAError as e: # If we made it here, we hit something weird not caught above. We didn't # bomb out, but we don't have IPA creds, either. current_app.logger.error( f'An unhandled error {e.__class__.__name__} happened while reseting ' f'the password for user {username}: {e.message}') form.non_field_errors.errors.append(_('Could not change password.')) if res and res.ok: flash(_('Your password has been changed'), 'success') current_app.logger.info(f'Password for {username} was changed') messaging.publish( UserUpdateV1({ "msg": { "agent": username, "user": username, "fields": ["password"] } })) return res
def activate_account(): register_url = f"{url_for('root')}?tab=register" token_string = request.args.get('token') if not token_string: flash(_('No token provided, please check your email validation link.'), 'warning') return redirect(register_url) try: token = EmailValidationToken.from_string(token_string) except jwt.exceptions.DecodeError: flash(_("The token is invalid, please register again."), "warning") return redirect(register_url) if not token.is_valid(): flash(_("This token is no longer valid, please register again."), "warning") return redirect(register_url) try: user = User(ipa_admin.stageuser_show(token.username)) except python_freeipa.exceptions.NotFound: flash(_("This user cannot be found, please register again."), "warning") return redirect(register_url) if not user.mail == token.mail: app.logger.error( f'User {user.username} tried to validate a token for address {token.mail} while they ' f'are registered with address {user.mail}, something fishy may be going on.' ) flash( _("The username and the email address don't match the token you used, " "please register again."), "warning", ) return redirect(register_url) form = PasswordSetForm() if form.validate_on_submit(): with handle_form_errors(form): password = form.password.data # First we activate the stage user try: ipa_admin.stageuser_activate(user.username) except python_freeipa.exceptions.FreeIPAError as e: app.logger.error( f'An unhandled error {e.__class__.__name__} happened while activating ' f'stage user {user.username}: {e.message}') raise FormError( "non_field_errors", _("Something went wrong while activating your account, " "please try again later."), ) # User activation succeeded. Send message. messaging.publish( UserCreateV1( {"msg": { "agent": user.username, "user": user.username }})) # Now we set the password. try: # First, set it as an admin. This will mark it as expired. ipa_admin.user_mod(user.username, userpassword=password) # And now we set it again as the user, so it is not expired any more. ipa = untouched_ipa_client(app) ipa.change_password(user.username, new_password=password, old_password=password) except python_freeipa.exceptions.PWChangePolicyError as e: # The user is active but the password does not match the policy. # Tell the user what's going to happen. flash( _( 'Your account has been activated, but the password you chose does not ' 'comply with the policy (%(policy_error)s) and has thus been set as ' 'expired. You will be asked to change it after logging in.', policy_error=e.policy_error, ), 'warning', ) return redirect(url_for("root")) except python_freeipa.exceptions.ValidationError as e: # for example: invalid username. We don't know which field to link it to _handle_registration_validation_error(user.username, e) except python_freeipa.exceptions.FreeIPAError as e: app.logger.error( f'An unhandled error {e.__class__.__name__} happened while changing initial ' f'password for user {user.username}: {e.message}') # At this point the user has been activated, they can't register again. Send them to # the login page with an appropriate warning. flash( _( 'Your account has been activated, but an error occurred while setting your ' 'password (%(message)s). You may need to change it after logging in.', message=e.message, ), 'warning', ) return redirect(url_for("root")) # Try to log them in directly, so they don't have to type their password again. try: ipa = maybe_ipa_login(app, session, user.username, password) except python_freeipa.exceptions.FreeIPAError: ipa = None if ipa: flash( _( 'Congratulations, your account is now active! Welcome, %(name)s.', name=user.name, ), 'success', ) else: # No shortcut for you, you'll have to login properly (maybe the password is # expired). flash( _('Congratulations, your account is now active! Go ahead and sign in ' 'to proceed.'), 'success', ) return redirect(url_for('root')) return render_template('registration-activation.html', user=user, form=form)
def group_add_member(ipa, groupname): group_or_404(ipa, groupname) sponsor_form = AddGroupMemberForm() if sponsor_form.validate_on_submit(): username = sponsor_form.new_member_username.data # First make sure the user exists try: ipa.user_show(username) except python_freeipa.exceptions.NotFound: flash( _('User %(username)s was not found in the system.', username=username), 'danger', ) return redirect(url_for('.group', groupname=groupname)) try: ipa.group_add_member(a_cn=groupname, o_user=username) except python_freeipa.exceptions.ValidationError as e: # e.message is a dict that we have to process ourselves for now: # https://github.com/opennode/python-freeipa/issues/24 for error in e.message['member']['user']: flash( _( 'Unable to add user %(username)s: %(errormessage)s', username=error[0], errormessage=error[1], ), 'danger', ) return redirect(url_for('.group', groupname=groupname)) flash_text = _( 'You got it! %(username)s has been added to %(groupname)s.', username=username, groupname=groupname, ) flash( flash_text + undo_button( url_for(".group_remove_member", groupname=groupname), "username", username, sponsor_form.hidden_tag(), ), 'success', ) messaging.publish( MemberSponsorV1({ "msg": { "agent": g.current_user.username, "user": username, "group": groupname, } })) return redirect(url_for('.group', groupname=groupname)) for field_errors in sponsor_form.errors.values(): for error in field_errors: flash(error, 'danger') return redirect(url_for('.group', groupname=groupname))
def forgot_password_change(): token = request.args.get('token') if not token: flash('No token provided, please request one.', 'warning') return redirect(url_for('.forgot_password_ask')) try: token_data = read_token(token, audience=Audience.password_reset) except jwt.exceptions.DecodeError: flash(_("The token is invalid, please request a new one."), "warning") return redirect(url_for('.forgot_password_ask')) username = token_data["sub"] lock = PasswordResetLock(username) valid_until = lock.valid_until() now = datetime.datetime.now() if valid_until is None or now > valid_until: lock.delete() flash(_("The token has expired, please request a new one."), "warning") return redirect(url_for('.forgot_password_ask')) user = User(ipa_admin.user_show(a_uid=username)['result']) if user.last_password_change != token_data["lpc"]: lock.delete() flash( _("Your password has been changed since you requested this token, please request " "a new one."), "warning", ) return redirect(url_for('.forgot_password_ask')) form = NewPasswordForm() if form.validate_on_submit(): password = form.password.data # Generate a random temporary number. temp_password = ''.join( random.choices(string.ascii_letters + string.digits, k=24)) try: # Force change password to the random password, so that the password is not actually # changed to the given one in case the next step fails (because the OTP is wrong for # example) ipa_admin.user_mod(username, userpassword=temp_password) # Change the password as the user, so it's not expired. ipa = untouched_ipa_client(current_app) ipa.change_password( username, new_password=password, old_password=temp_password, otp=form.otp.data, ) except python_freeipa.exceptions.PWChangePolicyError as e: lock.delete() flash( _( 'Your password has been changed, but it does not comply with the policy ' '(%(policy_error)s) and has thus been set as expired. You will be asked to ' 'change it after logging in.', policy_error=e.policy_error, ), 'warning', ) current_app.logger.info( f"Password for {username} was changed to a non-compliant password after " f"completing the forgotten password process.") # Send them to the login page, they will have to change their password # after login. return redirect(url_for('.root')) except python_freeipa.exceptions.PWChangeInvalidPassword: # The provided OTP was wrong current_app.logger.info( f"Password for {username} was changed to a random string because " f"the OTP token they provided was wrong.") # Oh noes, the token is now invalid since the user's password was changed! Let's # re-generate a token so they can keep going. user = User(ipa_admin.user_show(a_uid=username)['result']) token = make_token( { "sub": user.username, "lpc": user.last_password_change }, audience=Audience.password_reset, ) form.otp.errors.append(_("Incorrect value.")) except python_freeipa.exceptions.FreeIPAError as e: # If we made it here, we hit something weird not caught above. current_app.logger.error( f'An unhandled error {e.__class__.__name__} happened while reseting ' f'the password for user {username}: {e.message}') form.non_field_errors.errors.append( _('Could not change password, please try again.')) else: lock.delete() flash(_('Your password has been changed.'), 'success') current_app.logger.info( f"Password for {username} was changed after completing the forgotten " f"password process.") messaging.publish( UserUpdateV1({ "msg": { "agent": username, "user": username, "fields": ["password"], } })) return redirect(url_for('.root')) return render_template('forgot-password-change.html', username=username, form=form, token=token)