def spamcheck_hook(): if not current_app.config.get("BASSET_URL"): return jsonify({"error": "Spamcheck disabled"}), 501 data = request.get_json() if not data: return jsonify({"error": "Bad payload"}), 400 try: token = data["token"] status = data["status"] except KeyError as e: return jsonify({"error": f"Missing key: {e}"}), 400 try: token_data = read_token(token, audience=Audience.spam_check) except jwt.ExpiredSignatureError: return jsonify({"error": "The token has expired"}), 400 except jwt.InvalidTokenError as e: return jsonify({"error": f"Invalid token: {e}"}), 400 username = token_data["sub"] if status not in ("active", "spamcheck_denied", "spamcheck_manual"): return jsonify({"error": f"Invalid status: {status}."}), 400 result = ipa_admin.stageuser_mod(a_uid=username, fasstatusnote=status) user = User(result["result"]) if status == "active": # Send the address validation email _send_validation_email(user) return jsonify({"status": "success"})
def test_ask_post(client, dummy_user, patched_lock): with mailer.record_messages() as outbox: result = client.post('/forgot-password/ask', data={"username": "******"}) # Confirmation message assert_redirects_with_flash( result, expected_url="/", expected_message=( "An email has been sent to your address with instructions on how to reset " "your password" ), expected_category="success", ) # Sent email assert len(outbox) == 1 message = outbox[0] assert message.subject == "Password reset procedure" assert message.recipients == ["*****@*****.**"] # Valid token token_match = re.search(r"\?token=([^\s\"']+)", message.body) assert token_match is not None token = token_match.group(1) token_data = read_token(token, audience=Audience.password_reset) assert token_data.get("sub") == "dummy" assert "lpc" in token_data # Lock activated patched_lock["store"].assert_called_once()
def test_signal_basset(client, mocker, dummy_user): mocked_requests = mocker.patch("noggin.signals.requests") mocker.patch.dict(current_app.config, {"BASSET_URL": "http://basset.test"}) user = User(ipa_admin.user_show("dummy")["result"]) with current_app.test_request_context('/'): request_basset_check(user) call_args = mocked_requests.post.call_args_list[0] assert list(call_args[0]) == ["http://basset.test"] json_data = call_args[1]["json"] assert json_data["action"] == "fedora.noggin.registration" expected_dict = user.as_dict() expected_dict["human_name"] = user.commonname expected_dict["email"] = user.mail assert json_data["data"]["user"] == expected_dict assert json_data["data"]["request_headers"] == {"Host": "localhost"} assert json_data["data"]["callback"] == "http://localhost/register/spamcheck-hook" token = json_data["data"]["token"] token_data = read_token(token, audience=Audience.spam_check) assert token_data["sub"] == "dummy"
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 = read_token(token_string, audience=Audience.email_validation) except jwt.exceptions.DecodeError: flash(_("The token is invalid, please register again."), "warning") return redirect(register_url) except jwt.exceptions.ExpiredSignatureError: flash(_("This token is no longer valid, please register again."), "warning") return redirect(register_url) try: user = User(ipa_admin.stageuser_show(token["sub"])["result"]) except python_freeipa.exceptions.NotFound: flash(_("This user cannot be found, please register again."), "warning") return redirect(register_url) token_mail = token["mail"] if not user.mail == token_mail: current_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: current_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 creating your account, " "please try again later."), ) # User activation succeeded. Send signal. user_registered.send(user, request=request._get_current_object()) # 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(current_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 created, 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: current_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 created, 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(current_app, session, user.username, password) except python_freeipa.exceptions.FreeIPAError: ipa = None if ipa: flash( _( 'Congratulations, your account has been created! 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 has been created! Go ahead and sign in ' 'to proceed.'), 'success', ) return redirect(url_for('.root')) return render_template('registration-activation.html', user=user, form=form)
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)