diff --git a/dci/api/v1/analytics.py b/dci/api/v1/analytics.py index 72da95c8f..87b5aed18 100644 --- a/dci/api/v1/analytics.py +++ b/dci/api/v1/analytics.py @@ -24,7 +24,7 @@ from dci.analytics import query_es_dsl as qed from dci.api.v1 import api from dci.api.v1 import base -from dci.api.v1 import export_control +from dci.api.v1 import permissions from dci.common import exceptions as dci_exc from dci.common.schemas import ( analytics_task_duration_cumulated, @@ -60,7 +60,7 @@ def tasks_duration_cumulated(user): if user.is_not_super_admin() and user.is_not_epm() and user.is_not_read_only_user(): if remoteci.team_id not in user.teams_ids: raise dci_exc.Unauthorized() - export_control.verify_access_to_topic(user, topic) + permissions.verify_access_to_topic(user, topic) query = "q=topic_id:%s AND remoteci_id:%s" % (args["topic_id"], args["remoteci_id"]) offset, limit = handle_pagination(args) diff --git a/dci/api/v1/components.py b/dci/api/v1/components.py index 5c36e75e4..cee26c86c 100644 --- a/dci/api/v1/components.py +++ b/dci/api/v1/components.py @@ -24,8 +24,9 @@ from dci.api.v1 import api, notifications from dci.api.v1 import base -from dci.api.v1 import export_control +from dci.api.v1 import permissions from dci.api.v1 import utils as v1_utils +from dci.api.v2 import components as components_v2 from dci import decorators from dci.common import exceptions as dci_exc from dci.common.schemas import ( @@ -47,42 +48,6 @@ logger = logging.getLogger(__name__) -def get_components_access_teams_ids(teams_ids): - components_access_teams_ids = [] - JTCA = models2.JOIN_TEAMS_COMPONENTS_ACCESS - for team_id in teams_ids: - query = flask.g.session.query(JTCA) - query = query.filter(JTCA.c.team_id == team_id) - teams_components_access = query.all() - if teams_components_access: - for tca in teams_components_access: - components_access_teams_ids.append(tca.access_team_id) - return components_access_teams_ids - - -def _verify_component_and_topic_access(user, component): - component_team_id = component.team_id - if component_team_id is not None: - if user.is_not_in_team(component_team_id): - components_access_teams_ids = get_components_access_teams_ids( - user.teams_ids - ) - if component_team_id not in components_access_teams_ids: - raise dci_exc.Unauthorized() - else: - topic = base.get_resource_orm(models2.Topic, component.topic_id) - export_control.verify_access_to_topic(user, topic) - - -def _verify_component_access_and_role(user, component): - component_team_id = component.team_id - if component_team_id is not None: - if user.is_not_in_team(component_team_id): - dci_exc.Unauthorized() - elif user.is_not_super_admin() and user.is_not_feeder() and user.is_not_epm(): - raise dci_exc.Unauthorized() - - @api.route("/components", methods=["POST"]) @decorators.login_required def create_components(user): @@ -135,7 +100,7 @@ def update_components(user, c_id): if_match_etag = utils.check_and_get_etag(flask.request.headers) component = base.get_resource_orm(models2.Component, c_id, if_match_etag) - _verify_component_and_topic_access(user, component) + permissions.verify_access_to_component(user, component) initial_component_state = component.state @@ -180,7 +145,9 @@ def get_all_components(user, topics_ids): ) if user.is_not_super_admin() and user.is_not_feeder() and user.is_not_epm(): - components_access_teams_ids = get_components_access_teams_ids(user.teams_ids) + components_access_teams_ids = permissions.get_components_access_teams_ids( + user.teams_ids + ) query = query.filter( sql.or_( models2.Component.team_id.in_(user.teams_ids), @@ -201,7 +168,7 @@ def get_all_components(user, topics_ids): @api.route("/components", methods=["GET"]) @decorators.login_required def get_components(user): - topics_ids = export_control.get_user_topic_ids(user) + topics_ids = permissions.get_user_topic_ids(user) return get_all_components(user, topics_ids) @@ -211,7 +178,7 @@ def get_component_by_id(user, c_id): component = base.get_resource_orm( models2.Component, c_id, options=[sa_orm.selectinload("files")] ) - _verify_component_and_topic_access(user, component) + permissions.verify_access_to_component(user, component) component_jobs_query = ( flask.g.session.query(models2.Job) @@ -242,7 +209,7 @@ def get_component_by_id(user, c_id): def delete_component_by_id(user, c_id): if_match_etag = utils.check_and_get_etag(flask.request.headers) component = base.get_resource_orm(models2.Component, c_id, if_match_etag) - _verify_component_access_and_role(user, component) + permissions.can_delete_component(user, component) base.update_resource_orm(component, {"state": "archived"}) return flask.Response(None, 204, content_type="application/json") @@ -252,7 +219,7 @@ def delete_component_by_id(user, c_id): @decorators.login_required def list_components_files(user, c_id): component = base.get_resource_orm(models2.Component, c_id) - _verify_component_and_topic_access(user, component) + permissions.verify_access_to_component(user, component) args = check_and_get_args(flask.request.args.to_dict()) query = flask.g.session.query(models2.Componentfile) @@ -278,8 +245,9 @@ def list_components_files(user, c_id): @decorators.login_required def get_component_file_from_s3(user, c_id, filepath): component = base.get_resource_orm(models2.Component, c_id) - _verify_component_and_topic_access(user, component) - + permissions.verify_access_to_component(user, component) + if component.tags and "rhdl" in component.tags: + return components_v2.get_component_file_from_rhdl(filepath, component) normalized_filepath = os.path.normpath("/" + filepath).lstrip("/") normalized_component_id_filepath = os.path.join(str(c_id), normalized_filepath) component_id_filepath = os.path.join(str(c_id), filepath) @@ -302,19 +270,17 @@ def get_component_file_from_s3(user, c_id, filepath): @decorators.login_required def get_component_file_by_id(user, c_id, f_id): component = base.get_resource_orm(models2.Component, c_id) - _verify_component_and_topic_access(user, component) + permissions.verify_access_to_component(user, component) componentfile = base.get_resource_orm(models2.Componentfile, f_id) - - res = flask.jsonify({"component_file": componentfile.serialize()}) - return res + return flask.jsonify({"component_file": componentfile.serialize()}) @api.route("/components//files//content", methods=["GET"]) @decorators.login_required def download_component_file(user, c_id, f_id): component = base.get_resource_orm(models2.Component, c_id) - _verify_component_and_topic_access(user, component) + permissions.verify_access_to_component(user, component) store = flask.g.store @@ -332,7 +298,7 @@ def download_component_file(user, c_id, f_id): @decorators.login_required def upload_component_file(user, c_id): component = base.get_resource_orm(models2.Component, c_id) - _verify_component_and_topic_access(user, component) + permissions.verify_access_to_component(user, component) if str(component.topic_id) not in v1_utils.user_topic_ids(flask.g.session, user): raise dci_exc.Unauthorized() @@ -370,7 +336,7 @@ def upload_component_file(user, c_id): def delete_component_file(user, c_id, f_id): if_match_etag = utils.check_and_get_etag(flask.request.headers) component = base.get_resource_orm(models2.Component, c_id) - _verify_component_access_and_role(user, component) + permissions.can_delete_component(user, component) componentfile = base.get_resource_orm(models2.Componentfile, f_id, if_match_etag) base.update_resource_orm(componentfile, {"state": "archived"}) diff --git a/dci/api/v1/jobs.py b/dci/api/v1/jobs.py index f9f80bbd4..eb6be42a8 100644 --- a/dci/api/v1/jobs.py +++ b/dci/api/v1/jobs.py @@ -44,7 +44,7 @@ from dci.db import models2 from dci.api.v1 import files -from dci.api.v1 import export_control +from dci.api.v1 import permissions from dci.api.v1 import jobstates @@ -97,7 +97,7 @@ def internal_create_jobs(user, values, components_ids=None): if topic.state != "active": msg = "Topic %s:%s not active." % (topic_id, topic.name) raise dci_exc.DCIException(msg, status_code=412) - export_control.verify_access_to_topic(user, topic) + permissions.verify_access_to_topic(user, topic) previous_job_id = values.get("previous_job_id") if previous_job_id: @@ -119,7 +119,7 @@ def internal_create_jobs(user, values, components_ids=None): "previous_job_id": previous_job_id, } ) - components_access_teams_ids = components.get_components_access_teams_ids( + components_access_teams_ids = permissions.get_components_access_teams_ids( user.teams_ids ) # schedule @@ -392,7 +392,7 @@ def add_component_to_job(user, job_id): j = base.get_resource_orm(models2.Job, job_id) component = base.get_resource_orm(models2.Component, values["id"]) - components_access_teams_ids = components.get_components_access_teams_ids( + components_access_teams_ids = permissions.get_components_access_teams_ids( user.teams_ids ) diff --git a/dci/api/v1/export_control.py b/dci/api/v1/permissions.py similarity index 65% rename from dci/api/v1/export_control.py rename to dci/api/v1/permissions.py index d6160482d..5e88762a3 100644 --- a/dci/api/v1/export_control.py +++ b/dci/api/v1/permissions.py @@ -84,3 +84,42 @@ def get_user_topic_ids(user): if user.has_not_pre_release_access(): filters.append(models2.Topic.export_control == True) # noqa return [t.id for t in base.get_resources_orm(models2.Topic, filters)] + + +def get_components_access_teams_ids(teams_ids): + """A team can allow another team to see its components. + This method returns the list of teams ids that allowed teams_ids to see theirs components. + """ + components_access_teams_ids = [] + JTCA = models2.JOIN_TEAMS_COMPONENTS_ACCESS + for team_id in teams_ids: + query = flask.g.session.query(JTCA) + query = query.filter(JTCA.c.team_id == team_id) + teams_components_access = query.all() + if teams_components_access: + for tca in teams_components_access: + components_access_teams_ids.append(tca.access_team_id) + return components_access_teams_ids + + +def verify_access_to_component(user, component): + component_team_id = component.team_id + if component_team_id is not None: + if user.is_not_in_team(component_team_id): + components_access_teams_ids = get_components_access_teams_ids( + user.teams_ids + ) + if component_team_id not in components_access_teams_ids: + raise dci_exc.Unauthorized() + else: + topic = base.get_resource_orm(models2.Topic, component.topic_id) + verify_access_to_topic(user, topic) + + +def can_delete_component(user, component): + component_team_id = component.team_id + if component_team_id is not None: + if user.is_not_in_team(component_team_id): + dci_exc.Unauthorized() + elif user.is_not_super_admin() and user.is_not_feeder() and user.is_not_epm(): + raise dci_exc.Unauthorized() diff --git a/dci/api/v1/topics.py b/dci/api/v1/topics.py index 629b34332..5a8284d13 100644 --- a/dci/api/v1/topics.py +++ b/dci/api/v1/topics.py @@ -21,7 +21,7 @@ from dci.api.v1 import api from dci.api.v1 import base from dci.api.v1 import components -from dci.api.v1 import export_control +from dci.api.v1 import permissions from dci.api.v1 import utils as v1_utils from dci import decorators from dci.common import exceptions as dci_exc @@ -78,7 +78,7 @@ def get_topic_by_id(user, topic_id): and user.is_not_feeder() and user.is_not_read_only_user() ): - export_control.verify_access_to_topic(user, topic) + permissions.verify_access_to_topic(user, topic) return flask.Response( json.dumps({"topic": topic_serialized}), @@ -100,7 +100,7 @@ def get_all_topics(user): ) if user.is_not_super_admin() and user.is_not_read_only_user() and user.is_not_epm(): - product_ids = export_control.get_user_product_ids(user) + product_ids = permissions.get_user_product_ids(user) q = q.filter(models2.Topic.product_id.in_(product_ids)) if user.has_not_pre_release_access(): q = q.filter(models2.Topic.export_control == True) # noqa @@ -166,21 +166,21 @@ def delete_topic_by_id(user, topic_id): @decorators.login_required def get_topics_components(user, topic_id): topic = base.get_resource_orm(models2.Topic, topic_id) - export_control.verify_access_to_topic(user, topic) + permissions.verify_access_to_topic(user, topic) return components.get_all_components(user, [topic_id]) @api.route("/topics//notifications", methods=["POST"]) @decorators.login_required def subscribe_user_to_topic(user, topic_id): - t = base.get_resource_orm(models2.Topic, topic_id) - export_control.verify_access_to_topic(user, t) - ut = base.create_resource_orm( + topic = base.get_resource_orm(models2.Topic, topic_id) + permissions.verify_access_to_topic(user, topic) + user_topic = base.create_resource_orm( models2.UserTopic, {"user_id": user.id, "topic_id": topic_id} ) return flask.Response( - json.dumps(ut), + json.dumps(user_topic), 201, content_type="application/json", ) diff --git a/dci/api/v2/components.py b/dci/api/v2/components.py index 6825c250e..97f450bba 100644 --- a/dci/api/v2/components.py +++ b/dci/api/v2/components.py @@ -22,7 +22,7 @@ from dci.api.v2 import api from dci.api.v1 import base -from dci.api.v1.components import _verify_component_and_topic_access +from dci.api.v1 import permissions from dci import decorators from dci.common import exceptions as dci_exc from dci.dci_config import CONFIG @@ -33,12 +33,7 @@ logger = logging.getLogger(__name__) -@api.route("/components//files/", methods=["GET", "HEAD"]) -@decorators.login_required -def get_component_file_from_rhdl(user, c_id, filepath): - component = base.get_resource_orm(models2.Component, c_id) - _verify_component_and_topic_access(user, component) - +def get_component_file_from_rhdl(filepath, component): if filepath == "dci_files_list.json": filepath = "rhdl_files_list.json" normalized_filepath = os.path.normpath("/" + filepath).lstrip("/") @@ -72,3 +67,12 @@ def get_component_file_from_rhdl(user, c_id, filepath): ) return flask.Response(None, 302, headers={"Location": redirect.headers["Location"]}) + + +@api.route("/components//files/", methods=["GET", "HEAD"]) +@decorators.login_required +def get_component_file_from_rhdl_endpoint(user, c_id, filepath): + component = base.get_resource_orm(models2.Component, c_id) + permissions.verify_access_to_component(user, component) + + return get_component_file_from_rhdl(filepath, component)