def validate_status_transition(af, from_status, to_status): if from_status == 'deleted': msg = _("Cannot change status if artifact is deleted.") raise exception.Forbidden(msg) if to_status == 'active': if from_status == 'drafted': for name, type_obj in af.fields.items(): if type_obj.required_on_activate and getattr(af, name) is None: msg = _("'%s' field value must be set before " "activation.") % name raise exception.Forbidden(msg) elif to_status == 'drafted': if from_status != 'drafted': msg = _("Cannot change status to 'drafted'") % from_status raise exception.Forbidden(msg) elif to_status == 'deactivated': if from_status not in ('active', 'deactivated'): msg = _("Cannot deactivate artifact if it's not active.") raise exception.Forbidden(msg) elif to_status == 'deleted': msg = _("Cannot delete artifact with PATCH requests. Use special " "API to do this.") raise exception.Forbidden(msg) else: msg = _("Unknown artifact status: %s.") % to_status raise exception.BadRequest(msg)
def validate_visibility_transition(af, from_visibility, to_visibility): if to_visibility == 'private': if from_visibility != 'private': msg = _("Cannot make artifact private again.") raise exception.Forbidden() elif to_visibility == 'public': if af.status != 'active': msg = _("Cannot change visibility to 'public' if artifact" " is not active.") raise exception.Forbidden(msg) else: msg = _("Unknown artifact visibility: %s.") % to_visibility raise exception.BadRequest(msg)
def delete_external_blob(self, context, type_name, artifact_id, field_name, blob_key=None): """Delete artifact blob with external location. :param context: user context :param type_name: name of artifact type :param artifact_id: id of artifact with the blob to delete :param field_name: name of blob or blob dict field :param blob_key: if field_name is blob dict it specifies key in this dictionary """ af = self._show_artifact(context, type_name, artifact_id) action_name = 'artifact:delete_blob' policy.authorize(action_name, af.to_dict(), context) blob_name = self._generate_blob_name(field_name, blob_key) blob = self._get_blob_info(af, field_name, blob_key) if blob is None: msg = _("Blob %s wasn't found for artifact") % blob_name raise exception.NotFound(message=msg) if not blob['external']: msg = _("Blob %s is not external") % blob_name raise exception.Forbidden(message=msg) af = self._save_blob_info(context, af, field_name, blob_key, None) Notifier.notify(context, action_name, af) return af.to_dict()
def _show_artifact(ctx, type_name, artifact_id, read_only=False, get_any_artifact=False): """Return artifact requested by user. Check access permissions and policies. :param ctx: user context :param type_name: artifact type name :param artifact_id: id of the artifact to be updated :param read_only: flag, if set to True only read access is checked, if False then engine checks if artifact can be modified by the user :param get_any_artifact: flag, if set to True will get artifact from any realm """ artifact_type = registry.ArtifactRegistry.get_artifact_type(type_name) # only artifact is available for class users af = artifact_type.show(ctx, artifact_id, get_any_artifact) if not read_only and not get_any_artifact: if not ctx.is_admin and ctx.tenant != af.owner or ctx.read_only: raise exception.Forbidden() LOG.debug("Artifact %s acquired for read-write access", artifact_id) else: LOG.debug("Artifact %s acquired for read-only access", artifact_id) return af
def verify_artifact_count(context, type_name): """Verify if user can upload data based on his quota limits. :param context: user context :param type_name: name of artifact type """ global_limit = CONF.max_artifact_number type_limit = getattr(CONF, 'artifact_type:' + type_name).max_artifact_number # update limits if they were reassigned for project project_id = context.project_id quotas = list_quotas(project_id).get(project_id, {}) if 'max_artifact_number' in quotas: global_limit = quotas['max_artifact_number'] if 'max_artifact_number:' + type_name in quotas: type_limit = quotas['max_artifact_number:' + type_name] session = api.get_session() if global_limit != -1: # the whole amount of created artifacts whole_number = api.count_artifact_number(context, session) if whole_number >= global_limit: msg = _("Can't create artifact because of global quota " "limit is %(global_limit)d artifacts. " "You have %(whole_number)d artifact(s).") % { 'global_limit': global_limit, 'whole_number': whole_number } raise exception.Forbidden(msg) if type_limit != -1: # the amount of artifacts for specific type type_number = api.count_artifact_number(context, session, type_name) if type_number >= type_limit: msg = _("Can't create artifact because of quota limit for " "artifact type '%(type_name)s' is %(type_limit)d " "artifacts. You have %(type_number)d artifact(s) " "of this type.") % { 'type_name': type_name, 'type_limit': type_limit, 'type_number': type_number } raise exception.Forbidden(msg)
def validate_change_allowed(af, field_name): """Validate if fields can be set for the artifact.""" if field_name not in af.fields: msg = _("Cannot add new field '%s' to artifact.") % field_name raise exception.BadRequest(msg) if af.status not in ('active', 'drafted'): msg = _("Forbidden to change fields " "if artifact is not active or drafted.") raise exception.Forbidden(message=msg) if af.fields[field_name].system is True: msg = _("Forbidden to specify system field %s. It is not " "available for modifying by users.") % field_name raise exception.Forbidden(msg) if af.status == 'active' and not af.fields[field_name].mutable: msg = (_("Forbidden to change field '%s' after activation.") % field_name) raise exception.Forbidden(message=msg)
def _check_read_write_access(ctx, af): """Check if artifact can be modified by user :param ctx: user context :param af: artifact definition :raise Forbidden if access is not allowed """ if not ctx.is_admin and ctx.tenant != af.owner or ctx.read_only: raise exception.Forbidden()
def _check_read_only_access(ctx, af): """Check if user has read only access to artifact :param ctx: user context :param af: artifact definition :raise Forbidden if access is not allowed """ private = af.visibility != 'public' if (private and not ctx.is_admin and ctx.tenant != af.owner): # TODO(kairat): check artifact sharing here raise exception.Forbidden()
def authenticate(self, access_token, realm_name): info = None if self.mcclient: info = self.mcclient.get(access_token) if info is None and CONF.keycloak_oidc.user_info_endpoint_url: url = self.url_template % realm_name verify = None if urllib.parse.urlparse(url).scheme == "https": verify = False if self.insecure else self.cafile cert = (self.certfile, self.keyfile) \ if self.certfile and self.keyfile else None try: resp = requests.get( url, headers={"Authorization": "Bearer %s" % access_token}, verify=verify, cert=cert ) except requests.ConnectionError: msg = _("Can't connect to keycloak server with address '%s'." ) % CONF.keycloak_oidc.auth_url LOG.error(msg) raise exception.GlareException(message=msg) if resp.status_code == 400: raise exception.BadRequest(message=resp.text) if resp.status_code == 401: LOG.warning("HTTP response from OIDC provider:" " [%s] with WWW-Authenticate: [%s]", pprint.pformat(resp.text), resp.headers.get("WWW-Authenticate")) raise exception.Unauthorized(message=resp.text) if resp.status_code == 403: raise exception.Forbidden(message=resp.text) elif resp.status_code > 400: raise exception.GlareException(message=resp.text) if self.mcclient: self.mcclient.set(access_token, resp.json(), time=CONF.keycloak_oidc.token_cache_time) info = resp.json() LOG.debug("HTTP response from OIDC provider: %s", pprint.pformat(info)) return info
def create(self, req, type_name, values): """Create artifact record in Glare. :param req: user request :param type_name: artifact type name :param values: dict with artifact fields :return: definition of created artifact """ if req.context.project_id is None or req.context.read_only: msg = _("It's forbidden to anonymous users to create artifacts.") raise exc.Forbidden(msg) if not values.get('name'): msg = _("Name must be specified at creation.") raise exc.BadRequest(msg) for field in ('visibility', 'status', 'display_type_name'): if field in values: msg = _("%s is not allowed in a request at creation.") % field raise exc.BadRequest(msg) return self.engine.create(req.context, type_name, values)
def update_blob(cls, context, af_id, field_name, values): raise exception.Forbidden("This type is read only.")
def delete(cls, context, af): raise exception.Forbidden("This type is read only.")
def save(self, context): raise exception.Forbidden("This type is read only.")
def create(cls, context): raise exception.Forbidden("This type is read only.")
def download_blob(self, context, type_name, artifact_id, field_name, blob_key=None): """Download binary data from Glare Artifact. :param context: user context :param type_name: name of artifact type :param artifact_id: id of the artifact to be updated :param field_name: name of blob or blob dict field :param blob_key: if field_name is blob dict it specifies key in this dict :return: file iterator for requested file """ download_from_any_artifact = False if policy.authorize("artifact:download_from_any_artifact", {}, context, do_raise=False): download_from_any_artifact = True af = self._show_artifact(context, type_name, artifact_id, read_only=True, get_any_artifact=download_from_any_artifact) if not download_from_any_artifact: policy.authorize("artifact:download", af.to_dict(), context) blob_name = self._generate_blob_name(field_name, blob_key) if af.status == 'deleted': msg = _("Cannot download data when artifact is deleted") raise exception.Forbidden(message=msg) blob = self._get_blob_info(af, field_name, blob_key) if blob is None: msg = _("No data found for blob %s") % blob_name raise exception.NotFound(message=msg) if blob['status'] != 'active': msg = _("%s is not ready for download") % blob_name raise exception.Conflict(message=msg) af.pre_download_hook(context, af, field_name, blob_key) meta = { 'md5': blob.get('md5'), 'sha1': blob.get('sha1'), 'sha256': blob.get('sha256'), 'external': blob.get('external') } if blob['external']: data = {'url': blob['url']} else: data = store_api.load_from_store(uri=blob['url'], context=context) meta['size'] = blob.get('size') meta['content_type'] = blob.get('content_type') try: # call download hook in the end data = af.post_download_hook(context, af, field_name, blob_key, data) except exception.GlareException: raise except Exception as e: raise exception.BadRequest(message=str(e)) return data, meta
def add_blob_location(self, context, type_name, artifact_id, field_name, location, blob_meta, blob_key=None): """Add external/internal location to blob. :param context: user context :param type_name: name of artifact type :param artifact_id: id of the artifact to be updated :param field_name: name of blob or blob dict field :param location: blob url :param blob_meta: dictionary containing blob metadata like md5 checksum :param blob_key: if field_name is blob dict it specifies key in this dict :return: dict representation of updated artifact """ blob_name = self._generate_blob_name(field_name, blob_key) location_type = blob_meta.pop('location_type', 'external') if location_type == 'external': action_name = 'artifact:set_location' elif location_type == 'internal': scheme = urlparse.urlparse(location).scheme if scheme in store_api.RESTRICTED_URI_SCHEMES: msg = _("Forbidden to set internal locations with " "scheme '%s'") % scheme raise exception.Forbidden(msg) if scheme not in store_api.get_known_schemes(): msg = _("Unknown scheme '%s'") % scheme raise exception.BadRequest(msg) action_name = 'artifact:set_internal_location' else: msg = _("Invalid location type: %s") % location_type raise exception.BadRequest(msg) blob = { 'url': location, 'size': None, 'md5': blob_meta.get("md5"), 'sha1': blob_meta.get("sha1"), 'id': uuidutils.generate_uuid(), 'sha256': blob_meta.get("sha256"), 'status': 'active', 'external': location_type == 'external', 'content_type': None } lock_key = "%s:%s" % (type_name, artifact_id) with self.lock_engine.acquire(context, lock_key): af = self._show_artifact(context, type_name, artifact_id) policy.authorize(action_name, af.to_dict(), context) if self._get_blob_info(af, field_name, blob_key): msg = _("Blob %(blob)s already exists for artifact " "%(af)s") % { 'blob': field_name, 'af': af.id } raise exception.Conflict(message=msg) utils.validate_change_allowed(af, field_name) af.pre_add_location_hook(context, af, field_name, location, blob_key) af = self._save_blob_info(context, af, field_name, blob_key, blob) LOG.info( "External location %(location)s has been created " "successfully for artifact %(artifact)s blob %(blob)s", { 'location': location, 'artifact': af.id, 'blob': blob_name }) af.post_add_location_hook(context, af, field_name, blob_key) Notifier.notify(context, action_name, af) return af.to_dict()