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 ------- flowserv.model.base.User Raises ------ flowserv.error.ConstraintViolationError flowserv.error.DuplicateUserError """ # Ensure that the password has at least one (non-space) character validate_password(password) # Ensure that the username is not empty and does not contain more than # 512 characters. if username is None: raise err.ConstraintViolationError('missing user name') username = username.strip() if username == '' or len(username) > 512: raise err.ConstraintViolationError('username too long') # If a user with the given username already exists raise an error # Get the unique user identifier and encrypted password. Raise error # if user is unknown query = self.session.query(User).filter(User.name == username) user = query.one_or_none() if user is not None: raise err.DuplicateUserError(username) # Insert new user into database after creating an unique user # identifier and the password hash. user = User(user_id=util.get_unique_identifier(), name=username, secret=pbkdf2_sha256.hash(password.strip()), active=False if verify else True) self.session.add(user) return user
def read_run_results(run: RunObject, schema: ResultSchema, rundir: str): """Read the run results from the result file that is specified in the workflow result schema. If the file is not found we currently do not raise an error. Parameters ---------- run: flowserv.model.base.RunObject Handle for a workflow run. schema: flowserv.model.template.schema.ResultSchema Workflow result schema specification that contains the reference to the result file key. rundir: string Directory containing run result files. """ filename = os.path.join(rundir, schema.result_file) if os.path.exists(filename): results = util.read_object(filename) # Create a dictionary of result values. values = dict() for col in schema.columns: val = util.jquery(doc=results, path=col.jpath()) col_id = col.column_id if val is None and col.required: msg = "missing value for '{}'".format(col_id) raise err.ConstraintViolationError(msg) elif val is not None: values[col_id] = col.cast(val) run.result = values
def read_run_results(run: RunObject, schema: ResultSchema, runstore: StorageVolume): """Read the run results from the result file that is specified in the workflow result schema. If the file is not found we currently do not raise an error. Parameters ---------- run: flowserv.model.base.RunObject Handle for a workflow run. schema: flowserv.model.template.schema.ResultSchema Workflow result schema specification that contains the reference to the result file key. runstore: flowserv.volume.base.StorageVolume Storage volume containing the run (result) files for a successful workflow run. """ with runstore.load(schema.result_file).open() as f: results = util.read_object(f) # Create a dictionary of result values. values = dict() for col in schema.columns: val = util.jquery(doc=results, path=col.jpath()) col_id = col.column_id if val is None and col.required: msg = "missing value for '{}'".format(col_id) raise err.ConstraintViolationError(msg) elif val is not None: values[col_id] = col.cast(val) run.result = values
def validate_name(name): """Validate the given name. Raises an error if the given name violates the current constraints for names. The constraints are: - no empty or missing names - names can be at most 512 characters long Parameters ---------- name: string Name that is being validated Raises ------ flowserv.error.ConstraintViolationError """ if name is None: raise err.ConstraintViolationError('missing name') name = name.strip() if name == '' or len(name) > 512: raise err.ConstraintViolationError('invalid name')
def update_workflow(self, workflow_id, name=None, description=None, instructions=None): """Update name, description, and instructions for a given workflow. Raises an error if the given workflow does not exist or if the name is not unique. Parameters ---------- workflow_id: string Unique workflow identifier name: string, optional Unique workflow name description: string, optional Optional short description for display in workflow listings instructions: string, optional Text containing detailed instructions for workflow execution Returns ------- flowserv.model.base.WorkflowObject Raises ------ flowserv.error.ConstraintViolationError flowserv.error.UnknownWorkflowError """ # Get the workflow from the database. This will raise an error if the # workflow does not exist. workflow = self.get_workflow(workflow_id) # Update workflow properties. if name is not None: # Ensure that the name is a valid name. constraint.validate_name(name) # Ensure that the name is unique. wf = self.session\ .query(WorkflowObject)\ .filter(WorkflowObject.name == name)\ .one_or_none() if wf is not None and wf.workflow_id != workflow_id: msg = "workflow '{}' exists".format(name) raise err.ConstraintViolationError(msg) workflow.name = name if description is not None: workflow.description = description if instructions is not None: workflow.instructions = instructions return workflow
def validate_state_transition(current_state: str, target_state: str, valid_states: List[str]): """Validate that a transition from current state to target state is permitted. The list of valid state identifier determines the current states that are permitted to transition to the target state. If an invalid transition is detected an error is raised. Parameters ---------- current_state: str Identifier for the current run state. target_state: str Identifier for the target workflow state. valid_states: list of string List of valid source states for the anticipated target state. """ if current_state not in valid_states: msg = 'cannot change run in state {} to state {}' raise err.ConstraintViolationError(msg.format(current_state, target_state))
def validate_password(password): """Validate a given password. Raises constraint violation error if an invalid password is given. Currently, the only constraint for passwords is that they are not empty Parameters ---------- password: string User password for authentication Raises ------ flowserv.error.ConstraintViolationError """ # Raise error if password is invalid if password is None or password.strip() == '': raise err.ConstraintViolationError('empty password')
def create_group(self, workflow_id: str, name: str, parameters: List[Parameter], workflow_spec: Dict, user_id: Optional[str] = None, members: List[str] = None, engine_config: Optional[Dict] = None, identifier: Optional[str] = None): """Create a new group for a given workflow. Within each workflow, the names of groups are expected to be unique. The workflow group may define additional parameters for the template. The full (modifued or original) parameter list is stored with the group together with the workflow specification. A group may have a list of users that are members. Membership can be used to control which users are allowed to execute the associated workflow and to upload/view files. The user that creates the group, identified by user_id parameter, is always part of the initial list of group members. If a list of members is given it is ensured that each identifier in the list references an existing user. Parameters ---------- workflow_id: string Unique workflow identifier name: string Group name user_id: string Unique identifier of the user that created the group parameters: list(flowserv.model.parameter.base.Parameter) List of workflow template parameter declarations that may be specific to the group workflow_spec: dict Workflow specification members: list(string), optional Optional list of user identifiers for other group members engine_config: dict, default=None Optional configuration settings that will be used as the default when running a workflow. identifier: string, default=None Optional user-provided group identifier. Returns ------- flowserv.model.base.GroupObject Raises ------ flowserv.error.ConstraintViolationError flowserv.error.UnknownUserError """ # Validate the given group identifier. This will raise a ValueError # if the identifier is invalid. validate_identifier(identifier) # Ensure that the given name is valid and unique for the workflow constraint.validate_name(name) # Ensure that the user identifier is not None. if user_id is None: raise err.UnknownUserError('none') group = self.session.query(GroupObject)\ .filter(GroupObject.name == name)\ .filter(GroupObject.workflow_id == workflow_id)\ .one_or_none() if group is not None: msg = "group '{}' exists".format(name) raise err.ConstraintViolationError(msg) # Create the group object identifier = identifier if identifier else unique_identifier() group = GroupObject(group_id=identifier, name=name, workflow_id=workflow_id, owner_id=user_id, parameters=parameters, workflow_spec=workflow_spec, engine_config=engine_config) # Create a set of member identifier that contains the identifier of # the group owner. Ensure that all group members exist. This will also # ensure that the group owner exists. member_set = set() if members is None else set(members) if user_id is not None and user_id not in member_set: member_set.add(user_id) for member_id in member_set: group.members.append(self.users.get_user(member_id, active=True)) # Enter group information into the database. self.session.add(group) return group