Example #1
0
def user_create(
    context: object,
    username: str,
    password: str,
    email: str,
    first_name: str,
    last_name: str,
    role: str,
) -> None:
    try:
        config_setup(context)
        user = User(
            username=username,
            password=password,
            first_name=first_name,
            last_name=last_name,
            email=email,
            role=role if role else "",
        )
        user.add()
        if user.is_admin():
            click.echo(f"Admin user {username} registered")
        else:
            click.echo(f"User {username} registered")
        rv = 0
    except Exception as exc:
        click.echo(exc, err=True)
        rv = 2 if isinstance(exc, BadConfig) else 1

    click.get_current_context().exit(rv)
Example #2
0
 def test_dataset_survives_user(self, db_session, create_user):
     """The Dataset isn't automatically removed when the referenced
     user is removed.
     """
     user = create_user
     ds = Dataset(owner=user.username, controller="frodo", name="fio")
     ds.add()
     User.delete(username=user.username)
     ds1 = Dataset.attach(controller="frodo", name="fio")
     assert ds1 == ds
Example #3
0
def create_user() -> User:
    user = User(
        email="*****@*****.**",
        password="******",
        username="******",
        first_name="Test",
        last_name="Account",
    )
    user.add()
    return user
Example #4
0
def user_delete(context: object, username: str) -> None:
    try:
        # Delete the the user with specified username
        config_setup(context)
        User.delete(username=username)
        rv = 0
    except Exception as exc:
        click.echo(exc, err=True)
        rv = 2 if isinstance(exc, BadConfig) else 1

    click.get_current_context().exit(rv)
Example #5
0
    def delete(self, username):
        """
        Delete request for deleting a user from database.
        This requires a Pbench auth token in the header field

        Required headers include

            Content-Type:   application/json
            Accept:         application/json
            Authorization:   Bearer Pbench_auth_token (user received upon login)

        :return:
            Success: 200 with empty payload
            Failure: <status_Code>,
                    response_object = {
                        "message": "failure message"
                    }
        """
        try:
            user, verified = self.auth.verify_user(username)
        except Exception:
            self.logger.exception("Exception occurred during the getUser {}",
                                  username)
            abort(500, message="INTERNAL ERROR")

        # TODO: Check if the user has the right privileges
        if not verified and not user.is_admin():
            self.logger.warning(
                "User {} is not authorized to delete user {}.",
                user.username,
                username,
            )
            abort(403, message="Not authorized to delete user")

        try:
            user = User.query(username=username)
            # Do not delete if the user is admin
            if not user.is_admin():
                User.delete(username)
        except Exception:
            self.logger.exception(
                "Exception occurred during deleting the user entry for user '{}'",
                username,
            )
            abort(500, message="INTERNAL ERROR")
        else:
            if user.is_admin():
                self.logger.warning("Admin attempted to delete admin user")
                abort(403, message="Admin user can not be deleted")
            self.logger.info("User entry deleted for user with username {}",
                             username)

        return "", 200
Example #6
0
 def test_registration_with_registered_user(client, server_config):
     """ Test registration with already registered email"""
     user = User(
         email="*****@*****.**",
         password="******",
         username="******",
         first_name="firstname",
         last_name="lastname",
     )
     Database.db_session.add(user)
     Database.db_session.commit()
     with client:
         response = register_user(
             client,
             server_config,
             username="******",
             firstname="firstname",
             lastname="lastName",
             email="*****@*****.**",
             password="******",
         )
         data = response.json
         assert data["message"] == "Provided username is already in use."
         assert response.content_type == "application/json"
         assert response.status_code == 403
Example #7
0
    def validate_user(name: str) -> str:
        """
        Encapsulate a query to reject "username" values that don't correspond to
        a registered user. A valid username is translated to the internal
        representation for Elasticsearch indexing, which is the stringified
        user ID number.

        Args:
            name: The username field of a registered user

        Raises:
            ValueError: The username doesn't correspond to a registered user
            TypeError: Some other error occurred looking for the user

        Returns:
            The user's ID value (as a string)
        """
        try:
            user = User.query(username=name)
        except Exception:
            User.logger.exception(
                "Unexpected exception from query for user {}", name)
            raise
        if not user:
            raise UnknownUser(name)
        return str(user.id)
Example #8
0
    def delete(self, target_username):
        """
        Delete request for deleting a user from database.
        This requires a Pbench auth token in the header field

        Required headers include

            Content-Type:   application/json
            Accept:         application/json
            Authorization:   Bearer Pbench_auth_token (user received upon login)

        :return:
            Success: 200 with empty payload
            Failure: <status_Code>,
                    response_object = {
                        "message": "failure message"
                    }
        """
        result = self.get_valid_target_user(target_username, "DELETE")
        if not result.target_user:
            abort(result.http_status, message=result.http_message)
        # Do not allow admin user to get self deleted via API
        if (result.target_user.is_admin()
                and self.auth.token_auth.current_user() == result.target_user):
            self.logger.warning(
                "Admin user is not allowed to self delete via API call. Username: {}",
                target_username,
            )
            abort(HTTPStatus.FORBIDDEN,
                  message="Not authorized to delete user")

        # If target user is a valid and not an admin proceed to delete
        try:
            User.delete(target_username)
            self.logger.info(
                "User entry deleted for user with username: {}, by user: {}",
                target_username,
                self.auth.token_auth.current_user().username,
            )
        except Exception:
            self.logger.exception(
                "Exception occurred while deleting a user {}",
                target_username,
            )
            abort(HTTPStatus.INTERNAL_SERVER_ERROR, message="INTERNAL ERROR")

        return "", HTTPStatus.OK
Example #9
0
    def process(self, link: str, state: States) -> int:
        """
        process Create Dataset records for pre-existing server tarballs that
        are in a specified filesystem "state" (the link directory in the
        archive tree), in a specified Dataset state.

        Each tarball for which a Dataset record already exists is IGNORED,
        and we don't attempt to advance the state.

        Args:
            :link (str):        Filesystem "state" link directory
                                (e.g., TO-INDEX)
            :state (States):    A state enum value representing desired Dataset
                                state.

        Returns:
            int: Status (0 success, 1 failure)
        """
        logger = self.logger
        done = 0
        fail = 0
        ignore = 0
        args = {}
        owner = User.validate_user(self.options.user)

        for tarball in self._collect_tb(link):
            if self.options.verify:
                print(f"Processing {tarball} from {link} -> state {state}")
            try:
                args["path"] = tarball
                try:
                    dataset = Dataset.attach(**args)
                    if self.options.verify:
                        print(f"Found existing {dataset}: {dataset.state}")
                    ignore = ignore + 1
                except DatasetNotFound:
                    a = args.copy()
                    a["md5"] = open(f"{tarball}.md5").read().split()[0]

                    # NOTE: including "state" on attach above would attempt to
                    # advance the dataset's state, which we don't want for
                    # import, so we add it only here. "owner" would be ignored
                    # by attach, but we add it here anyway for clarity.
                    a["state"] = state
                    a["owner"] = owner
                    dataset = Dataset.create(**a)
                    print(f"Imported {dataset}: {state}")
                    done = done + 1
            except Exception as e:
                # Stringify any exception and report it; then fail
                logger.exception("Import of dataset {} failed", tarball)
                print(f"{_NAME_}: dataset {tarball} failed with {e}",
                      file=sys.stderr)
                fail = fail + 1
        print(
            f"Imported {done} datasets from {link} with {fail} errors and {ignore} ignored"
        )
        return 1 if fail > 0 else 0
Example #10
0
    def get(self, username):
        """
        Get request for getting user data.
        This requires a Pbench auth token in the header field

        Required headers include

            Content-Type:   application/json
            Accept:         application/json
            Authorization:  Bearer Pbench_auth_token (user received upon login)

        :return: JSON Payload
            Success: 200,
                    response_object = {
                        "username": <username>,
                        "first_name": <firstName>,
                        "last_name": <lastName>,
                        "registered_on": <registered_on>,
                    }
            Failure: <status_Code>,
                    response_object = {
                        "message": "failure message"
                    }
        """
        try:
            user, verified = self.auth.verify_user(username)
        except Exception:
            self.logger.exception(
                "Exception occurred during verifying the user")
            abort(500, message="INTERNAL ERROR")

        # TODO: Check if the user has the right privileges
        if verified:
            response_object = user.get_json()
            return make_response(jsonify(response_object), 200)
        elif user.is_admin():
            try:
                target_user = User.query(username=username)
                response_object = target_user.get_json()
                return make_response(jsonify(response_object), 200)
            except Exception:
                self.logger.exception(
                    "Exception occurred while querying the user. Username: {}",
                    username)
                abort(500, message="INTERNAL ERROR")
        else:
            self.logger.warning(
                "User {} is not authorized to get user {}.",
                user.username,
                username,
            )
            abort(
                403,
                message=
                f"Not authorized to access information about user {username}",
            )
Example #11
0
 def ok(auth: Auth, username: str) -> User:
     user = User(
         id=1,
         username=username,
         password="******",
         first_name="first_name",
         last_name="last_name",
         email="*****@*****.**",
     )
     return user
Example #12
0
def create_user():
    user = User(
        username=TestUserManagement.USER_TEXT,
        password=TestUserManagement.PSWD_TEXT,
        first_name=TestUserManagement.FIRST_NAME_TEXT,
        last_name=TestUserManagement.LAST_NAME_TEXT,
        email=TestUserManagement.EMAIL_TEXT,
        registered_on=TestUserManagement.USER_CREATE_TIMESTAMP,
    )
    return user
Example #13
0
    def execute(self):
        config = PbenchServerConfig(self.context.config)

        logger = get_pbench_logger(_NAME_, config)

        # We're going to need the Postgres DB to track dataset state, so setup
        # DB access.
        Database.init_db(config, logger)

        user = User(
            username=self.context.username,
            password=self.context.password,
            first_name=self.context.first_name,
            last_name=self.context.last_name,
            email=self.context.email,
            role=self.context.role if self.context.role else "",
        )

        user.add()
        if user.is_admin():
            click.echo(f"Admin user {self.context.username} registered")
        else:
            click.echo(f"User {self.context.username} registered")
Example #14
0
def attach_dataset(monkeypatch, pbench_token, create_user):
    """
    Mock a Dataset attach call to return an object. We mock the Dataset.attach
    method to avoid DB access here, however the user authentication mechanism
    is not yet mocked so we have to look up User data.

    Args:
        monkeypatch: patching fixture
        pbench_token: create a "drb" user for testing
        create_user: create a "test" user
    """
    datasets = {}
    drb = User.query(username="******")  # Created by pbench_token fixture
    test = User.query(username="******")  # Created by create_user fixture
    datasets["drb"] = Dataset(
        owner=drb,
        owner_id=drb.id,
        controller="node",
        name="drb",
        access="private",
        id=1,
    )
    datasets["test"] = Dataset(
        owner=test,
        owner_id=test.id,
        controller="node",
        name="test",
        access="private",
        id=2,
    )

    def attach_dataset(controller: str, name: str) -> Dataset:
        return datasets[name]

    with monkeypatch.context() as m:
        m.setattr(Dataset, "attach", attach_dataset)
        yield
Example #15
0
    def verify_user(self, username):
        """
        Check if the provided username belongs to the current user by
        querying the Usermodel with the current user
        :param username:
        :param logger
        :return: User (UserModel instance), verified status (boolean)
        """
        user = User.query(id=self.token_auth.current_user().id)
        # check if the current username matches with the one provided
        verified = user is not None and user.username == username
        Auth.logger.warning("verified status of user '{}' is '{}'", username,
                            verified)

        return user, verified
Example #16
0
def user_update(
    context: object,
    updateuser: str,
    username: str,
    first_name: str,
    last_name: str,
    email: str,
    role: str,
) -> None:
    try:
        config_setup(context)
        # Query the user
        user = User.query(username=updateuser)

        if user is None:
            click.echo(f"User {updateuser} doesn't exist")
            rv = 1
        else:
            dict_to_update = {}
            if username:
                dict_to_update["username"] = username

            if first_name:
                dict_to_update["first_name"] = first_name

            if last_name:
                dict_to_update["last_name"] = last_name

            if email:
                dict_to_update["email"] = email

            if role:
                dict_to_update["role"] = role

            # Update the user
            user.update(**dict_to_update)

            click.echo(f"User {updateuser} updated")
            rv = 0
    except Exception as exc:
        click.echo(exc, err=True)
        rv = 2 if isinstance(exc, BadConfig) else 1

    click.get_current_context().exit(rv)
Example #17
0
    def get_valid_target_user(self, target_username: str,
                              request_type: str) -> "UserAPI.TargetUser":
        """
        Helper function to determine whether the API call is permitted for the target username
        Right now it is only permitted for an admin user and the target user itself.
        This returns a target User on success or None on failure; in the case of failure,
        also returns the corresponding HTTP status code and message string
        """
        current_user = self.auth.token_auth.current_user()
        if current_user.username == target_username:
            return UserAPI.TargetUser(target_user=current_user,
                                      http_status=HTTPStatus.OK,
                                      http_message="")
        if current_user.is_admin():
            target_user = User.query(username=target_username)
            if target_user:
                return UserAPI.TargetUser(target_user=target_user,
                                          http_status=HTTPStatus.OK,
                                          http_message="")

            self.logger.warning(
                "User {} requested {} operation but user {} is not found.",
                current_user.username,
                request_type,
                target_username,
            )
            return UserAPI.TargetUser(
                target_user=None,
                http_status=HTTPStatus.NOT_FOUND,
                http_message=f"User {target_username} not found",
            )

        self.logger.warning(
            "User {} is not authorized to {} user {}.",
            current_user.username,
            request_type,
            target_username,
        )
        return UserAPI.TargetUser(
            target_user=None,
            http_status=HTTPStatus.FORBIDDEN,
            http_message=f"Not authorized to access user {target_username}",
        )
Example #18
0
    def verify_user(self, target_username: str) -> User:
        """
        Check whether the requested target user is the owner of the authorization
        token provided to the API.

        We're returning a User instance corresponding to the target username provided.
        If the request is not authenticated (current user is None) we return None as
        we won't be able to verify the target user. However, if the current user is
        an admin, we return the instance of the target user by querying the target
        username provided.
        """
        current_user = Auth.token_auth.current_user()
        if not current_user:
            return None
        if current_user.username == target_username:
            return current_user
        if current_user.is_admin():
            target_user = User.query(username=target_username)
            return target_user
        return None
Example #19
0
    def preprocess(self, client_json: JSON) -> CONTEXT:
        """
        Query the Dataset associated with this name, and determine whether the
        authenticated user has UPDATE access to this dataset. (Currently, this
        means the authenticated user is the owner of the dataset, or has ADMIN
        role.)

        If the user has authorization to update the dataset, return the dataset
        object as CONTEXT so that the postprocess operation can mark it as
        published.

        Args:
            json_data: JSON dictionary of type-normalized key-value pairs
                controller: the controller that generated the dataset
                name: name of the dataset to publish
                access: The desired access level of the dataset (currently either
                    "private" or "public")

        Returns:
            CONTEXT referring to the dataset object if the operation should
            continue, or None
        """
        dataset = Dataset.attach(controller=client_json["controller"],
                                 name=client_json["name"])
        owner = User.query(id=dataset.owner_id)
        if not owner:
            self.logger.error("Dataset owner ID {} cannot be found in Users",
                              dataset.owner_id)
            abort(HTTPStatus.INTERNAL_SERVER_ERROR,
                  message="Dataset owner not found")

        # For publish, we check authorization against the ownership of the
        # dataset that was selected rather than having an explicit "user"
        # JSON parameter. This will raise UnauthorizedAccess on failure.
        self._check_authorization(owner.username, client_json["access"])

        # The dataset exists, so continue the operation with the appropriate
        # CONTEXT.
        return {"dataset": dataset}
Example #20
0
    def validate_owner(self, key: str, value: Any) -> User:
        """
        Validate and translate owner name to User object

        Args:
            key: owner
            value: username

        Raises:
            DatasetBadParameter: the owner value given doesn't resolve to a
                Pbench username.

        Returns:
            User object
        """
        if type(value) is User:
            return value
        elif type(value) is str:
            user = User.query(username=value)
            if user:
                return user
        raise DatasetBadParameterType(value, "username")
Example #21
0
    def validate_user(name: str) -> str:
        """
        Encapsulate a query to reject "username" values that don't correspond to
        a registered user.

        TODO: We need to decide exactly how we're representing our users, and
        what's mutable. For example, if we allow changing "username" we need to
        have a stable "userid" field that we use for indexing... in which case
        this should translate "username" into "userid" for internal use. For
        now, just return the original username if it was found.

        FIXME: I thought this would be an ArgumentParser "type" so we could
        do validation during parsing. That's awkward because we need to finish
        parsing the --config parameter in order to access the config and logger
        objects required to initialize database access, which needs to be done
        before we can make a query. Can we work around this without too much
        mess?

        Args:
            :name: The username field of a registered user

        Raises:
            ValueError: The username doesn't correspond to a registered user
            TypeError: Some other error occurred looking for the user

        Returns:
            The specified username if it's valid; does not return on failure
        """
        try:
            user = User.query(username=name)
        except Exception:
            User.logger.exception(
                "Unexpected exception from query for user {}", name)
            raise
        if not user:
            raise UnknownUser(name)
        return name
Example #22
0
 def verify_auth(auth_token):
     """
     Validates the auth token
     :param auth_token:
     :return: User object/None
     """
     try:
         payload = jwt.decode(
             auth_token,
             os.getenv("SECRET_KEY", "my_precious"),
             algorithms="HS256",
         )
         user_id = payload["sub"]
         if ActiveTokens.valid(auth_token):
             user = User.query(id=user_id)
             return user
     except jwt.ExpiredSignatureError:
         try:
             ActiveTokens.delete(auth_token)
         except Exception:
             Auth.logger.error(
                 "User attempted Pbench expired token but we could not delete the expired auth token from the database. token: '{}'",
                 auth_token,
             )
             return None
         Auth.logger.warning(
             "User attempted Pbench expired token '{}', Token deleted from the database and no longer tracked",
             auth_token,
         )
     except jwt.InvalidTokenError:
         Auth.logger.warning("User attempted invalid Pbench token '{}'",
                             auth_token)
     except Exception:
         Auth.logger.exception(
             "Exception occurred while verifying the auth token '{}'",
             auth_token)
     return None
Example #23
0
def user_list(context: object) -> None:
    try:
        config_setup(context)
        click.echo(USER_LIST_HEADER_ROW)

        # Query all the users
        users = User.query_all()

        for user in users:
            click.echo(
                USER_LIST_ROW_FORMAT.format(
                    user.username,
                    user.first_name,
                    user.last_name,
                    user.registered_on.strftime("%Y-%m-%d"),
                    user.email,
                ))

        rv = 0
    except Exception as exc:
        click.echo(exc, err=True)
        rv = 2 if isinstance(exc, BadConfig) else 1

    click.get_current_context().exit(rv)
Example #24
0
def main(options):
    try:
        if not options.cfg_name:
            print(
                f"{_NAME_}: ERROR: No config file specified; set"
                " _PBENCH_SERVER_CONFIG env variable",
                file=sys.stderr,
            )
            return 1

        try:
            config = PbenchServerConfig(options.cfg_name)
        except BadConfig as e:
            print(f"{_NAME_}: {e}", file=sys.stderr)
            return 2

        logger = get_pbench_logger(_NAME_, config)

        # We're going to need the Postgres DB to track dataset state, so setup
        # DB access.
        Database.init_db(config, logger)

        args = {}
        if options.create:
            user = options.create
            try:
                user = Auth.validate_user(user)
            except UnknownUser:
                # FIXME: I don't want to be creating the user here or
                # dealing with a non-existing user. The unittest setup
                # should create the test users we want ahead of time
                # using a pbench-user-manager command and we should
                # depend on having them here! The following is a hack
                # until that command exists.
                #
                # The desired behavior would be to remove this try and
                # except and allow UnknownUser to be handled below with
                # an error message and termination.
                User(
                    username=user,
                    first_name=user.capitalize(),
                    last_name="Account",
                    password=f"{user}_password",
                    email=f"{user}@example.com",
                ).add()
            args["owner"] = user
        if options.controller:
            args["controller"] = options.controller
        if options.path:
            args["path"] = options.path
        if options.name:
            args["name"] = options.name
        if options.md5:
            args["md5"] = options.md5
        if options.state:
            try:
                new_state = States[options.state.upper()]
            except KeyError:
                print(
                    f"{_NAME_}: Specified string '{options.state}' is not a Pbench dataset state",
                    file=sys.stderr,
                )
                return 1
            args["state"] = new_state

        if "path" not in args and ("controller" not in args
                                   or "name" not in args):
            print(
                f"{_NAME_}: Either --path or both --controller and --name must be specified",
                file=sys.stderr,
            )
            return 1

        # Either create a new dataset or attach to an existing dataset
        doit = Dataset.create if options.create else Dataset.attach

        # Find or create the specified dataset.
        doit(**args)
    except Exception as e:
        # Stringify any exception and report it; then fail
        logger.exception("Failed")
        print(f"{_NAME_}: {e}", file=sys.stderr)
        return 1
    else:
        return 0
Example #25
0
    def put(self, username):
        """
        PUT request for updating user data.
        This requires a Pbench auth token in the header field

        This requires a JSON data with required user registration fields that needs an update
        Example Json:
        {
            "first_name": "new_name",
            "password": "******",
            ...
        }

        Required headers include

            Content-Type:   application/json
            Accept:         application/json
            Authorization:  Bearer Pbench_auth_token (user received upon login)

        :return: JSON Payload
            Success: 200,
                    response_object = {
                        "username": <username>,
                        "first_name": <firstName>,
                        "last_name": <lastName>,
                        "registered_on": <registered_on>,
                    }
            Failure: <status_Code>,
                    response_object = {
                        "message": "failure message"
                    }
        """
        post_data = request.get_json()
        if not post_data:
            self.logger.warning("Invalid json object: {}", request.url)
            abort(400, message="Invalid json object in request")

        try:
            user, verified = self.auth.verify_user(username)
        except Exception:
            self.logger.exception(
                "Exception occurred while verifying the user")
            abort(500, message="INTERNAL ERROR")

        # TODO: Check if the user has the right privileges
        if not verified:
            self.logger.warning(
                "User {} is not authorized to delete user {}.",
                user.username,
                username,
            )
            abort(
                403,
                message=
                f"Not authorized to update information about user {username}",
            )

        # Check if the user payload contain fields that are either protected or
        # are not present in the user db. If any key in the payload does not match
        # with the column name we will abort the update request.
        non_existent = set(post_data.keys()).difference(
            set(User.__table__.columns.keys()))
        if non_existent:
            self.logger.warning(
                "User trying to update fields that are not present in the user database. Fields: {}",
                non_existent,
            )
            abort(400, message="Invalid fields in update request payload")
        protected = set(post_data.keys()).intersection(
            set(User.get_protected()))
        for field in protected:
            if getattr(user, field) != post_data[field]:
                self.logger.warning(
                    "User trying to update the non-updatable fields. {}: {}",
                    field,
                    post_data[field],
                )
                abort(403, message="Invalid update request payload")
        try:
            user.update(**post_data)
        except Exception:
            self.logger.exception(
                "Exception occurred during updating user object")
            abort(500, message="INTERNAL ERROR")

        response_object = user.get_json()
        return make_response(jsonify(response_object), 200)
Example #26
0
    def post(self):
        """
        Post request for logging in user.
        The user is allowed to re-login multiple times and each time a new valid auth token will be provided

        This requires a JSON data with required user metadata fields
        {
            "username": "******",
            "password": "******",
        }

        Required headers include

            Content-Type:   application/json
            Accept:         application/json

        :return: JSON Payload
            Success: 200,
                    response_object = {
                        "auth_token": "<authorization_token>"
                        "username": <username>
                    }
            Failure: <status_Code>,
                    response_object = {
                        "message": "failure message"
                    }
        """
        # get the post data
        post_data = request.get_json()
        if not post_data:
            self.logger.warning("Invalid json object: {}", request.url)
            abort(HTTPStatus.BAD_REQUEST,
                  message="Invalid json object in request")

        username = post_data.get("username")
        if not username:
            self.logger.warning(
                "Username not provided during the login process")
            abort(HTTPStatus.BAD_REQUEST,
                  message="Please provide a valid username")

        password = post_data.get("password")
        if not password:
            self.logger.warning(
                "Password not provided during the login process")
            abort(HTTPStatus.BAD_REQUEST,
                  message="Please provide a valid password")

        try:
            # fetch the user data
            user = User.query(username=username)
        except Exception:
            self.logger.exception("Exception occurred during user login")
            abort(HTTPStatus.INTERNAL_SERVER_ERROR, message="INTERNAL ERROR")

        if not user:
            self.logger.warning(
                "No user found in the db for Username: {} while login",
                username)
            abort(HTTPStatus.UNAUTHORIZED, message="Bad login")

        # Validate the password
        if not check_password_hash(user.password, password):
            self.logger.warning("Wrong password for user: {} during login",
                                username)
            abort(HTTPStatus.UNAUTHORIZED, message="Bad login")

        try:
            auth_token = self.auth.encode_auth_token(
                self.token_expire_duration, user.id)
        except (
                jwt.InvalidIssuer,
                jwt.InvalidIssuedAtError,
                jwt.InvalidAlgorithmError,
                jwt.PyJWTError,
        ):
            self.logger.exception(
                "Could not encode the JWT auth token for user: {} while login",
                username)
            abort(
                HTTPStatus.INTERNAL_SERVER_ERROR,
                message="INTERNAL ERROR",
            )

        # Add the new auth token to the database for later access
        try:
            token = ActiveTokens(token=auth_token)
            # TODO: Decide on the auth token limit per user
            user.update(auth_tokens=token)

            self.logger.info("New auth token registered for user {}",
                             user.email)
        except IntegrityError:
            self.logger.warning(
                "Duplicate auth token got created, user might have tried to re-login immediately"
            )
            abort(HTTPStatus.CONFLICT,
                  message="Login collision; please wait and retry")
        except SQLAlchemyError as e:
            self.logger.error(
                "SQLAlchemy Exception while logging in a user {}", type(e))
            abort(HTTPStatus.INTERNAL_SERVER_ERROR, message="INTERNAL ERROR")
        except Exception:
            self.logger.exception("Exception while logging in a user")
            abort(HTTPStatus.INTERNAL_SERVER_ERROR, message="INTERNAL ERROR")

        response_object = {
            "auth_token": auth_token,
            "username": username,
        }
        return make_response(jsonify(response_object), HTTPStatus.OK)
Example #27
0
    def post(self):
        """
        Post request for registering a new user.
        This requires a JSON data with required user fields
        {
            "username": "******",
            "password": "******",
            "first_name": first_name,
            "last_name": "last_name",
            "email": "*****@*****.**"
        }

        Required headers include

            Content-Type:   application/json
            Accept:         application/json

        :return:
            Success: 201 with empty payload
            Failure: <status_Code>,
                    response_object = {
                        "message": "failure message"
                    }
        To get the auth token user has to perform the login action
        """
        # get the post data
        user_data = request.get_json()
        if not user_data:
            self.logger.warning("Invalid json object: {}", request.url)
            abort(HTTPStatus.BAD_REQUEST,
                  message="Invalid json object in request")

        username = user_data.get("username")
        if not username:
            self.logger.warning("Missing username field")
            abort(HTTPStatus.BAD_REQUEST, message="Missing username field")
        username = username.lower()
        if User.is_admin_username(username):
            self.logger.warning("User tried to register with admin username")
            abort(
                HTTPStatus.BAD_REQUEST,
                message="Please choose another username",
            )

        # check if provided username already exists
        try:
            user = User.query(username=user_data.get("username"))
        except Exception:
            self.logger.exception("Exception while querying username")
            abort(HTTPStatus.INTERNAL_SERVER_ERROR, message="INTERNAL ERROR")
        if user:
            self.logger.warning("A user tried to re-register. Username: {}",
                                user.username)
            abort(HTTPStatus.FORBIDDEN,
                  message="Provided username is already in use.")

        password = user_data.get("password")
        if not password:
            self.logger.warning("Missing password field")
            abort(HTTPStatus.BAD_REQUEST, message="Missing password field")

        email_id = user_data.get("email")
        if not email_id:
            self.logger.warning("Missing email field")
            abort(HTTPStatus.BAD_REQUEST, message="Missing email field")
        # check if provided email already exists
        try:
            user = User.query(email=email_id)
        except Exception:
            self.logger.exception("Exception while querying user email")
            abort(HTTPStatus.INTERNAL_SERVER_ERROR, message="INTERNAL ERROR")
        if user:
            self.logger.warning("A user tried to re-register. Email: {}",
                                user.email)
            abort(HTTPStatus.FORBIDDEN,
                  message="Provided email is already in use")

        first_name = user_data.get("first_name")
        if not first_name:
            self.logger.warning("Missing first_name field")
            abort(HTTPStatus.BAD_REQUEST, message="Missing first_name field")

        last_name = user_data.get("last_name")
        if not last_name:
            self.logger.warning("Missing last_name field")
            abort(HTTPStatus.BAD_REQUEST, message="Missing last_name field")

        try:
            user = User(
                username=username,
                password=password,
                first_name=first_name,
                last_name=last_name,
                email=email_id,
            )

            # insert the user
            user.add()
            self.logger.info("New user registered, username: {}, email: {}",
                             username, email_id)
            return "", HTTPStatus.CREATED
        except EmailNotValidError:
            self.logger.warning("Invalid email {}", email_id)
            abort(HTTPStatus.BAD_REQUEST, message=f"Invalid email: {email_id}")
        except Exception:
            self.logger.exception("Exception while registering a user")
            abort(HTTPStatus.INTERNAL_SERVER_ERROR, message="INTERNAL ERROR")
Example #28
0
    def put(self, target_username):
        """
        PUT request for updating user data.
        This requires a Pbench auth token in the header field

        This requires a JSON data with required user registration fields that needs an update
        Example Json:
        {
            "first_name": "new_name",
            "password": "******",
            ...
        }

        Required headers include

            Content-Type:   application/json
            Accept:         application/json
            Authorization:  Bearer Pbench_auth_token (user received upon login)

        :return: JSON Payload
            Success: 200,
                    response_object = {
                        "username": <username>,
                        "first_name": <firstName>,
                        "last_name": <lastName>,
                        "registered_on": <registered_on>,
                    }
            Failure: <status_Code>,
                    response_object = {
                        "message": "failure message"
                    }
        """
        user_payload = request.get_json()
        if not user_payload:
            self.logger.warning("Invalid json object: {}", request.url)
            abort(HTTPStatus.BAD_REQUEST,
                  message="Invalid json object in request")

        result = self.get_valid_target_user(target_username, "PUT")
        if not result.target_user:
            abort(result.http_status, message=result.http_message)

        # Check if the user payload contain fields that are either protected or
        # are not present in the user db. If any key in the payload does not match
        # with the column name we will abort the update request.
        non_existent = set(user_payload.keys()).difference(
            set(User.__table__.columns.keys()))
        if non_existent:
            self.logger.warning(
                "User trying to update fields that are not present in the user database. Fields: {}",
                non_existent,
            )
            abort(
                HTTPStatus.BAD_REQUEST,
                message="Invalid fields in update request payload",
            )
        # Only admin user will be allowed to change other user's role. However,
        # Admin users will not be able to change their admin role,
        # This is done to prevent last admin user from de-admining him/herself
        protected_db_fields = User.get_protected()
        if (not self.auth.token_auth.current_user().is_admin()
                or self.auth.token_auth.current_user() == result.target_user):
            protected_db_fields.append("role")

        protected = set(user_payload.keys()).intersection(
            set(protected_db_fields))
        for field in protected:
            if getattr(result.target_user, field) != user_payload[field]:
                self.logger.warning(
                    "User trying to update the non-updatable fields. {}: {}",
                    field,
                    user_payload[field],
                )
                abort(HTTPStatus.FORBIDDEN,
                      message="Invalid update request payload")
        try:
            result.target_user.update(**user_payload)
        except Exception:
            self.logger.exception(
                "Exception occurred during updating user object")
            abort(HTTPStatus.INTERNAL_SERVER_ERROR, message="INTERNAL ERROR")

        response_object = result.target_user.get_json()
        return make_response(jsonify(response_object), HTTPStatus.OK)