Merge "Adding support to super user roles."

This commit is contained in:
Zuul 2018-02-20 10:10:07 +00:00 committed by Gerrit Code Review
commit 900af2f99b
6 changed files with 206 additions and 29 deletions

View File

@ -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"),

View File

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

View File

@ -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.

View File

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

View File

@ -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

View File

@ -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')