Merge "Adding support to super user roles."
This commit is contained in:
commit
900af2f99b
|
@ -51,8 +51,13 @@ artifact_policy_rules = [
|
|||
"Policy to publish artifact"),
|
||||
policy.RuleDefault("artifact:get", "",
|
||||
"Policy to get artifact definition"),
|
||||
policy.RuleDefault("artifact:get_any_artifact", "rule:context_is_admin",
|
||||
"Policy to get artifact from any project"),
|
||||
policy.RuleDefault("artifact:list", "",
|
||||
"Policy to list artifacts"),
|
||||
policy.RuleDefault("artifact:list_all_artifacts",
|
||||
"rule:context_is_admin",
|
||||
"Policy to list artifacts from all projects"),
|
||||
policy.RuleDefault("artifact:delete_public",
|
||||
"'public':%(visibility)s and rule:context_is_admin "
|
||||
"or not 'public':%(visibility)s",
|
||||
|
@ -80,6 +85,11 @@ artifact_policy_rules = [
|
|||
"rule:admin_or_owner and "
|
||||
"rule:artifact:download_deactivated",
|
||||
"Policy to download blob from artifact"),
|
||||
policy.RuleDefault("artifact:download_from_any_artifact",
|
||||
"rule:context_is_admin",
|
||||
"Policy to download blob from any artifact"
|
||||
" in any project"
|
||||
),
|
||||
policy.RuleDefault("artifact:delete_blob", "rule:admin_or_owner",
|
||||
"Policy to delete blob with external location "
|
||||
"from artifact"),
|
||||
|
|
|
@ -87,20 +87,24 @@ class ArtifactAPI(object):
|
|||
|
||||
@retry(retry_on_exception=_retry_on_connection_error, wait_fixed=1000,
|
||||
stop_max_attempt_number=20)
|
||||
def get(self, context, type_name, artifact_id):
|
||||
def get(self, context, type_name, artifact_id, get_any_artifact=False):
|
||||
"""Return artifact values from database
|
||||
|
||||
:param context: user context
|
||||
:param type_name: artifact type name or None for metatypes
|
||||
:param artifact_id: id of the artifact
|
||||
:param get_any_artifact: flag that indicate, if we want to enable
|
||||
to get artifact from other realm as well.
|
||||
:return: dict of artifact values
|
||||
"""
|
||||
session = api.get_session()
|
||||
return api.get(context, type_name, artifact_id, session)
|
||||
return api.get(context, type_name, artifact_id,
|
||||
session, get_any_artifact)
|
||||
|
||||
@retry(retry_on_exception=_retry_on_connection_error, wait_fixed=1000,
|
||||
stop_max_attempt_number=20)
|
||||
def list(self, context, filters, marker, limit, sort, latest):
|
||||
def list(self, context, filters, marker, limit, sort,
|
||||
latest, list_all_artifacts=False):
|
||||
"""List artifacts from db
|
||||
|
||||
:param context: user request context
|
||||
|
@ -111,13 +115,17 @@ class ArtifactAPI(object):
|
|||
:param sort: sort conditions
|
||||
:param latest: flag that indicates, that only artifacts with highest
|
||||
versions should be returned in output
|
||||
:param list_all_artifacts: flag that indicate, if the list should
|
||||
return artifact from all realms (True),
|
||||
or from the specific realm (False)
|
||||
:return: list of artifacts. Each artifact is represented as dict of
|
||||
values.
|
||||
"""
|
||||
session = api.get_session()
|
||||
return api.get_all(context=context, session=session, filters=filters,
|
||||
marker=marker, limit=limit, sort=sort,
|
||||
latest=latest)
|
||||
latest=latest,
|
||||
list_all_artifacts=list_all_artifacts)
|
||||
|
||||
@retry(retry_on_exception=_retry_on_connection_error, wait_fixed=1000,
|
||||
stop_max_attempt_number=20)
|
||||
|
|
|
@ -164,10 +164,11 @@ def create_or_update(context, artifact_id, values, session):
|
|||
return artifact.to_dict()
|
||||
|
||||
|
||||
def _get(context, type_name, artifact_id, session):
|
||||
def _get(context, type_name, artifact_id, session, get_any_artifact=False):
|
||||
try:
|
||||
query = _do_artifacts_query(context, session).filter_by(
|
||||
id=artifact_id)
|
||||
query = _do_artifacts_query(
|
||||
context, session, list_all_artifacts=get_any_artifact).\
|
||||
filter_by(id=artifact_id)
|
||||
if type_name is not None:
|
||||
query = query.filter_by(type_name=type_name)
|
||||
artifact = query.one()
|
||||
|
@ -178,12 +179,13 @@ def _get(context, type_name, artifact_id, session):
|
|||
return artifact
|
||||
|
||||
|
||||
def get(context, type_name, artifact_id, session):
|
||||
return _get(context, type_name, artifact_id, session).to_dict()
|
||||
def get(context, type_name, artifact_id, session, get_any_artifact=False):
|
||||
return _get(context, type_name, artifact_id,
|
||||
session, get_any_artifact).to_dict()
|
||||
|
||||
|
||||
def get_all(context, session, filters=None, marker=None, limit=None,
|
||||
sort=None, latest=False):
|
||||
sort=None, latest=False, list_all_artifacts=False):
|
||||
"""List all visible artifacts
|
||||
|
||||
:param filters: dict of filter keys and values.
|
||||
|
@ -193,11 +195,15 @@ def get_all(context, session, filters=None, marker=None, limit=None,
|
|||
which results should be sorted, dir is a direction: 'asc' or 'desc',
|
||||
and type is type of the attribute: 'bool', 'string', 'numeric' or 'int' or
|
||||
None if attribute is base.
|
||||
:param list_all_artifacts: flag that indicate, if the list should
|
||||
return artifact from all realms (True),
|
||||
or from the specific realm (False)
|
||||
:param latest: flag that indicates, that only artifacts with highest
|
||||
versions should be returned in output
|
||||
"""
|
||||
artifacts = _get_all(
|
||||
context, session, filters, marker, limit, sort, latest)
|
||||
context, session, filters, marker,
|
||||
limit, sort, latest, list_all_artifacts)
|
||||
total_artifacts_count = get_artifact_count(context, session, filters,
|
||||
latest)
|
||||
return {
|
||||
|
@ -265,11 +271,11 @@ def _apply_user_filters(query, basic_conds, tag_conds, prop_conds):
|
|||
|
||||
|
||||
def _get_all(context, session, filters=None, marker=None, limit=None,
|
||||
sort=None, latest=False):
|
||||
sort=None, latest=False, list_all_artifacts=False):
|
||||
|
||||
filters = filters or {}
|
||||
|
||||
query = _do_artifacts_query(context, session)
|
||||
query = _do_artifacts_query(context, session, list_all_artifacts)
|
||||
|
||||
basic_conds, tag_conds, prop_conds = _do_query_filters(filters)
|
||||
|
||||
|
@ -375,7 +381,7 @@ def _do_paginate_query(query, marker=None, limit=None, sort=None):
|
|||
return query
|
||||
|
||||
|
||||
def _do_artifacts_query(context, session):
|
||||
def _do_artifacts_query(context, session, list_all_artifacts=False):
|
||||
"""Build the query to get all artifacts based on the context"""
|
||||
|
||||
query = session.query(models.Artifact)
|
||||
|
@ -384,12 +390,12 @@ def _do_artifacts_query(context, session):
|
|||
options(joinedload(models.Artifact.tags)).
|
||||
options(joinedload(models.Artifact.blobs)))
|
||||
|
||||
return _apply_query_base_filters(query, context)
|
||||
return _apply_query_base_filters(query, context, list_all_artifacts)
|
||||
|
||||
|
||||
def _apply_query_base_filters(query, context):
|
||||
def _apply_query_base_filters(query, context, list_all_artifacts=False):
|
||||
# If admin, return everything.
|
||||
if context.is_admin:
|
||||
if context.is_admin or list_all_artifacts:
|
||||
return query
|
||||
|
||||
# If anonymous user, return only public artifacts.
|
||||
|
|
|
@ -110,7 +110,8 @@ class Engine(object):
|
|||
return lock
|
||||
|
||||
@staticmethod
|
||||
def _show_artifact(ctx, type_name, artifact_id, read_only=False):
|
||||
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.
|
||||
|
@ -120,11 +121,13 @@ class Engine(object):
|
|||
: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)
|
||||
if not read_only:
|
||||
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",
|
||||
|
@ -307,9 +310,14 @@ class Engine(object):
|
|||
:param artifact_id: id of artifact to show
|
||||
:return: definition of requested artifact
|
||||
"""
|
||||
get_any_artifact = False
|
||||
if policy.authorize("artifact:get_any_artifact", {},
|
||||
context, do_raise=False):
|
||||
get_any_artifact = True
|
||||
policy.authorize("artifact:get", {}, context)
|
||||
af = self._show_artifact(context, type_name, artifact_id,
|
||||
read_only=True)
|
||||
read_only=True,
|
||||
get_any_artifact=get_any_artifact)
|
||||
return af.to_dict()
|
||||
|
||||
@staticmethod
|
||||
|
@ -329,11 +337,16 @@ class Engine(object):
|
|||
versions should be returned in output
|
||||
:return: list of artifact definitions
|
||||
"""
|
||||
list_all_artifacts = False
|
||||
if policy.authorize("artifact:list_all_artifacts",
|
||||
{}, context, do_raise=False):
|
||||
list_all_artifacts = True
|
||||
policy.authorize("artifact:list", {}, context)
|
||||
artifact_type = registry.ArtifactRegistry.get_artifact_type(type_name)
|
||||
# return list to the user
|
||||
artifacts_data = artifact_type.list(context, filters, marker,
|
||||
limit, sort, latest)
|
||||
|
||||
artifacts_data = artifact_type.list(
|
||||
context, filters, marker, limit, sort, latest, list_all_artifacts)
|
||||
artifacts_data["artifacts"] = [af.to_dict()
|
||||
for af in artifacts_data["artifacts"]]
|
||||
return artifacts_data
|
||||
|
@ -631,9 +644,17 @@ class Engine(object):
|
|||
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)
|
||||
policy.authorize("artifact:download", af.to_dict(), context)
|
||||
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)
|
||||
|
||||
|
|
|
@ -277,7 +277,7 @@ Possible values:
|
|||
return self.init_artifact(context, updated_af)
|
||||
|
||||
@classmethod
|
||||
def show(cls, context, artifact_id):
|
||||
def show(cls, context, artifact_id, get_any_artifact=False):
|
||||
"""Return Artifact from Glare repo
|
||||
|
||||
:param context: user context
|
||||
|
@ -288,7 +288,7 @@ Possible values:
|
|||
type_name = cls.get_type_name()
|
||||
else:
|
||||
type_name = None
|
||||
af = cls.db_api.get(context, type_name, artifact_id)
|
||||
af = cls.db_api.get(context, type_name, artifact_id, get_any_artifact)
|
||||
return cls.init_artifact(context, af)
|
||||
|
||||
@classmethod
|
||||
|
@ -396,7 +396,7 @@ Possible values:
|
|||
|
||||
@classmethod
|
||||
def list(cls, context, filters=None, marker=None, limit=None,
|
||||
sort=None, latest=False):
|
||||
sort=None, latest=False, list_all_artifacts=False):
|
||||
"""Return list of artifacts requested by user.
|
||||
|
||||
:param context: user context
|
||||
|
@ -408,6 +408,9 @@ Possible values:
|
|||
:param sort: sorting options
|
||||
:param latest: flag that indicates, that only artifacts with highest
|
||||
versions should be returned in output
|
||||
:param list_all_artifacts: flag that indicate, if the list should
|
||||
return artifact from all tenants (True),
|
||||
or from the specific tenant (False)
|
||||
:return: list of artifact objects
|
||||
"""
|
||||
|
||||
|
@ -435,7 +438,7 @@ Possible values:
|
|||
filters.append(default_filter)
|
||||
|
||||
artifacts_data = cls.db_api.list(context, filters, marker, limit,
|
||||
sort, latest)
|
||||
sort, latest, list_all_artifacts)
|
||||
artifacts_data["artifacts"] = [cls.init_artifact(context, af)
|
||||
for af in artifacts_data["artifacts"]]
|
||||
return artifacts_data
|
||||
|
|
|
@ -11,8 +11,11 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from io import BytesIO
|
||||
|
||||
from glare.common import exception as exc
|
||||
from glare.common import store_api
|
||||
|
||||
from glare.tests.unit import base
|
||||
|
||||
|
||||
|
@ -45,3 +48,129 @@ class TestPolicies(base.BaseTestArtifactAPI):
|
|||
self.controller.list_type_schemas, self.req)
|
||||
self.assertRaises(exc.PolicyException,
|
||||
self.controller.list_type_schemas, admin_req)
|
||||
|
||||
def test_list_all_artifacts(self):
|
||||
|
||||
# create artifacts with user1 and user 2
|
||||
user1_req = self.get_fake_request(user=self.users['user1'])
|
||||
user2_req = self.get_fake_request(user=self.users['user2'])
|
||||
|
||||
for i in range(5):
|
||||
self.controller.create(user1_req, 'sample_artifact',
|
||||
{'name': 'user1%s' % i,
|
||||
'version': '%d.0' % i})
|
||||
|
||||
self.controller.create(user2_req, 'sample_artifact',
|
||||
{'name': 'user2%s' % i,
|
||||
'version': '%d.0' % i})
|
||||
|
||||
# user1 list only its realm artifacts
|
||||
user1_art_list = self.controller.list(user1_req, 'sample_artifact')
|
||||
self.assertEqual(5, len(user1_art_list["artifacts"]))
|
||||
|
||||
# user2 list only it's realm artifact
|
||||
user2_art_list = self.controller.list(user2_req, 'sample_artifact')
|
||||
self.assertEqual(5, len(user2_art_list["artifacts"]))
|
||||
|
||||
# enable to list all arts from all realms for a user with role su_role
|
||||
rule = {"artifact:list_all_artifacts": "role:su_role"}
|
||||
self.policy(rule)
|
||||
|
||||
# Append su_role to 'user1'
|
||||
self.users['user1']['roles'].append("su_role")
|
||||
list_all_art = self.controller.list(user1_req, "sample_artifact")
|
||||
|
||||
# now glare returns all the artifacts (from all the realms)
|
||||
self.assertEqual(10, len(list_all_art["artifacts"]))
|
||||
|
||||
def test_get_any_artifact(self):
|
||||
|
||||
# create artifacts with user1 and user 2
|
||||
user1_req = self.get_fake_request(user=self.users['user1'])
|
||||
user2_req = self.get_fake_request(user=self.users['user2'])
|
||||
|
||||
art1 = self.controller.create(user1_req, 'sample_artifact',
|
||||
{'name': 'user1%s' % 1,
|
||||
'version': '%d.0' % 1})
|
||||
|
||||
art2 = self.controller.create(user2_req, 'sample_artifact',
|
||||
{'name': 'user2%s' % 2,
|
||||
'version': '%d.0' % 2})
|
||||
|
||||
# user1 can get artifacts from its realm
|
||||
self.controller.show(user1_req, 'sample_artifact', art1['id'])
|
||||
|
||||
# user1 cannot get artifacts from other realm
|
||||
self.assertRaises(exc.NotFound, self.controller.show, user1_req,
|
||||
'sample_artifact', art2['id'])
|
||||
|
||||
# get_any_artifact
|
||||
rule = {"artifact:get_any_artifact": "role:su_role"}
|
||||
self.policy(rule)
|
||||
|
||||
# user2 can get artifact from his realm only
|
||||
self.controller.show(user2_req, 'sample_artifact', art2['id'])
|
||||
self.assertRaises(exc.NotFound, self.controller.show, user2_req,
|
||||
'sample_artifact', art1['id'])
|
||||
|
||||
# Append su_role to 'user1'
|
||||
self.users['user1']['roles'].append("su_role")
|
||||
|
||||
# Now user1 can get artifact from other realm
|
||||
self.controller.show(user1_req, 'sample_artifact', art2['id'])
|
||||
|
||||
def test_download_from_any_artifact(self):
|
||||
|
||||
# create artifacts with user1 and user 2
|
||||
user1_req = self.get_fake_request(user=self.users['user1'])
|
||||
user2_req = self.get_fake_request(user=self.users['user2'])
|
||||
|
||||
art1 = self.controller.create(user1_req, 'sample_artifact',
|
||||
{'name': 'user1%s' % 1,
|
||||
'version': '%d.0' % 1})
|
||||
|
||||
art2 = self.controller.create(user2_req, 'sample_artifact',
|
||||
{'name': 'user2%s' % 2,
|
||||
'version': '%d.0' % 2})
|
||||
|
||||
# Upload blobs
|
||||
self.controller.upload_blob(
|
||||
user1_req, 'sample_artifact', art1['id'], 'blob',
|
||||
BytesIO(b'a' * 100), 'application/octet-stream')
|
||||
|
||||
self.controller.upload_blob(
|
||||
user2_req, 'sample_artifact', art2['id'], 'blob',
|
||||
BytesIO(b'a' * 50), 'application/octet-stream')
|
||||
|
||||
# Download blobs
|
||||
flobj1 = self.controller.download_blob(
|
||||
user1_req, 'sample_artifact', art1['id'], 'blob')
|
||||
self.assertEqual(b'a' * 100, store_api.read_data(flobj1['data']))
|
||||
|
||||
flobj2 = self.controller.download_blob(
|
||||
user2_req, 'sample_artifact', art2['id'], 'blob')
|
||||
self.assertEqual(b'a' * 50, store_api.read_data(flobj2['data']))
|
||||
|
||||
# Make sure user2 cannot download blob from artifact in realm1
|
||||
self.assertRaises(exc.NotFound, self.controller.download_blob,
|
||||
user2_req, 'sample_artifact', art1['id'], 'blob')
|
||||
|
||||
# Add role def to policy
|
||||
rule = {"artifact:download_from_any_artifact": "role:su_role"}
|
||||
self.policy(rule)
|
||||
|
||||
# Make sure user1 cannot download blob from artifact in realm2
|
||||
self.assertRaises(exc.NotFound, self.controller.download_blob,
|
||||
user1_req, 'sample_artifact', art2['id'], 'blob')
|
||||
|
||||
# Append su_role to 'user1'
|
||||
self.users['user1']['roles'].append("su_role")
|
||||
|
||||
# Now user1 can get download blob from other realm
|
||||
flobj2 = self.controller.download_blob(
|
||||
user1_req, 'sample_artifact', art2['id'], 'blob')
|
||||
self.assertEqual(b'a' * 50, store_api.read_data(flobj2['data']))
|
||||
|
||||
# User2 still cannot download blob from artifact in realm1
|
||||
self.assertRaises(exc.NotFound, self.controller.download_blob,
|
||||
user2_req, 'sample_artifact', art1['id'], 'blob')
|
||||
|
|
Loading…
Reference in New Issue