Example #1
0
    def upload_stream(self, file, file_name):
        """Create a new entry from a given file stream. Will copy the given
        file to a file in the base directory.

        Parameters
        ----------
        file: werkzeug.datastructures.FileStorage
            File object (e.g., uploaded via HTTP request)
        file_name: string
            Name of the file

        Returns
        -------
        benchtmpl.io.files.base.FileHandle
        """
        # Create a new unique identifier for the file.
        identifier = util.get_unique_identifier()
        file_dir = self.get_file_dir(identifier, create=True)
        output_file = os.path.join(file_dir, file_name)
        # Save the file object to the new file path
        file.save(output_file)
        f_handle = FileHandle(identifier=identifier,
                              filepath=output_file,
                              file_name=file_name)
        return f_handle
Example #2
0
    def upload_file(self, filename):
        """Create a new entry from a given local file. Will make a copy of the
        given file.

        Raises ValueError if the given file does not exist.

        Parameters
        ----------
        filename: string
            Path to file on disk

        Returns
        -------
        benchtmpl.io.files.base.FileHandle
        """
        # Ensure that the given file exists
        if not os.path.isfile(filename):
            raise ValueError('invalid file path \'' + str(filename) + '\'')
        file_name = os.path.basename(filename)
        # Create a new unique identifier for the file.
        identifier = util.get_unique_identifier()
        file_dir = self.get_file_dir(identifier, create=True)
        output_file = os.path.join(file_dir, file_name)
        # Copy the uploaded file
        shutil.copyfile(filename, output_file)
        # Add file to file index
        f_handle = FileHandle(identifier=identifier,
                              filepath=output_file,
                              file_name=file_name)
        return f_handle
Example #3
0
    def request_password_reset(self, username):
        """Request a passowrd reset for the user with a given name. Returns
        the request identifier that is required as an argument to reset the
        password. The result is always going to be the identifier string
        independently of whether a user with the given username is registered or
        not.

        Invalidates all previous password reset requests for the user.

        Parameters
        ----------
        username: string
            User email that was provided at registration

        Returns
        -------
        string
        """
        request_id = util.get_unique_identifier()
        # Get user identifier that is associated with the username
        sql = 'SELECT id FROM registered_user WHERE email = ? AND active = 1'
        user = self.con.execute(sql, (username, )).fetchone()
        if user is None:
            return request_id
        user_id = user['id']
        # Delete any existing password reset request for the given user
        sql = 'DELETE FROM password_request WHERE user_id = ?'
        self.con.execute(sql, (user_id, ))
        # Insert new password reset request. The expiry date for the request is
        # calculated using the login timeout
        expires = dt.datetime.now() + dt.timedelta(seconds=self.login_timeout)
        sql = 'INSERT INTO password_request(user_id, request_id, expires) VALUES(?, ?, ?)'
        self.con.execute(sql, (user_id, request_id, expires.isoformat()))
        self.con.commit()
        return request_id
Example #4
0
    def register_user(self, username, password, verify=False):
        """Create a new user for the given username. Raises an error if a user
        with that name already is registered. Returns the internal unique
        identifier for the created user.

        The verify flag allows to create active or inactive users. An inactive
        user cannot login until they have been activated. This option is
        intended for scenarios where the user receives an email after they
        register that contains a verification/activation link to ensure that
        the provided email address is valid.

        Parameters
        ----------
        username: string
            User email address that is used as the username
        password: string
            Password used to authenticate the user
        verify: bool, optional
            Determines whether the created user is active or inactive

        Returns
        -------
        string

        Raises
        ------
        benchengine.error.ConstraintViolationError
        benchengine.error.DuplicateUserError
        """
        # Ensure that the username does not contain more than 255 characters
        # and that the password has at least one (non-space) character
        if len(username) > 255:
            raise err.ConstraintViolationError('username too long')
        self.validate_password(password)
        # If a user with the given username already exists raise an error
        sql = 'SELECT id FROM registered_user WHERE email = ?'
        if not self.con.execute(sql, (username, )).fetchone() is None:
            raise err.DuplicateUserError(username)
        # Insert new user into database after creating an unique user identifier
        # and the password hash.
        user_id = util.get_unique_identifier()
        hash = pbkdf2_sha256.hash(password.strip())
        active = 0 if verify else 1
        sql = 'INSERT INTO registered_user(id, email, secret, active) '
        sql += 'VALUES(?, ?, ?, ?)'
        self.con.execute(sql, (user_id, username, hash, active))
        self.con.commit()
        # Log user in after successful registration and return API key
        return user_id
Example #5
0
    def login(self, username, password):
        """Authorize a given user and assigne an API key for them. If the user
        is unknown or the given credentials do not match those in the database
        an unknown user error is raised.

        Returns the API key that has been associated with the user identifier.

        Parameters
        ----------
        username: string
            Unique name (i.e., email address) that the user provided when they
            registered
        password: string
            User password specified during registration (in plain text)

        Returns
        -------
        string

        Raises
        ------
        benchengine.user.error.UnknownUserError
        """
        # Get the unique user identifier and encrypted password. Raise error
        # if user is unknown
        sql = 'SELECT id, secret FROM registered_user '
        sql += 'WHERE email = ? AND active = 1'
        user = self.con.execute(sql, (username, )).fetchone()
        if user is None:
            raise err.UnknownUserError(username)
        # Validate that given credentials match the stored user secret
        if not pbkdf2_sha256.verify(password, user['secret']):
            raise err.UnknownUserError(username)
        user_id = user['id']
        # Remove any API key that may be associated with the user currently
        sql = 'DELETE FROM user_key WHERE user_id = ?'
        self.con.execute(sql, (user_id, ))
        # Create a new API key for the user and set the expiry date. The key
        # expires login_timeout seconds from now.
        api_key = util.get_unique_identifier()
        expires = dt.datetime.now() + dt.timedelta(seconds=self.login_timeout)
        # Insert API key and expiry date into database and return the key
        sql = 'INSERT INTO user_key(user_id, api_key, expires) VALUES(?, ?, ?)'
        self.con.execute(sql, (user_id, api_key, expires.isoformat()))
        self.con.commit()
        return api_key
Example #6
0
    def __init__(self, filepath, identifier=None, file_name=None):
        """Initialize the file identifier, the (full) file path, and the file
        name. The file path is mandatory.

        Parameters
        ----------
        filepath: string
            Absolute path to file on disk
        identifier: string, optional
            Unique file identifier
        file_name: string, optional
            Base name of the file
        """
        self.filepath = os.path.abspath(filepath)
        self.identifier = identifier if not identifier is None else get_unique_identifier(
        )
        self.file_name = file_name if not file_name is None else os.path.basename(
            self.filepath)
Example #7
0
    def __init__(self,
                 workflow_spec,
                 identifier=None,
                 base_dir=None,
                 parameters=None):
        """Initialize the components of the workflow template. A ValueError is
        raised if the identifier of template parameters are not unique.

        Parameters
        ----------
        workflow_spec: dict
            Workflow specification object
        identifier: string, optional
            Unique template identifier. If no value is given a UUID will be
            assigned.
        base_dir: string, optional
            Optional path to directory on disk that contains static files that
            are required to run the represented workflow
        parameters: list(benchtmpl.workflow.parameter.base.TemplateParameter), optional
            List of workflow template parameter declarations

        Raises
        ------
        benchtmpl.error.InvalidTemplateError
        """
        self.workflow_spec = workflow_spec
        if not identifier is None:
            self.identifier = identifier
        else:
            self.identifier = util.get_unique_identifier()
        self.base_dir = base_dir
        # Add given parameter declarations to the parameter index.
        self.parameters = dict()
        if not parameters is None:
            for para in parameters:
                # Ensure that the identifier of all parameters are unique
                if para.identifier in self.parameters:
                    raise err.InvalidTemplateError(
                        'parameter \'{}\' not unique'.format(para.identifier))
                self.parameters[para.identifier] = para
Example #8
0
# terms of the MIT License; see LICENSE file for more details.
"""Test functionality to authenticate a user."""

import os
import pytest
import time

from passlib.hash import pbkdf2_sha256

from benchengine.db import DatabaseDriver
from benchengine.user.auth import Auth

import benchengine.error as err
import benchtmpl.util.core as util

USER_1 = util.get_unique_identifier()
USER_2 = util.get_unique_identifier()
USER_3 = util.get_unique_identifier()


class TestUserAuthentication(object):
    """Test login and logout functionality."""
    def connect(self, base_dir):
        """Create empty database and open connection."""
        connect_string = 'sqlite:{}/auth.db'.format(str(base_dir))
        DatabaseDriver.init_db(connect_string=connect_string)
        con = DatabaseDriver.connect(connect_string=connect_string)
        sql = 'INSERT INTO registered_user(id, email, secret, active) VALUES(?, ?, ?, ?)'
        con.execute(sql, (USER_1, USER_1, pbkdf2_sha256.hash(USER_1), 1))
        con.execute(sql, (USER_2, USER_2, pbkdf2_sha256.hash(USER_2), 1))
        con.execute(sql, (USER_3, USER_3, pbkdf2_sha256.hash(USER_3), 0))
Example #9
0
    def create_team(self, name, owner_id, members=None):
        """Create a new team with the given name. Ensures that at least the team
        owner is added as a member to the new team.

        Parameters
        ----------
        name: string
            Unique team name
        owner_id: string
            Unique user identifier for team owner
        members: list(string), optional
            List of team members

        Returns
        -------
        benchengine.user.team.base.TeamDescriptor

        Raises
        ------
        benchengine.error.ConstraintViolationError
        benchengine.error.UnknownUserError
        """
        # Ensure that the owner exists and all team members exist. Will raise
        # exception if user is unknown.
        self.assert_user_exists(owner_id)
        if not members is None:
            for user_id in set(members):
                if not user_id == owner_id:
                    self.assert_user_exists(user_id)
        # Ensure that the given team name is uniqe and does not contain too many
        # characters
        sql = 'SELECT * FROM team WHERE name = ?'
        if name is None or name.strip() == '':
            raise err.ConstraintViolationError('missing team name')
        elif len(name.strip()) > 255:
            raise err.ConstraintViolationError(
                'team name contains more than 255 character')
        elif not self.con.execute(sql, (name.strip(), )).fetchone() is None:
            raise err.ConstraintViolationError(
                'team name \'{}\' exists'.format(name.strip()))
        # Get unique identifier for the new team.
        team_id = util.get_unique_identifier()
        # Create the new team and add team members. Ensure that at least the
        # team owner is added as a team member.
        sql = 'INSERT INTO team_member(team_id, user_id) VALUES(?, ?)'
        self.con.execute(
            'INSERT INTO team(id, name, owner_id) VALUES(?, ?, ?)',
            (team_id, name.strip(), owner_id))
        self.con.execute(sql, (team_id, owner_id))
        member_count = 1
        if not members is None:
            for user_id in set(members):
                if not user_id == owner_id:
                    self.con.execute(sql, (team_id, user_id))
                    member_count += 1
        self.con.commit()
        # Return team descriptor
        return TeamDescriptor(identifier=team_id,
                              name=name,
                              owner_id=owner_id,
                              member_count=member_count)
Example #10
0
    def execute(self, template, arguments):
        """Execute a given workflow template for a set of argument values.
        Returns an unique identifier for the started workflow run.

        Parameters
        ----------
        template: benchtmpl.workflow.template.base.TemplateHandle
            Workflow template containing the parameterized specification and the
            parameter declarations
        arguments: dict(benchtmpl.workflow.parameter.value.TemplateArgument)
            Dictionary of argument values for parameters in the template

        Returns
        -------
        string, benchtmpl.workflow.state.WorkflowState

        Raises
        ------
        benchtmpl.error.MissingArgumentError
        """
        # Before we start creating directories and copying files make sure that
        # there are values for all template parameters (either in the arguments
        # dictionary or set as default values)
        template.validate_arguments(arguments)
        # Create unique run identifier
        identifier = util.get_unique_identifier()
        # Create run folder for input and output files
        run_dir = os.path.join(self.base_dir, identifier)
        os.makedirs(run_dir)
        # Copy all static files and files in the argument dictionary into the
        # run folder.
        try:
            # Copy all required code and input files
            fileio.upload_files(template=template,
                                files=template.workflow_spec.get(
                                    'inputs', {}).get('files', []),
                                arguments=arguments,
                                loader=FileCopy(run_dir))
            # Get list of workflow commands after substituting references to
            # parameters from the inputs/parameters section of the REANA
            # workflow specification.
            commands = get_commands(template, arguments)
            # Replace references to template parameters in the list of output
            # files from the workflow specification
            output_files = tmpl.replace_args(spec=template.workflow_spec.get(
                'outputs', {}).get('files', {}),
                                             arguments=arguments,
                                             parameters=template.parameters)
            # Run workflow
            if self.run_async:
                state = self.execute_async(identifier=identifier,
                                           commands=commands,
                                           run_dir=run_dir,
                                           output_files=output_files,
                                           verbose=False)
            else:
                ts = datetime.now()
                with self.lock:
                    self.tasks[identifier] = (wf.StateRunning(created_at=ts,
                                                              started_at=ts),
                                              None)
                # Start the task and wait until completions
                result = tasks.run(identifier=identifier,
                                   commands=commands,
                                   run_dir=run_dir,
                                   verbose=self.verbose)
                # Call callback handler with task result
                callback_function(result=result,
                                  lock=self.lock,
                                  tasks=self.tasks,
                                  run_dir=run_dir,
                                  output_files=output_files)
                state = self.get_state(identifier)
        except Exception as ex:
            # Remove run directory if anything goes wrong while preparing the
            # workflow and starting the run
            shutil.rmtree(run_dir)
            raise ex
        # Return run identifier
        return identifier, state