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
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
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
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
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
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)
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
# 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))
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)
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