def update(self, trans, encoded_folder_id, payload: dict): """ Update the folder defined by an ``encoded_folder_id`` with the data in the payload. .. note:: Currently, only admin users can update library folders. Also the folder must not be `deleted`. :param id: the encoded id of the folder :type id: an encoded id string :param payload: (required) dictionary structure containing:: 'name': new folder's name, cannot be empty 'description': new folder's description :type payload: dict :returns: detailed folder information :rtype: dict :raises: RequestParameterMissingException """ decoded_folder_id = self.folder_manager.cut_and_decode( trans, encoded_folder_id) folder = self.folder_manager.get(trans, decoded_folder_id) name = payload.get('name', None) if not name: raise RequestParameterMissingException( "Parameter 'name' of library folder is required. You cannot remove it." ) description = payload.get('description', None) updated_folder = self.folder_manager.update(trans, folder, name, description) folder_dict = self.folder_manager.get_folder_dict( trans, updated_folder) return folder_dict
def create(self, trans, payload, **kwd): """ POST /api/groups Return a dictionary of information about the created group. The following parameters are included in the payload: :param name (required): the name of the group :param description (optional): the description of the group Example: POST /api/groups/?key=XXXYYYXXXYYY Content-Disposition: form-data; name="name" Group_Name Content-Disposition: form-data; name="description" Group_Description """ group_dict = dict(message='', status='ok') name = payload.get('name', '') if name: description = payload.get('description', '') if not description: description = '' else: # TODO add description field to the model group_dict = self.group_manager.create( trans, name=name).to_dict( view='element', value_mapper=self.__get_value_mapper(trans)) else: raise RequestParameterMissingException( 'Missing required parameter "name".') return group_dict
def create(self, trans, encoded_parent_folder_id, payload: dict): """ Create a new folder object underneath the one specified in the parameters. :param encoded_parent_folder_id: (required) the parent folder's id :type encoded_parent_folder_id: an encoded id string (should be prefixed by 'F') :param payload: dictionary structure containing: :param name: (required) the name of the new folder :type name: str :param description: the description of the new folder :type description: str :type dictionary :returns: information about newly created folder, notably including ID :rtype: dictionary :raises: RequestParameterMissingException """ name = payload.get('name', None) if name is None: raise RequestParameterMissingException( "Missing required parameter 'name'.") description = payload.get('description', '') decoded_parent_folder_id = self.folder_manager.cut_and_decode( trans, encoded_parent_folder_id) parent_folder = self.folder_manager.get(trans, decoded_parent_folder_id) new_folder = self.folder_manager.create(trans, parent_folder.id, name, description) return self.folder_manager.get_folder_dict(trans, new_folder)
def _parse_dependency_info(self, kwds): extra_kwds = kwds.copy() name = extra_kwds.pop("name", None) if name is None: raise RequestParameterMissingException("Missing 'name' parameter required for resolution.") version = extra_kwds.pop("version", None) type = extra_kwds.pop("type", "package") return name, version, type, extra_kwds
def update( self, trans, id, **kwd ): """ PATCH /api/repositories/{encoded_repository_id} Updates information about a repository in the Tool Shed. :param id: the encoded id of the Repository object :param payload: dictionary structure containing:: 'name': repo's name (optional) 'synopsis': repo's synopsis (optional) 'description': repo's description (optional) 'remote_repository_url': repo's remote repo (optional) 'homepage_url': repo's homepage url (optional) 'category_ids': list of existing encoded TS category ids the updated repo should be associated with (optional) :type payload: dict :returns: detailed repository information :rtype: dict :raises: RequestParameterInvalidException, InsufficientPermissionsException """ payload = kwd.get( 'payload', None ) if not payload: raise RequestParameterMissingException( "You did not specify any payload." ) name = payload.get( 'name', None ) synopsis = payload.get( 'synopsis', None ) description = payload.get( 'description', None ) remote_repository_url = payload.get( 'remote_repository_url', None ) homepage_url = payload.get( 'homepage_url', None ) category_ids = payload.get( 'category_ids', None ) if category_ids is not None: # We need to know if it was actually passed, and listify turns None into [] category_ids = util.listify( category_ids ) update_kwds = dict( name=name, description=synopsis, long_description=description, remote_repository_url=remote_repository_url, homepage_url=homepage_url, category_ids=category_ids, ) repo, message = repository_util.update_repository( app=trans.app, trans=trans, id=id, **update_kwds ) if repo is None: if "You are not the owner" in message: raise InsufficientPermissionsException( message ) else: raise ActionInputError( message ) repository_dict = repo.to_dict( view='element', value_mapper=self.__get_value_mapper( trans ) ) repository_dict[ 'category_ids' ] = \ [ trans.security.encode_id( x.category.id ) for x in repo.categories ] return repository_dict
def replace_file_srcs(request_part): if isinstance(request_part, dict): if request_part.get("src", None) == "files": path_def = next(files_iter) if path_def is None or path_def["file_data"] is None: raise RequestParameterMissingException("Failed to find uploaded file matching target with src='files'") request_part["path"] = path_def["file_data"]["local_filename"] if "name" not in request_part: request_part["name"] = path_def["file_data"]["filename"] request_part["src"] = "path" else: for key, value in request_part.items(): replace_file_srcs(value) elif isinstance(request_part, list): for value in request_part: replace_file_srcs(value)
def validation_error_to_message_exception(e): invalid_found = False missing_found = False for error in e.errors(): if error["type"] == "value_error.missing" or error[ "type"] == "type_error.none.not_allowed": missing_found = True elif error["type"].startswith("type_error"): invalid_found = True if missing_found and not invalid_found: return RequestParameterMissingException(str(e), validation_errors=loads( e.json())) else: return RequestParameterInvalidException(str(e), validation_errors=loads( e.json()))
def configure_provider(provider, credentials): """ Given a provider name and required credentials, it configures and returns a cloudbridge connection to the provider. :type provider: string :param provider: the name of cloud-based resource provided. A list of supported providers is given in `SUPPORTED_PROVIDERS` variable. :type credentials: dict :param credentials: a dictionary containing all the credentials required to authenticated to the specified provider. :rtype: provider specific, e.g., `cloudbridge.cloud.providers.aws.provider.AWSCloudProvider` for AWS. :return: a cloudbridge connection to the specified provider. """ missing_credentials = [] if provider == 'aws': access = credentials.get('access_key', None) if access is None: access = credentials.get("AccessKeyId", None) if access is None: missing_credentials.append('access_key') secret = credentials.get('secret_key', None) if secret is None: secret = credentials.get("SecretAccessKey", None) if secret is None: missing_credentials.append('secret_key') if len(missing_credentials) > 0: raise RequestParameterMissingException( "The following required key(s) are missing from the provided " "credentials object: {}".format(missing_credentials)) session_token = credentials.get("SessionToken") config = { 'aws_access_key': access, 'aws_secret_key': secret, "aws_session_token": session_token } connection = CloudProviderFactory().create_provider( ProviderList.AWS, config) elif provider == "azure": subscription = credentials.get('subscription_id', None) if subscription is None: missing_credentials.append('subscription_id') client = credentials.get('client_id', None) if client is None: missing_credentials.append('client_id') secret = credentials.get('secret', None) if secret is None: missing_credentials.append('secret') tenant = credentials.get('tenant', None) if tenant is None: missing_credentials.append('tenant') if len(missing_credentials) > 0: raise RequestParameterMissingException( "The following required key(s) are missing from the provided " "credentials object: {}".format(missing_credentials)) config = { 'azure_subscription_id': subscription, 'azure_client_id': client, 'azure_secret': secret, 'azure_tenant': tenant } storage_account = credentials.get("storage_account") if storage_account: config["azure_storage_account"] = storage_account resource_group = credentials.get("resource_group") if resource_group: config["azure_resource_group"] = resource_group connection = CloudProviderFactory().create_provider( ProviderList.AZURE, config) elif provider == "openstack": username = credentials.get('username', None) if username is None: missing_credentials.append('username') password = credentials.get('password', None) if password is None: missing_credentials.append('password') auth_url = credentials.get('auth_url', None) if auth_url is None: missing_credentials.append('auth_url') prj_name = credentials.get('project_name', None) if prj_name is None: missing_credentials.append('project_name') prj_domain_name = credentials.get('project_domain_name', None) if prj_domain_name is None: missing_credentials.append('project_domain_name') user_domain_name = credentials.get('user_domain_name', None) if user_domain_name is None: missing_credentials.append('user_domain_name') if len(missing_credentials) > 0: raise RequestParameterMissingException( "The following required key(s) are missing from the provided " "credentials object: {}".format(missing_credentials)) config = { 'os_username': username, 'os_password': password, 'os_auth_url': auth_url, 'os_project_name': prj_name, 'os_project_domain_name': prj_domain_name, 'os_user_domain_name': user_domain_name } connection = CloudProviderFactory().create_provider( ProviderList.OPENSTACK, config) elif provider == "gcp": config = {"gcp_service_creds_dict": credentials} connection = CloudProviderFactory().create_provider( ProviderList.GCP, config) else: raise RequestParameterInvalidException( "Unrecognized provider '{}'; the following are the supported " "providers: {}.".format(provider, SUPPORTED_PROVIDERS.keys())) # The authorization-assertion mechanism of Cloudbridge assumes a user has an elevated privileges, # such as Admin-level access to all resources (see https://github.com/CloudVE/cloudbridge/issues/135). # As a result, a user who wants to authorize Galaxy to read/write an Amazon S3 bucket, need to # also authorize Galaxy with full permission to Amazon EC2 (because Cloudbridge leverages EC2-specific # operation to assert credentials). While the EC2 authorization is not required by Galaxy to # read/write a S3 bucket, it can cause this exception. # # Until Cloudbridge implements an authorization-specific credentials assertion, we are not asserting # the authorization/validity of the credentials, in order to avoid asking users to grant Galaxy with an # elevated, yet unnecessary, privileges. # # Note, if user's credentials are invalid/expired to perform the authorized action, that can cause # exceptions which we capture separately in related read/write attempts. return connection
def create( self, trans, **kwd ): """ create( self, trans, payload, **kwd ) * POST /api/repositories: Creates a new repository. Only ``name`` and ``synopsis`` parameters are required. :param payload: dictionary structure containing:: 'name': new repo's name (required) 'synopsis': new repo's synopsis (required) 'description': new repo's description (optional) 'remote_repository_url': new repo's remote repo (optional) 'homepage_url': new repo's homepage url (optional) 'category_ids[]': list of existing encoded TS category ids the new repo should be associated with (optional) 'type': new repo's type, defaults to ``unrestricted`` (optional) :type payload: dict :returns: detailed repository information :rtype: dict :raises: RequestParameterMissingException, RequestParameterInvalidException """ payload = kwd.get( 'payload', None ) if not payload: raise RequestParameterMissingException( "You did not specify any payload." ) name = payload.get( 'name', None ) if not name: raise RequestParameterMissingException( "Missing required parameter 'name'." ) synopsis = payload.get( 'synopsis', None ) if not synopsis: raise RequestParameterMissingException( "Missing required parameter 'synopsis'." ) description = payload.get( 'description', '' ) remote_repository_url = payload.get( 'remote_repository_url', '' ) homepage_url = payload.get( 'homepage_url', '' ) category_ids = util.listify( payload.get( 'category_ids[]', '' ) ) selected_categories = [ trans.security.decode_id( id ) for id in category_ids ] repo_type = payload.get( 'type', rt_util.UNRESTRICTED ) if repo_type not in rt_util.types: raise RequestParameterInvalidException( 'This repository type is not valid' ) invalid_message = repository_util.validate_repository_name( trans.app, name, trans.user ) if invalid_message: raise RequestParameterInvalidException( invalid_message ) repo, message = repository_util.create_repository( app=trans.app, name=name, type=repo_type, description=synopsis, long_description=description, user_id = trans.user.id, category_ids=category_ids, remote_repository_url=remote_repository_url, homepage_url=homepage_url ) repository_dict = repo.to_dict( view='element', value_mapper=self.__get_value_mapper( trans ) ) repository_dict[ 'category_ids' ] = \ [ trans.security.encode_id( x.category.id ) for x in repo.categories ] return repository_dict
def create(self, trans, payload, **kwargs): """ * POST /api/cloud/authz Request to store the payload as a cloudauthz (cloud authorization) configuration for a user. :type trans: galaxy.webapps.base.webapp.GalaxyWebTransaction :param trans: Galaxy web transaction :type payload: dict :param payload: A dictionary structure containing the following keys: * provider: the cloud-based resource provider to which this configuration belongs to. * config: a dictionary containing all the configuration required to request temporary credentials from the provider. See the following page for details: https://galaxyproject.org/cloud/authnz/ * authn_id: the (encoded) ID of a third-party authentication of a user. To have this ID, user must have logged-in to this Galaxy server using third-party identity (e.g., Google), or has associated his/her Galaxy account with a third-party OIDC-based identity. See this page: https://galaxyproject.org/admin/authentication/ * description: [Optional] a brief description for this configuration. :param kwargs: empty dict :rtype: dict :return: a dictionary with the following kvp: * status: HTTP response code * message: A message complementary to the response code. """ msg_template = "Rejected user `" + str( trans.user.id ) + "`'s request to create cloudauthz config because of {}." if not isinstance(payload, dict): raise ActionInputError( 'Invalid payload data type. The payload is expected to be a dictionary, but ' 'received data of type `{}`.'.format(str(type(payload)))) missing_arguments = [] provider = payload.get('provider', None) if provider is None: missing_arguments.append('provider') config = payload.get('config', None) if config is None: missing_arguments.append('config') authn_id = payload.get('authn_id', None) if authn_id is None and provider.lower() not in ["azure", "gcp"]: missing_arguments.append('authn_id') if len(missing_arguments) > 0: log.debug( msg_template.format( "missing required config {}".format(missing_arguments))) raise RequestParameterMissingException( 'The following required arguments are missing in the payload: ' '{}'.format(missing_arguments)) description = payload.get("description", "") if not isinstance(config, dict): log.debug( msg_template.format( "invalid config type `{}`, expect `dict`".format( type(config)))) raise RequestParameterInvalidException( 'Invalid type for the required `config` variable; expect `dict` ' 'but received `{}`.'.format(type(config))) if authn_id: try: authn_id = self.decode_id(authn_id) except Exception: log.debug( msg_template.format("cannot decode authn_id `" + str(authn_id) + "`")) raise MalformedId('Invalid `authn_id`!') try: trans.app.authnz_manager.can_user_assume_authn(trans, authn_id) except Exception as e: raise e # No two authorization configuration with # exact same key/value should exist. for ca in trans.user.cloudauthzs: if ca.equals(trans.user.id, provider, authn_id, config): log.debug( "Rejected user `{}`'s request to create cloud authorization because a similar config " "already exists.".format(trans.user.id)) raise ActionInputError( "A similar cloud authorization configuration is already defined." ) try: new_cloudauthz = self.cloudauthz_manager.create( user_id=trans.user.id, provider=provider, config=config, authn_id=authn_id, description=description) view = self.cloudauthz_serializer.serialize_to_view( new_cloudauthz, trans=trans, **self._parse_serialization_params(kwargs, 'summary')) log.debug( 'Created a new cloudauthz record for the user id `{}` '.format( str(trans.user.id))) return view except Exception as e: log.exception( msg_template.format( "exception while creating the new cloudauthz record")) raise InternalServerError( 'An unexpected error has occurred while responding to the create request of the ' 'cloudauthz API.' + unicodify(e))
def set_permissions(self, trans, encoded_folder_id: EncodedDatabaseIdField, payload: dict) -> LibraryFolderCurrentPermissions: """ Set permissions of the given folder to the given role ids. :param encoded_folder_id: the encoded id of the folder to set the permissions of :type encoded_folder_id: an encoded id string :param payload: dictionary structure containing: :param action: (required) describes what action should be performed :type action: string :param add_ids[]: list of Role.id defining roles that should have add item permission on the folder :type add_ids[]: string or list :param manage_ids[]: list of Role.id defining roles that should have manage permission on the folder :type manage_ids[]: string or list :param modify_ids[]: list of Role.id defining roles that should have modify permission on the folder :type modify_ids[]: string or list :type dictionary :returns: dict of current roles for all available permission types. :rtype: dictionary :raises: RequestParameterInvalidException, InsufficientPermissionsException, RequestParameterMissingException """ is_admin = trans.user_is_admin current_user_roles = trans.get_current_user_roles() decoded_folder_id = self.folder_manager.cut_and_decode( trans, encoded_folder_id) folder = self.folder_manager.get(trans, decoded_folder_id) if not (is_admin or trans.app.security_agent.can_manage_library_item( current_user_roles, folder)): raise InsufficientPermissionsException( 'You do not have proper permission to modify permissions of this folder.' ) new_add_roles_ids = util.listify(payload.get('add_ids[]', None)) new_manage_roles_ids = util.listify(payload.get('manage_ids[]', None)) new_modify_roles_ids = util.listify(payload.get('modify_ids[]', None)) action = payload.get('action', None) if action is None: raise RequestParameterMissingException( 'The mandatory parameter "action" is missing.') elif action == 'set_permissions': # ADD TO LIBRARY ROLES valid_add_roles = [] invalid_add_roles_names = [] for role_id in new_add_roles_ids: role = self.role_manager.get( trans, trans.security.decode_id(role_id, object_name='role')) # Check whether role is in the set of allowed roles valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, folder) if role in valid_roles: valid_add_roles.append(role) else: invalid_add_roles_names.append(role_id) if len(invalid_add_roles_names) > 0: log.warning( f"The following roles could not be added to the add library item permission: {str(invalid_add_roles_names)}" ) # MANAGE FOLDER ROLES valid_manage_roles = [] invalid_manage_roles_names = [] for role_id in new_manage_roles_ids: role = self.role_manager.get( trans, trans.security.decode_id(role_id, object_name='role')) # Check whether role is in the set of allowed roles valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, folder) if role in valid_roles: valid_manage_roles.append(role) else: invalid_manage_roles_names.append(role_id) if len(invalid_manage_roles_names) > 0: log.warning( f"The following roles could not be added to the manage folder permission: {str(invalid_manage_roles_names)}" ) # MODIFY FOLDER ROLES valid_modify_roles = [] invalid_modify_roles_names = [] for role_id in new_modify_roles_ids: role = self.role_manager.get( trans, trans.security.decode_id(role_id, object_name='role')) # Check whether role is in the set of allowed roles valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, folder) if role in valid_roles: valid_modify_roles.append(role) else: invalid_modify_roles_names.append(role_id) if len(invalid_modify_roles_names) > 0: log.warning( f"The following roles could not be added to the modify folder permission: {str(invalid_modify_roles_names)}" ) permissions = { trans.app.security_agent.permitted_actions.LIBRARY_ADD: valid_add_roles } permissions.update({ trans.app.security_agent.permitted_actions.LIBRARY_MANAGE: valid_manage_roles }) permissions.update({ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY: valid_modify_roles }) trans.app.security_agent.set_all_library_permissions( trans, folder, permissions) else: raise RequestParameterInvalidException( 'The mandatory parameter "action" has an invalid value.' 'Allowed values are: "set_permissions"') current_permissions = self.folder_manager.get_current_roles( trans, folder) return LibraryFolderCurrentPermissions.parse_obj(current_permissions)
def _configure_provider(provider, credentials): """ Given a provider name and required credentials, it configures and returns a cloudbridge connection to the provider. :type provider: string :param provider: the name of cloud-based resource provided. A list of supported providers is given in `SUPPORTED_PROVIDERS` variable. :type credentials: dict :param credentials: a dictionary containing all the credentials required to authenticated to the specified provider. :rtype: provider specific, e.g., `cloudbridge.cloud.providers.aws.provider.AWSCloudProvider` for AWS. :return: a cloudbridge connection to the specified provider. """ missing_credentials = [] if provider == 'aws': access = credentials.get('access_key', None) if access is None: missing_credentials.append('access_key') secret = credentials.get('secret_key', None) if secret is None: missing_credentials.append('secret_key') if len(missing_credentials) > 0: raise RequestParameterMissingException( "The following required key(s) are missing from the provided " "credentials object: {}".format(missing_credentials)) config = {'aws_access_key': access, 'aws_secret_key': secret} connection = CloudProviderFactory().create_provider( ProviderList.AWS, config) elif provider == "azure": subscription = credentials.get('subscription_id', None) if subscription is None: missing_credentials.append('subscription_id') client = credentials.get('client_id', None) if client is None: missing_credentials.append('client_id') secret = credentials.get('secret', None) if secret is None: missing_credentials.append('secret') tenant = credentials.get('tenant', None) if tenant is None: missing_credentials.append('tenant') if len(missing_credentials) > 0: raise RequestParameterMissingException( "The following required key(s) are missing from the provided " "credentials object: {}".format(missing_credentials)) config = { 'azure_subscription_id': subscription, 'azure_client_id': client, 'azure_secret': secret, 'azure_tenant': tenant } connection = CloudProviderFactory().create_provider( ProviderList.AZURE, config) elif provider == "openstack": username = credentials.get('username', None) if username is None: missing_credentials.append('username') password = credentials.get('password', None) if password is None: missing_credentials.append('password') auth_url = credentials.get('auth_url', None) if auth_url is None: missing_credentials.append('auth_url') prj_name = credentials.get('project_name', None) if prj_name is None: missing_credentials.append('project_name') prj_domain_name = credentials.get('project_domain_name', None) if prj_domain_name is None: missing_credentials.append('project_domain_name') user_domain_name = credentials.get('user_domain_name', None) if user_domain_name is None: missing_credentials.append('user_domain_name') if len(missing_credentials) > 0: raise RequestParameterMissingException( "The following required key(s) are missing from the provided " "credentials object: {}".format(missing_credentials)) config = { 'os_username': username, 'os_password': password, 'os_auth_url': auth_url, 'os_project_name': prj_name, 'os_project_domain_name': prj_domain_name, 'os_user_domain_name': user_domain_name } connection = CloudProviderFactory().create_provider( ProviderList.OPENSTACK, config) else: raise RequestParameterInvalidException( "Unrecognized provider '{}'; the following are the supported " "providers: {}.".format(provider, SUPPORTED_PROVIDERS)) try: if connection.authenticate(): return connection except ProviderConnectionException as e: raise AuthenticationFailed( "Could not authenticate to the '{}' provider. {}".format( provider, e))