def user_settings_otp(ipa, username): addotpform = UserSettingsAddOTPForm() user = User(user_or_404(ipa, username)) if addotpform.validate_on_submit(): try: maybe_ipa_login(current_app, session, username, addotpform.password.data) result = ipa.otptoken_add( o_ipatokenowner=username, o_ipatokenotpalgorithm='sha512', o_description=addotpform.description.data, )['result'] uri = urlparse(result['uri']) # Use the provided description in the token, so it shows up in the user's app instead of # the token's UUID principal = uri.path.split(":", 1)[0] new_uri = uri._replace( path=f"{principal.lower()}:{quote(addotpform.description.data)}" ) session['otp_uri'] = new_uri.geturl() except python_freeipa.exceptions.InvalidSessionPassword: addotpform.password.errors.append(_("Incorrect password")) except python_freeipa.exceptions.FreeIPAError as e: current_app.logger.error( f'An error happened while creating an OTP token for user {username}: {e.message}' ) addotpform.non_field_errors.errors.append( _('Cannot create the token.')) else: return redirect(url_for('.user_settings_otp', username=username)) otp_uri = session.get('otp_uri') session['otp_uri'] = None tokens = [ OTPToken(t) for t in ipa.otptoken_find(o_ipatokenowner=username)["result"] ] tokens.sort(key=lambda t: t.description or "") return render_template( 'user-settings-otp.html', addotpform=addotpform, user=user, activetab="otp", tokens=tokens, otp_uri=otp_uri, )
def handle_login_form(form): username = form.username.data.lower() password = form.password.data try: # This call will set the cookie itself, we don't have to. ipa = maybe_ipa_login(current_app, session, username, password) except python_freeipa.exceptions.PasswordExpired: flash(_('Password expired. Please reset it.'), 'danger') return redirect(url_for('.password_reset', username=username)) except python_freeipa.exceptions.Unauthorized as e: raise FormError("non_field_errors", e.message) 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 logging in user ' f'{username}: {e.message}') raise FormError("non_field_errors", _('Could not log in to the IPA server.')) if not ipa: current_app.logger.error( f'An unhandled situation happened while logging in user {username}: ' f'could not connect to the IPA server') raise FormError("non_field_errors", _('Could not log in to the IPA server.')) flash(_('Welcome, %(username)s!', username=username), 'success') return redirect(url_for('.user', username=username))
def logged_in_dummy_user(client, dummy_user): with client.session_transaction() as sess: ipa = maybe_ipa_login(app, sess, "dummy", "dummy_password") yield ipa ipa.logout() with client.session_transaction() as sess: sess.clear()
def test_ipa_login(client, dummy_user): with client.session_transaction() as sess: ipa = maybe_ipa_login(current_app, sess, "dummy", "dummy_password") assert ipa is not None with client.session_transaction() as sess: assert sess.get('noggin_session') assert sess.get('noggin_ipa_server_hostname') == "ipa.example.com" assert sess.get('noggin_username') == "dummy" # Test that the session is valid Fernet ipa_session = Fernet(current_app.config['FERNET_SECRET']).decrypt( sess.get('noggin_session')) assert str(ipa_session, 'ascii').startswith("MagBearerToken=")
def test_with_ipa(client, dummy_user): """Test the with_ipa decorator""" view = mock.Mock() with current_app.test_request_context('/'): ipa = maybe_ipa_login(current_app, session, "dummy", "dummy_password") wrapped = with_ipa()(view) wrapped("arg") view.assert_called_once() assert "ipa" in view.call_args_list[0][1] assert isinstance(view.call_args_list[0][1]["ipa"], ipa.__class__) assert "arg" in view.call_args_list[0][0] assert "ipa" in g assert isinstance(g.ipa, ipa.__class__) assert "current_user" in g assert g.current_user.username == "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 = 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 user_settings_otp(ipa, username): addotpform = UserSettingsAddOTPForm(prefix="add-") confirmotpform = UserSettingsConfirmOTPForm(prefix="confirm-") user = User(user_or_404(ipa, username)) secret = None if addotpform.validate_on_submit(): description = addotpform.description.data password = addotpform.password.data if addotpform.otp.data: password += addotpform.otp.data try: maybe_ipa_login(current_app, session, username, password) except python_freeipa.exceptions.InvalidSessionPassword: addotpform.password.errors.append(_("Incorrect password")) else: secret = b32encode(os.urandom(OTP_KEY_LENGTH)).decode('ascii') # Prefill the form for the next step confirmotpform.process( MultiDict({ "confirm-secret": secret, "confirm-description": description })) if confirmotpform.validate_on_submit(): try: ipa.otptoken_add( o_ipatokenowner=username, o_description=confirmotpform.description.data, o_ipatokenotpkey=confirmotpform.secret.data, ) except python_freeipa.exceptions.FreeIPAError as e: current_app.logger.error( f'An error happened while creating an OTP token for user {username}: {e.message}' ) confirmotpform.non_field_errors.errors.append( _('Cannot create the token.')) else: flash(_('The token has been created.'), "success") return redirect(url_for('.user_settings_otp', username=username)) if confirmotpform.is_submitted(): # This form is inside the modal. Keep a value in otp_uri or the modal will not open # to show the errors. secret = confirmotpform.secret.data # Compute the token URI if secret: description = addotpform.description.data or confirmotpform.description.data token = TOTP(secret) otp_uri = token.provisioning_uri(name=description, issuer_name=user.krbname) else: otp_uri = None # List existing tokens tokens = [ OTPToken(t) for t in ipa.otptoken_find(o_ipatokenowner=username)["result"] ] tokens.sort(key=lambda t: t.description or "") return render_template( 'user-settings-otp.html', addotpform=addotpform, confirmotpform=confirmotpform, user=user, activetab="otp", tokens=tokens, otp_uri=otp_uri, )