def change_password_with_old_password(db: DatabaseHandler, email: str,
                                      old_password: str, new_password: str,
                                      new_password_repeat: str) -> None:
    """Change password by entering old password."""

    email = decode_object_from_bytes_if_needed(email)
    old_password = decode_object_from_bytes_if_needed(old_password)
    new_password = decode_object_from_bytes_if_needed(new_password)
    new_password_repeat = decode_object_from_bytes_if_needed(
        new_password_repeat)

    # Check if user exists
    try:
        user_info(db=db, email=email)
    except Exception:
        raise McAuthChangePasswordException(
            'User with email address "%s" does not exist.' % email)

    if old_password == new_password:
        raise McAuthChangePasswordException(
            'Old and new passwords are the same.')

    # Validate old password; fetch the hash from the database again because that hash might be outdated (e.g. if the
    # password has been changed already)
    db_password_old = db.query(
        """
        SELECT auth_users_id,
               email,
               password_hash
        FROM auth_users
        WHERE email = %(email)s
        LIMIT 1
    """, {
            'email': email
        }).hash()
    if db_password_old is None or len(db_password_old) == 0:
        raise McAuthChangePasswordException(
            'Unable to find the user in the database.')

    # Validate the password
    try:
        login_with_email_password(db=db, email=email, password=old_password)
    except Exception as ex:
        raise McAuthChangePasswordException(
            "Unable to log in with old password: %s" % str(ex))

    # Execute the change
    try:
        change_password(db=db,
                        email=email,
                        new_password=new_password,
                        new_password_repeat=new_password_repeat)
    except Exception as ex:
        raise McAuthChangePasswordException("Unable to change password: %s" %
                                            str(ex))
def send_password_reset_token(db: DatabaseHandler, email: str, password_reset_link: str) -> None:
    """Prepare for password reset by emailing the password reset token."""

    email = decode_object_from_bytes_if_needed(email)
    password_reset_link = decode_object_from_bytes_if_needed(password_reset_link)

    # Check if user exists
    try:
        user = user_info(db=db, email=email)
        full_name = user.full_name()

    except Exception as ex:
        log.warning("Unable to fetch user profile for user '%s': %s" % (email, str(ex),))
        full_name = 'Nonexistent user'

    # If user was not found, send an email to a random address anyway to avoid timing attack
    full_password_reset_link = _generate_password_reset_token(
        db=db,
        email=email,
        password_reset_link=password_reset_link,
    )
    if not full_password_reset_link:
        log.warning("Unable to generate full password reset link for email '%s'" % email)
        email = '*****@*****.**'
        full_password_reset_link = 'password reset link'

    message = AuthResetPasswordMessage(to=email, full_name=full_name, password_reset_url=full_password_reset_link)
    if not send_email(message):
        raise McAuthResetPasswordException('Unable to send password reset email.')
def change_password_with_old_password(db: DatabaseHandler,
                                      email: str,
                                      old_password: str,
                                      new_password: str,
                                      new_password_repeat: str) -> None:
    """Change password by entering old password."""

    email = decode_object_from_bytes_if_needed(email)
    old_password = decode_object_from_bytes_if_needed(old_password)
    new_password = decode_object_from_bytes_if_needed(new_password)
    new_password_repeat = decode_object_from_bytes_if_needed(new_password_repeat)

    # Check if user exists
    try:
        user_info(db=db, email=email)
    except Exception:
        raise McAuthChangePasswordException('User with email address "%s" does not exist.' % email)

    if old_password == new_password:
        raise McAuthChangePasswordException('Old and new passwords are the same.')

    # Validate old password; fetch the hash from the database again because that hash might be outdated (e.g. if the
    # password has been changed already)
    db_password_old = db.query("""
        SELECT auth_users_id,
               email,
               password_hash
        FROM auth_users
        WHERE email = %(email)s
        LIMIT 1
    """, {'email': email}).hash()
    if db_password_old is None or len(db_password_old) == 0:
        raise McAuthChangePasswordException('Unable to find the user in the database.')

    # Validate the password
    try:
        login_with_email_password(db=db, email=email, password=old_password)
    except Exception as ex:
        raise McAuthChangePasswordException("Unable to log in with old password: %s" % str(ex))

    # Execute the change
    try:
        change_password(db=db, email=email, new_password=new_password, new_password_repeat=new_password_repeat)
    except Exception as ex:
        raise McAuthChangePasswordException("Unable to change password: %s" % str(ex))
def change_password_with_reset_token(db: DatabaseHandler, email: str,
                                     password_reset_token: str,
                                     new_password: str,
                                     new_password_repeat: str) -> None:
    """Change password with a password token sent by email."""

    email = decode_object_from_bytes_if_needed(email)
    password_reset_token = decode_object_from_bytes_if_needed(
        password_reset_token)
    new_password = decode_object_from_bytes_if_needed(new_password)
    new_password_repeat = decode_object_from_bytes_if_needed(
        new_password_repeat)

    if not password_reset_token:
        raise McAuthChangePasswordException('Password reset token is empty.')

    # Check if user exists
    try:
        user_info(db=db, email=email)
    except Exception:
        raise McAuthChangePasswordException(
            'User with email address "%s" does not exist.' % email)

    # Validate the token once more (was pre-validated in controller)
    if not password_reset_token_is_valid(
            db=db, email=email, password_reset_token=password_reset_token):
        raise McAuthChangePasswordException('Password reset token is invalid.')

    # Execute the change
    try:
        change_password(db=db,
                        email=email,
                        new_password=new_password,
                        new_password_repeat=new_password_repeat)
    except Exception as ex:
        raise McAuthChangePasswordException("Unable to change password: %s" %
                                            str(ex))

    # Unset the password reset token
    db.query(
        """
        UPDATE auth_users
        SET password_reset_token_hash = NULL
        WHERE email = %(email)s
    """, {'email': email})
Beispiel #5
0
def delete_user(db: DatabaseHandler, email: str) -> None:
    """Delete user."""

    email = decode_object_from_bytes_if_needed(email)

    if not email:
        raise McAuthProfileException('Email address is empty.')

    # Check if user exists
    try:
        user_info(db=db, email=email)
    except Exception:
        raise McAuthProfileException("User with email address '%s' does not exist." % email)

    # Delete the user (PostgreSQL's relation will take care of 'auth_users_roles_map')
    db.query("""
        DELETE FROM auth_users
        WHERE email = %(email)s
    """, {'email': email})
def change_password(db: DatabaseHandler,
                    email: str,
                    new_password: str,
                    new_password_repeat: str,
                    do_not_inform_via_email: bool = False) -> None:
    """Change user's password."""

    email = decode_object_from_bytes_if_needed(email)
    new_password = decode_object_from_bytes_if_needed(new_password)
    new_password_repeat = decode_object_from_bytes_if_needed(new_password_repeat)

    if isinstance(do_not_inform_via_email, bytes):
        do_not_inform_via_email = decode_object_from_bytes_if_needed(do_not_inform_via_email)

    do_not_inform_via_email = bool(int(do_not_inform_via_email))

    # Check if user exists
    try:
        user = user_info(db=db, email=email)
    except Exception:
        raise McAuthChangePasswordException('User with email address "%s" does not exist.' % email)

    password_validation_message = validate_new_password(email=email,
                                                        password=new_password,
                                                        password_repeat=new_password_repeat)
    if password_validation_message:
        raise McAuthChangePasswordException("Unable to change password: %s" % password_validation_message)

    # Hash + validate the password
    try:
        password_new_hash = generate_secure_hash(password=new_password)
    except Exception as ex:
        raise McAuthChangePasswordException("Unable to hash a new password: %s" % str(ex))

    if not password_new_hash:
        raise McAuthChangePasswordException("Generated password hash is empty.")

    # Set the password hash
    db.query("""
        UPDATE auth_users
        SET password_hash = %(password_hash)s,
            active = TRUE
        WHERE email = %(email)s
    """, {
        'email': email,
        'password_hash': password_new_hash,
    })

    if not do_not_inform_via_email:

        message = AuthPasswordChangedMessage(to=email, full_name=user.full_name())
        if not send_email(message):
            raise McAuthChangePasswordException(
                'The password has been changed, but I was unable to send an email notifying you about the change.'
            )
Beispiel #7
0
def delete_user(db: DatabaseHandler, email: str) -> None:
    """Delete user."""

    email = decode_object_from_bytes_if_needed(email)

    if not email:
        raise McAuthProfileException('Email address is empty.')

    # Check if user exists
    try:
        user_info(db=db, email=email)
    except Exception as _:
        raise McAuthProfileException(
            "User with email address '%s' does not exist." % email)

    # Delete the user (PostgreSQL's relation will take care of 'auth_users_roles_map')
    db.query(
        """
        DELETE FROM auth_users
        WHERE email = %(email)s
    """, {'email': email})
Beispiel #8
0
def regenerate_api_key(db: DatabaseHandler, email: str) -> None:
    """Regenerate API key -- creates new non-IP limited API key, removes all IP-limited API keys."""

    email = decode_object_from_bytes_if_needed(email)

    if not email:
        raise McAuthProfileException('Email address is empty.')

    # Check if user exists
    try:
        user = user_info(db=db, email=email)
    except Exception as _:
        raise McAuthProfileException(
            "User with email address '%s' does not exist." % email)

    db.begin()

    # Purge all IP-limited API keys
    db.query(
        """
        DELETE FROM auth_user_api_keys
        WHERE ip_address IS NOT NULL
          AND auth_users_id = (
            SELECT auth_users_id
            FROM auth_users
            WHERE email = %(email)s
          )
    """, {'email': email})

    # Regenerate non-IP limited API key
    db.query(
        """
        UPDATE auth_user_api_keys

        -- DEFAULT points to a generation function
        SET api_key = DEFAULT

        WHERE ip_address IS NULL
          AND auth_users_id = (
            SELECT auth_users_id
            FROM auth_users
            WHERE email = %(email)s
          )
    """, {'email': email})

    message = AuthAPIKeyResetMessage(to=email, full_name=user.full_name())
    if not send_email(message):
        db.rollback()
        raise McAuthProfileException(
            "Unable to send email about reset API key.")

    db.commit()
def change_password_with_reset_token(db: DatabaseHandler,
                                     email: str,
                                     password_reset_token: str,
                                     new_password: str,
                                     new_password_repeat: str) -> None:
    """Change password with a password token sent by email."""

    email = decode_object_from_bytes_if_needed(email)
    password_reset_token = decode_object_from_bytes_if_needed(password_reset_token)
    new_password = decode_object_from_bytes_if_needed(new_password)
    new_password_repeat = decode_object_from_bytes_if_needed(new_password_repeat)

    if not password_reset_token:
        raise McAuthChangePasswordException('Password reset token is empty.')

    # Check if user exists
    try:
        user_info(db=db, email=email)
    except Exception:
        raise McAuthChangePasswordException('User with email address "%s" does not exist.' % email)

    # Validate the token once more (was pre-validated in controller)
    if not password_reset_token_is_valid(db=db, email=email, password_reset_token=password_reset_token):
        raise McAuthChangePasswordException('Password reset token is invalid.')

    # Execute the change
    try:
        change_password(db=db, email=email, new_password=new_password, new_password_repeat=new_password_repeat)
    except Exception as ex:
        raise McAuthChangePasswordException("Unable to change password: %s" % str(ex))

    # Unset the password reset token
    db.query("""
        UPDATE auth_users
        SET password_reset_token_hash = NULL
        WHERE email = %(email)s
    """, {'email': email})
Beispiel #10
0
def regenerate_api_key(db: DatabaseHandler, email: str) -> None:
    """Regenerate API key -- creates new non-IP limited API key, removes all IP-limited API keys."""

    email = decode_object_from_bytes_if_needed(email)

    if not email:
        raise McAuthProfileException('Email address is empty.')

    # Check if user exists
    try:
        user = user_info(db=db, email=email)
    except Exception:
        raise McAuthProfileException("User with email address '%s' does not exist." % email)

    db.begin()

    # Purge all IP-limited API keys
    db.query("""
        DELETE FROM auth_user_api_keys
        WHERE ip_address IS NOT NULL
          AND auth_users_id = (
            SELECT auth_users_id
            FROM auth_users
            WHERE email = %(email)s
          )
    """, {'email': email})

    # Regenerate non-IP limited API key
    db.query("""
        UPDATE auth_user_api_keys

        -- DEFAULT points to a generation function
        SET api_key = DEFAULT

        WHERE ip_address IS NULL
          AND auth_users_id = (
            SELECT auth_users_id
            FROM auth_users
            WHERE email = %(email)s
          )
    """, {'email': email})

    message = AuthAPIKeyResetMessage(to=email, full_name=user.full_name())
    if not send_email(message):
        db.rollback()
        raise McAuthProfileException("Unable to send email about reset API key.")

    db.commit()
Beispiel #11
0
def activate_user_via_token(db: DatabaseHandler, email: str,
                            activation_token: str) -> None:
    """Change password with a password token sent by email."""

    email = decode_object_from_bytes_if_needed(email)
    activation_token = decode_object_from_bytes_if_needed(activation_token)

    if not email:
        raise McAuthRegisterException("Email is empty.")
    if not activation_token:
        raise McAuthRegisterException('Password reset token is empty.')

    # Validate the token once more (was pre-validated in controller)
    if not password_reset_token_is_valid(
            db=db, email=email, password_reset_token=activation_token):
        raise McAuthRegisterException('Activation token is invalid.')

    db.begin()

    # Set the password hash
    db.query(
        """
        UPDATE auth_users
        SET active = TRUE
        WHERE email = %(email)s
    """, {'email': email})

    # Unset the password reset token
    db.query(
        """
        UPDATE auth_users
        SET password_reset_token_hash = NULL
        WHERE email = %(email)s
    """, {'email': email})

    user = user_info(db=db, email=email)

    message = AuthActivatedMessage(to=email, full_name=user.full_name())
    if not send_email(message):
        db.rollback()
        raise McAuthRegisterException(
            "Unable to send email about an activated user.")

    db.commit()
Beispiel #12
0
def send_user_activation_token(db: DatabaseHandler,
                               email: str,
                               activation_link: str,
                               subscribe_to_newsletter: bool = False) -> None:
    """Prepare for activation by emailing the activation token."""

    email = decode_object_from_bytes_if_needed(email)
    activation_link = decode_object_from_bytes_if_needed(activation_link)
    if isinstance(subscribe_to_newsletter, bytes):
        subscribe_to_newsletter = decode_object_from_bytes_if_needed(
            subscribe_to_newsletter)

    subscribe_to_newsletter = bool(int(subscribe_to_newsletter))

    # Check if user exists
    try:
        user = user_info(db=db, email=email)
        full_name = user.full_name()

    except Exception as ex:
        log.warning("Unable to fetch user profile for user '%s': %s" % (
            email,
            str(ex),
        ))
        full_name = 'Nonexistent user'

    # If user was not found, send an email to a random address anyway to avoid timing attack
    full_activation_link = _generate_user_activation_token(
        db=db, email=email, activation_link=activation_link)
    if not full_activation_link:
        log.warning("Unable to generate full activation link for email '%s'" %
                    email)
        email = '*****@*****.**'
        full_activation_link = 'activation link'

    message = AuthActivationNeededMessage(
        to=email,
        full_name=full_name,
        activation_url=full_activation_link,
        subscribe_to_newsletter=subscribe_to_newsletter)
    if not send_email(message):
        raise McAuthRegisterException(
            'The user was created, but I was unable to send you an activation email.'
        )
Beispiel #13
0
def all_users(db: DatabaseHandler) -> List[CurrentUser]:
    """Fetch and return a list of users and their roles."""

    # Start a transaction so that the list of users doesn't change while we run separate queries with user_info()
    db.begin()

    user_emails = db.query("""
        SELECT email
        FROM auth_users
        ORDER BY auth_users_id
    """).flat()

    users = []

    for email in user_emails:
        users.append(user_info(db=db, email=email))

    db.commit()

    return users
Beispiel #14
0
def all_users(db: DatabaseHandler) -> List[CurrentUser]:
    """Fetch and return a list of users and their roles."""

    # Start a transaction so that the list of users doesn't change while we run separate queries with user_info()
    db.begin()

    user_emails = db.query("""
        SELECT email
        FROM auth_users
        ORDER BY auth_users_id
    """).flat()

    users = []

    for email in user_emails:
        users.append(user_info(db=db, email=email))

    db.commit()

    return users
Beispiel #15
0
def send_password_reset_token(db: DatabaseHandler, email: str,
                              password_reset_link: str) -> None:
    """Prepare for password reset by emailing the password reset token."""

    email = decode_object_from_bytes_if_needed(email)
    password_reset_link = decode_object_from_bytes_if_needed(
        password_reset_link)

    # Check if user exists
    try:
        user = user_info(db=db, email=email)
        full_name = user.full_name()

    except Exception as ex:
        log.warning("Unable to fetch user profile for user '%s': %s" % (
            email,
            str(ex),
        ))
        full_name = 'Nonexistent user'

    # If user was not found, send an email to a random address anyway to avoid timing attack
    full_password_reset_link = _generate_password_reset_token(
        db=db,
        email=email,
        password_reset_link=password_reset_link,
    )
    if not full_password_reset_link:
        log.warning(
            "Unable to generate full password reset link for email '%s'" %
            email)
        email = '*****@*****.**'
        full_password_reset_link = 'password reset link'

    message = AuthResetPasswordMessage(
        to=email,
        full_name=full_name,
        password_reset_url=full_password_reset_link)
    if not send_email(message):
        raise McAuthResetPasswordException(
            'Unable to send password reset email.')
Beispiel #16
0
def activate_user_via_token(db: DatabaseHandler, email: str, activation_token: str) -> None:
    """Change password with a password token sent by email."""

    email = decode_object_from_bytes_if_needed(email)
    activation_token = decode_object_from_bytes_if_needed(activation_token)

    if not email:
        raise McAuthRegisterException("Email is empty.")
    if not activation_token:
        raise McAuthRegisterException('Password reset token is empty.')

    # Validate the token once more (was pre-validated in controller)
    if not password_reset_token_is_valid(db=db, email=email, password_reset_token=activation_token):
        raise McAuthRegisterException('Activation token is invalid.')

    db.begin()

    # Set the password hash
    db.query("""
        UPDATE auth_users
        SET active = TRUE
        WHERE email = %(email)s
    """, {'email': email})

    # Unset the password reset token
    db.query("""
        UPDATE auth_users
        SET password_reset_token_hash = NULL
        WHERE email = %(email)s
    """, {'email': email})

    user = user_info(db=db, email=email)

    message = AuthActivatedMessage(to=email, full_name=user.full_name())
    if not send_email(message):
        db.rollback()
        raise McAuthRegisterException("Unable to send email about an activated user.")

    db.commit()
Beispiel #17
0
    def test_user_info(self):
        email = '*****@*****.**'
        full_name = 'Test user info'
        notes = 'Test test test'
        weekly_requests_limit = 123
        weekly_requested_items_limit = 456

        add_user(
            db=self.db(),
            new_user=NewUser(
                email=email,
                full_name=full_name,
                notes=notes,
                role_ids=[1],
                active=True,
                password='******',
                password_repeat='user_info',
                activation_url='',  # user is active, no need for activation URL
                weekly_requests_limit=weekly_requests_limit,
                weekly_requested_items_limit=weekly_requested_items_limit,
            ),
        )

        user = user_info(db=self.db(), email=email)

        assert isinstance(user, CurrentUser)
        assert user.email() == email
        assert user.full_name() == full_name
        assert user.notes() == notes
        assert user.weekly_requests_limit() == weekly_requests_limit
        assert user.weekly_requested_items_limit() == weekly_requested_items_limit
        assert user.active()
        assert user.created_date()
        assert self.__looks_like_iso8601_date(user.created_date())
        assert user.global_api_key()
        assert user.password_hash()
        assert user.has_role('admin')
Beispiel #18
0
def send_user_activation_token(db: DatabaseHandler,
                               email: str,
                               activation_link: str,
                               subscribe_to_newsletter: bool = False) -> None:
    """Prepare for activation by emailing the activation token."""

    email = decode_object_from_bytes_if_needed(email)
    activation_link = decode_object_from_bytes_if_needed(activation_link)
    if isinstance(subscribe_to_newsletter, bytes):
        subscribe_to_newsletter = decode_object_from_bytes_if_needed(subscribe_to_newsletter)

    subscribe_to_newsletter = bool(int(subscribe_to_newsletter))

    # Check if user exists
    try:
        user = user_info(db=db, email=email)
        full_name = user.full_name()

    except Exception as ex:
        log.warning("Unable to fetch user profile for user '%s': %s" % (email, str(ex),))
        full_name = 'Nonexistent user'

    # If user was not found, send an email to a random address anyway to avoid timing attack
    full_activation_link = _generate_user_activation_token(db=db, email=email, activation_link=activation_link)
    if not full_activation_link:
        log.warning("Unable to generate full activation link for email '%s'" % email)
        email = '*****@*****.**'
        full_activation_link = 'activation link'

    message = AuthActivationNeededMessage(
        to=email,
        full_name=full_name,
        activation_url=full_activation_link,
        subscribe_to_newsletter=subscribe_to_newsletter
    )
    if not send_email(message):
        raise McAuthRegisterException('The user was created, but I was unable to send you an activation email.')
Beispiel #19
0
def login_with_email_password(db: DatabaseHandler, email: str, password: str, ip_address: str = None) -> CurrentUser:
    """Log in with username and password; raise on unsuccessful login."""

    email = decode_object_from_bytes_if_needed(email)
    password = decode_object_from_bytes_if_needed(password)

    if not (email and password):
        raise McAuthLoginException("Email and password must be defined.")

    # Try-except block because we don't want to reveal the specific reason why the login has failed
    try:

        user = user_info(db=db, email=email)

        # Check if user has tried to log in unsuccessfully before and now is trying
        # again too fast
        if __user_is_trying_to_login_too_soon(db=db, email=email):
            raise McAuthLoginException(
                "User '%s' is trying to log in too soon after the last unsuccessful attempt." % email
            )

        if not password_hash_is_valid(password_hash=user.password_hash(), password=password):
            raise McAuthLoginException("Password for user '%s' is invalid." % email)

    except Exception as ex:
        log.info(
            "Login failed for %(email)s, will delay any successive login attempt for %(delay)d seconds: %(exc)s" % {
                'email': email,
                'delay': __POST_UNSUCCESSFUL_LOGIN_DELAY,
                'exc': str(ex),
            }
        )

        # Set the unsuccessful login timestamp
        # (TIMESTAMP 'now' returns "current transaction's start time", so using LOCALTIMESTAMP instead)
        db.query("""
            UPDATE auth_users
            SET last_unsuccessful_login_attempt = LOCALTIMESTAMP
            WHERE email = %(email)s
        """, {'email': email})

        # It might make sense to time.sleep() here for the duration of $POST_UNSUCCESSFUL_LOGIN_DELAY seconds to prevent
        # legitimate users from trying to log in too fast. However, when being actually brute-forced through multiple
        # HTTP connections, this approach might end up creating a lot of processes that would time.sleep() and take up
        # memory.
        #
        # So, let's return the error page ASAP and hope that a legitimate user won't be able to reenter his / her
        # password before the $POST_UNSUCCESSFUL_LOGIN_DELAY amount of seconds pass.

        # Don't give out a specific reason for the user to not be able to find
        # out which user emails are registered
        raise McAuthLoginException("User '%s' was not found or password is incorrect." % email)

    if not user.active():
        raise McAuthLoginException("User with email '%s' is not active." % email)

    # Reset password reset token (if any)
    db.query("""
        UPDATE auth_users
        SET password_reset_token_hash = NULL
        WHERE email = %(email)s
          AND password_reset_token_hash IS NOT NULL
    """, {'email': email})

    if ip_address:
        if not user.api_key_for_ip_address(ip_address):
            db.create(
                table='auth_user_api_keys',
                insert_hash={
                    'auth_users_id': user.user_id(),
                    'ip_address': ip_address,
                })

            # Fetch user again
            user = user_info(db=db, email=email)

            if not user.api_key_for_ip_address(ip_address):
                raise McAuthLoginException("Unable to create per-IP API key for IP %s" % ip_address)

    return user
Beispiel #20
0
def update_user(db: DatabaseHandler, user_updates: ModifyUser) -> None:
    """Update an existing user."""

    if not user_updates:
        raise McAuthProfileException("Existing user is undefined.")

    # Check if user exists
    try:
        user = user_info(db=db, email=user_updates.email())
    except Exception:
        raise McAuthProfileException('User with email address "%s" does not exist.' % user_updates.email())

    db.begin()

    if user_updates.full_name() is not None:
        db.query("""
            UPDATE auth_users
            SET full_name = %(full_name)s
            WHERE email = %(email)s
        """, {
            'full_name': user_updates.full_name(),
            'email': user_updates.email(),
        })

    if user_updates.notes() is not None:
        db.query("""
            UPDATE auth_users
            SET notes = %(notes)s
            WHERE email = %(email)s
        """, {
            'notes': user_updates.notes(),
            'email': user_updates.email(),
        })

    if user_updates.active() is not None:
        db.query("""
            UPDATE auth_users
            SET active = %(active)s
            WHERE email = %(email)s
        """, {
            'active': bool(int(user_updates.active())),
            'email': user_updates.email(),
        })

    if user_updates.password() is not None:
        try:
            change_password(
                db=db,
                email=user_updates.email(),
                new_password=user_updates.password(),
                new_password_repeat=user_updates.password_repeat(),
                do_not_inform_via_email=True,
            )
        except Exception as ex:
            db.rollback()
            raise McAuthProfileException("Unable to change password: %s" % str(ex))

    if user_updates.weekly_requests_limit() is not None:
        db.query("""
            UPDATE auth_user_limits
            SET weekly_requests_limit = %(weekly_requests_limit)s
            WHERE auth_users_id = %(auth_users_id)s
        """, {
            'weekly_requests_limit': user_updates.weekly_requests_limit(),
            'auth_users_id': user.user_id(),
        })

    if user_updates.weekly_requested_items_limit() is not None:
        db.query("""
            UPDATE auth_user_limits
            SET weekly_requested_items_limit = %(weekly_requested_items_limit)s
            WHERE auth_users_id = %(auth_users_id)s
        """, {
            'weekly_requested_items_limit': user_updates.weekly_requested_items_limit(),
            'auth_users_id': user.user_id(),
        })

    if user_updates.role_ids() is not None:
        db.query("""
            DELETE FROM auth_users_roles_map
            WHERE auth_users_id = %(auth_users_id)s
        """, {'auth_users_id': user.user_id()})

        for auth_roles_id in user_updates.role_ids():
            db.insert(table='auth_users_roles_map', insert_hash={
                'auth_users_id': user.user_id(),
                'auth_roles_id': auth_roles_id,
            })

    db.commit()
def change_password(db: DatabaseHandler,
                    email: str,
                    new_password: str,
                    new_password_repeat: str,
                    do_not_inform_via_email: bool = False) -> None:
    """Change user's password."""

    email = decode_object_from_bytes_if_needed(email)
    new_password = decode_object_from_bytes_if_needed(new_password)
    new_password_repeat = decode_object_from_bytes_if_needed(
        new_password_repeat)

    if isinstance(do_not_inform_via_email, bytes):
        do_not_inform_via_email = decode_object_from_bytes_if_needed(
            do_not_inform_via_email)

    do_not_inform_via_email = bool(int(do_not_inform_via_email))

    # Check if user exists
    try:
        user = user_info(db=db, email=email)
    except Exception:
        raise McAuthChangePasswordException(
            'User with email address "%s" does not exist.' % email)

    password_validation_message = validate_new_password(
        email=email,
        password=new_password,
        password_repeat=new_password_repeat)
    if password_validation_message:
        raise McAuthChangePasswordException("Unable to change password: %s" %
                                            password_validation_message)

    # Hash + validate the password
    try:
        password_new_hash = generate_secure_hash(password=new_password)
    except Exception as ex:
        raise McAuthChangePasswordException(
            "Unable to hash a new password: %s" % str(ex))

    if not password_new_hash:
        raise McAuthChangePasswordException(
            "Generated password hash is empty.")

    # Set the password hash
    db.query(
        """
        UPDATE auth_users
        SET password_hash = %(password_hash)s,
            active = TRUE
        WHERE email = %(email)s
    """, {
            'email': email,
            'password_hash': password_new_hash,
        })

    if not do_not_inform_via_email:

        message = AuthPasswordChangedMessage(to=email,
                                             full_name=user.full_name())
        if not send_email(message):
            raise McAuthChangePasswordException(
                'The password has been changed, but I was unable to send an email notifying you about the change.'
            )
Beispiel #22
0
def add_user(db: DatabaseHandler, new_user: NewUser) -> None:
    """Add new user."""

    if not new_user:
        raise McAuthRegisterException("New user is undefined.")

    # Check if user already exists
    user_exists = db.query("""
        SELECT auth_users_id
        FROM auth_users
        WHERE email = %(email)s
        LIMIT 1
    """, {'email': new_user.email()}).hash()

    if user_exists is not None and 'auth_users_id' in user_exists:
        raise McAuthRegisterException("User with email '%s' already exists." % new_user.email())

    # Hash + validate the password
    try:
        password_hash = generate_secure_hash(password=new_user.password())
        if not password_hash:
            raise McAuthRegisterException("Password hash is empty.")
    except Exception as ex:
        log.error("Unable to hash a new password: {}".format(ex))
        raise McAuthRegisterException('Unable to hash a new password.')

    db.begin()

    # Create the user
    db.create(
        table='auth_users',
        insert_hash={
            'email': new_user.email(),
            'password_hash': password_hash,
            'full_name': new_user.full_name(),
            'notes': new_user.notes(),
            'active': bool(int(new_user.active())),
        }
    )

    # Fetch the user's ID
    try:
        user = user_info(db=db, email=new_user.email())
    except Exception as ex:
        db.rollback()
        raise McAuthRegisterException("I've attempted to create the user but it doesn't exist: %s" % str(ex))

    # Create roles
    try:
        for auth_roles_id in new_user.role_ids():
            db.create(table='auth_users_roles_map', insert_hash={
                'auth_users_id': user.user_id(),
                'auth_roles_id': auth_roles_id,
            })
    except Exception as ex:
        raise McAuthRegisterException("Unable to create roles: %s" % str(ex))

    # Update limits (if they're defined)
    if new_user.weekly_requests_limit() is not None:
        db.query("""
            UPDATE auth_user_limits
            SET weekly_requests_limit = %(weekly_requests_limit)s
            WHERE auth_users_id = %(auth_users_id)s
        """, {
            'auth_users_id': user.user_id(),
            'weekly_requests_limit': new_user.weekly_requests_limit(),
        })

    if new_user.weekly_requested_items_limit() is not None:
        db.query("""
            UPDATE auth_user_limits
            SET weekly_requested_items_limit = %(weekly_requested_items_limit)s
            WHERE auth_users_id = %(auth_users_id)s
        """, {
            'auth_users_id': user.user_id(),
            'weekly_requested_items_limit': new_user.weekly_requested_items_limit(),
        })

    # Subscribe to newsletter
    if new_user.subscribe_to_newsletter():
        db.create(table='auth_users_subscribe_to_newsletter', insert_hash={'auth_users_id': user.user_id()})

    if not new_user.active():
        send_user_activation_token(
            db=db,
            email=new_user.email(),
            activation_link=new_user.activation_url(),
            subscribe_to_newsletter=new_user.subscribe_to_newsletter(),
        )

    db.commit()
Beispiel #23
0
def login_with_api_key(db: DatabaseHandler, api_key: str, ip_address: str) -> CurrentUser:
    """Fetch user object for the API key. Only active users are fetched."""

    api_key = decode_object_from_bytes_if_needed(api_key)
    ip_address = decode_object_from_bytes_if_needed(ip_address)

    if not api_key:
        raise McAuthLoginException("API key is undefined.")

    if not ip_address:
        # Even if provided API key is the global one, we want the IP address
        raise McAuthLoginException("IP address is undefined.")

    api_key_user = db.query("""
        SELECT auth_users.email
        FROM auth_users
            INNER JOIN auth_user_api_keys
                ON auth_users.auth_users_id = auth_user_api_keys.auth_users_id
        WHERE
            (
                auth_user_api_keys.api_key = %(api_key)s AND
                (
                    auth_user_api_keys.ip_address IS NULL
                    OR
                    auth_user_api_keys.ip_address = %(ip_address)s
                )
            )

        GROUP BY auth_users.auth_users_id,
                 auth_users.email
        ORDER BY auth_users.auth_users_id
        LIMIT 1
    """, {
        'api_key': api_key,
        'ip_address': ip_address,
    }).hash()

    if api_key_user is None or len(api_key_user) == 0:
        raise McAuthLoginException("Unable to find user for API key '%s' and IP address '%s'" % (api_key, ip_address,))

    email = api_key_user['email']

    # Check if user has tried to log in unsuccessfully before and now is trying again too fast
    if __user_is_trying_to_login_too_soon(db=db, email=email):
        raise McAuthLoginException(
            "User '%s' is trying to log in too soon after the last unsuccessful attempt." % email
        )

    user = user_info(db=db, email=email)

    # Reset password reset token (if any)
    db.query("""
        UPDATE auth_users
        SET password_reset_token_hash = NULL
        WHERE email = %(email)s
          AND password_reset_token_hash IS NOT NULL
    """, {'email': email})

    if not user.active():
        raise McAuthLoginException("User '%s' for API key '%s' is not active." % (email, api_key,))

    return user
Beispiel #24
0
def update_user(db: DatabaseHandler, user_updates: ModifyUser) -> None:
    """Update an existing user."""

    if not user_updates:
        raise McAuthProfileException("Existing user is undefined.")

    # Check if user exists
    try:
        user = user_info(db=db, email=user_updates.email())
    except Exception as _:
        raise McAuthProfileException(
            'User with email address "%s" does not exist.' %
            user_updates.email())

    db.begin()

    if user_updates.full_name() is not None:
        db.query(
            """
            UPDATE auth_users
            SET full_name = %(full_name)s
            WHERE email = %(email)s
        """, {
                'full_name': user_updates.full_name(),
                'email': user_updates.email(),
            })

    if user_updates.notes() is not None:
        db.query(
            """
            UPDATE auth_users
            SET notes = %(notes)s
            WHERE email = %(email)s
        """, {
                'notes': user_updates.notes(),
                'email': user_updates.email(),
            })

    if user_updates.active() is not None:
        db.query(
            """
            UPDATE auth_users
            SET active = %(active)s
            WHERE email = %(email)s
        """, {
                'active': bool(int(user_updates.active())),
                'email': user_updates.email(),
            })

    if user_updates.password() is not None:
        try:
            change_password(
                db=db,
                email=user_updates.email(),
                new_password=user_updates.password(),
                new_password_repeat=user_updates.password_repeat(),
                do_not_inform_via_email=True,
            )
        except Exception as ex:
            db.rollback()
            raise McAuthProfileException("Unable to change password: %s" %
                                         str(ex))

    if user_updates.weekly_requests_limit() is not None:
        db.query(
            """
            UPDATE auth_user_limits
            SET weekly_requests_limit = %(weekly_requests_limit)s
            WHERE auth_users_id = %(auth_users_id)s
        """, {
                'weekly_requests_limit': user_updates.weekly_requests_limit(),
                'auth_users_id': user.user_id(),
            })

    if user_updates.weekly_requested_items_limit() is not None:
        db.query(
            """
            UPDATE auth_user_limits
            SET weekly_requested_items_limit = %(weekly_requested_items_limit)s
            WHERE auth_users_id = %(auth_users_id)s
        """, {
                'weekly_requested_items_limit':
                user_updates.weekly_requested_items_limit(),
                'auth_users_id':
                user.user_id(),
            })

    if user_updates.role_ids() is not None:
        db.query(
            """
            DELETE FROM auth_users_roles_map
            WHERE auth_users_id = %(auth_users_id)s
        """, {'auth_users_id': user.user_id()})

        for auth_roles_id in user_updates.role_ids():
            db.insert(table='auth_users_roles_map',
                      insert_hash={
                          'auth_users_id': user.user_id(),
                          'auth_roles_id': auth_roles_id,
                      })

    db.commit()
Beispiel #25
0
def add_user(db: DatabaseHandler, new_user: NewUser) -> None:
    """Add new user."""

    if not new_user:
        raise McAuthRegisterException("New user is undefined.")

    # Check if user already exists
    user_exists = db.query(
        """
        SELECT auth_users_id
        FROM auth_users
        WHERE email = %(email)s
        LIMIT 1
    """, {
            'email': new_user.email()
        }).hash()

    if user_exists is not None and 'auth_users_id' in user_exists:
        raise McAuthRegisterException("User with email '%s' already exists." %
                                      new_user.email())

    # Hash + validate the password
    try:
        password_hash = generate_secure_hash(password=new_user.password())
        if not password_hash:
            raise McAuthRegisterException("Password hash is empty.")
    except Exception as _:
        raise McAuthRegisterException('Unable to hash a new password.')

    db.begin()

    # Create the user
    db.create(table='auth_users',
              insert_hash={
                  'email': new_user.email(),
                  'password_hash': password_hash,
                  'full_name': new_user.full_name(),
                  'notes': new_user.notes(),
                  'active': bool(int(new_user.active())),
              })

    # Fetch the user's ID
    try:
        user = user_info(db=db, email=new_user.email())
    except Exception as ex:
        db.rollback()
        raise McAuthRegisterException(
            "I've attempted to create the user but it doesn't exist: %s" %
            str(ex))

    # Create roles
    try:
        for auth_roles_id in new_user.role_ids():
            db.create(table='auth_users_roles_map',
                      insert_hash={
                          'auth_users_id': user.user_id(),
                          'auth_roles_id': auth_roles_id,
                      })
    except Exception as ex:
        raise McAuthRegisterException("Unable to create roles: %s" % str(ex))

    # Update limits (if they're defined)
    if new_user.weekly_requests_limit() is not None:
        db.query(
            """
            UPDATE auth_user_limits
            SET weekly_requests_limit = %(weekly_requests_limit)s
            WHERE auth_users_id = %(auth_users_id)s
        """, {
                'auth_users_id': user.user_id(),
                'weekly_requests_limit': new_user.weekly_requests_limit(),
            })

    if new_user.weekly_requested_items_limit() is not None:
        db.query(
            """
            UPDATE auth_user_limits
            SET weekly_requested_items_limit = %(weekly_requested_items_limit)s
            WHERE auth_users_id = %(auth_users_id)s
        """, {
                'auth_users_id':
                user.user_id(),
                'weekly_requested_items_limit':
                new_user.weekly_requested_items_limit(),
            })

    # Subscribe to newsletter
    if new_user.subscribe_to_newsletter():
        db.create(table='auth_users_subscribe_to_newsletter',
                  insert_hash={'auth_users_id': user.user_id()})

    if not new_user.active():
        send_user_activation_token(
            db=db,
            email=new_user.email(),
            activation_link=new_user.activation_url(),
            subscribe_to_newsletter=new_user.subscribe_to_newsletter(),
        )

    db.commit()