diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index 5fd2d7845..60d4b95d9 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -177,6 +177,7 @@ import endpoints.api.build import endpoints.api.discovery import endpoints.api.image import endpoints.api.permission +import endpoints.api.prototype import endpoints.api.repository import endpoints.api.repotoken import endpoints.api.search diff --git a/endpoints/api/legacy.py b/endpoints/api/legacy.py index e3f1b3bf1..bb34b5918 100644 --- a/endpoints/api/legacy.py +++ b/endpoints/api/legacy.py @@ -526,6 +526,7 @@ def prototype_view(proto, org_members): 'id': proto.uuid, } +# Ported @api_bp.route('/organization//prototypes', methods=['GET']) @api_login_required def get_organization_prototype_permissions(orgname): @@ -564,6 +565,7 @@ def log_prototype_action(action_kind, orgname, prototype, **kwargs): log_action(action_kind, orgname, log_params) +# Ported @api_bp.route('/organization//prototypes', methods=['POST']) @api_login_required def create_organization_prototype_permission(orgname): @@ -612,6 +614,7 @@ def create_organization_prototype_permission(orgname): abort(403) +# Ported @api_bp.route('/organization//prototypes/', methods=['DELETE']) @api_login_required @@ -634,6 +637,7 @@ def delete_organization_prototype_permission(orgname, prototypeid): abort(403) +# Ported @api_bp.route('/organization//prototypes/', methods=['PUT']) @api_login_required @@ -663,6 +667,7 @@ def update_organization_prototype_permission(orgname, prototypeid): abort(403) +# Ported @api_bp.route('/organization//members', methods=['GET']) @api_login_required def get_organization_members(orgname): @@ -692,6 +697,7 @@ def get_organization_members(orgname): abort(403) +# Ported @api_bp.route('/organization//members/', methods=['GET']) @api_login_required def get_organization_member(orgname, membername): diff --git a/endpoints/api/organization.py b/endpoints/api/organization.py index 235f36ebf..059c4711f 100644 --- a/endpoints/api/organization.py +++ b/endpoints/api/organization.py @@ -181,4 +181,68 @@ class OrgPrivateRepositories(ApiResource): return data + abort(403) + + +@resource('/v1/organization//members') +class OrgnaizationMemberList(ApiResource): + """ Resource for listing the members of an organization. """ + @nickname('getOrganizationMembers') + def get(self, orgname): + """ List the members of the specified organization. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + try: + org = model.get_organization(orgname) + except model.InvalidOrganizationException: + abort(404) + + # Loop to create the members dictionary. Note that the members collection + # will return an entry for *every team* a member is on, so we will have + # duplicate keys (which is why we pre-build the dictionary). + members_dict = {} + members = model.get_organization_members_with_teams(org) + for member in members: + if not member.user.username in members_dict: + members_dict[member.user.username] = {'name': member.user.username, + 'kind': 'user', + 'is_robot': member.user.robot, + 'teams': []} + + members_dict[member.user.username]['teams'].append(member.team.name) + + return {'members': members_dict} + + abort(403) + + +@resource('/v1/organization//members/') +class OrganizationMember(ApiResource): + """ Resource for managing individual organization members. """ + @nickname('getOrganizationMember') + def get(self, orgname, membername): + """ Get information on the specific orgnaization member. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + try: + org = model.get_organization(orgname) + except model.InvalidOrganizationException: + abort(404) + + member_dict = None + member_teams = model.get_organization_members_with_teams(org, membername=membername) + for member in member_teams: + if not member_dict: + member_dict = {'name': member.user.username, + 'kind': 'user', + 'is_robot': member.user.robot, + 'teams': []} + + member_dict['teams'].append(member.team.name) + + if not member_dict: + abort(404) + + return {'member': member_dict} + abort(403) \ No newline at end of file diff --git a/endpoints/api/prototype.py b/endpoints/api/prototype.py new file mode 100644 index 000000000..3987941b4 --- /dev/null +++ b/endpoints/api/prototype.py @@ -0,0 +1,248 @@ +from flask import request +from flask.ext.restful import abort + +from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error, + log_action) +from auth.permissions import AdministerOrganizationPermission +from auth.auth_context import get_authenticated_user +from data import model + + +def prototype_view(proto, org_members): + def prototype_user_view(user): + return { + 'name': user.username, + 'is_robot': user.robot, + 'kind': 'user', + 'is_org_member': user.robot or user.username in org_members, + } + + if proto.delegate_user: + delegate_view = prototype_user_view(proto.delegate_user) + else: + delegate_view = { + 'name': proto.delegate_team.name, + 'kind': 'team', + } + + return { + 'activating_user': (prototype_user_view(proto.activating_user) + if proto.activating_user else None), + 'delegate': delegate_view, + 'role': proto.role.name, + 'id': proto.uuid, + } + +def log_prototype_action(action_kind, orgname, prototype, **kwargs): + username = get_authenticated_user().username + log_params = { + 'prototypeid': prototype.uuid, + 'username': username, + 'activating_username': (prototype.activating_user.username + if prototype.activating_user else None), + 'role': prototype.role.name + } + + for key, value in kwargs.items(): + log_params[key] = value + + if prototype.delegate_user: + log_params['delegate_user'] = prototype.delegate_user.username + elif prototype.delegate_team: + log_params['delegate_team'] = prototype.delegate_team.name + + log_action(action_kind, orgname, log_params) + + +@resource('/v1/organization//prototypes') +class PermissionPrototypeList(ApiResource): + """ Resource for listing and creating permission prototypes. """ + schemas = { + 'NewPrototype': { + 'id': 'NewPrototype', + 'type': 'object', + 'description': 'Description of a new prototype', + 'required': True, + 'properties': { + 'role': { + 'type': 'string', + 'description': 'Role that should be applied to the delegate', + 'required': True, + 'enum': [ + 'read', + 'write', + 'admin', + ], + }, + 'activating_user': { + 'type': 'object', + 'description': 'Repository creating user to whom the rule should apply', + 'properties': { + 'name': { + 'type': 'string', + 'description': 'The username for the activating_user', + 'required': True, + }, + }, + }, + 'delegate': { + 'type': 'object', + 'description': 'Information about the user or team to which the rule grants access', + 'required': True, + 'properties': { + 'name': { + 'type': 'string', + 'description': 'The name for the delegate team or user', + 'required': True, + }, + 'kind': { + 'type': 'string', + 'description': 'Whether the delegate is a user or a team', + 'required': True, + 'enum': [ + 'user', + 'team', + ], + }, + }, + }, + }, + }, + } + + @nickname('getOrganizationPrototypePermissions') + def get(self, orgname): + """ List the existing prototypes for this organization. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + try: + org = model.get_organization(orgname) + except model.InvalidOrganizationException: + abort(404) + + permissions = model.get_prototype_permissions(org) + org_members = model.get_organization_member_set(orgname) + return {'prototypes': [prototype_view(p, org_members) for p in permissions]} + + abort(403) + + @nickname('createOrganizationPrototypePermission') + @validate_json_request('NewPrototype') + def post(self, orgname): + """ Create a new permission prototype. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + try: + org = model.get_organization(orgname) + except model.InvalidOrganizationException: + abort(404) + + details = request.get_json() + activating_username = None + + if ('activating_user' in details and details['activating_user'] and + 'name' in details['activating_user']): + activating_username = details['activating_user']['name'] + + delegate = details['delegate'] if 'delegate' in details else {} + delegate_kind = delegate.get('kind', None) + delegate_name = delegate.get('name', None) + + delegate_username = delegate_name if delegate_kind == 'user' else None + delegate_teamname = delegate_name if delegate_kind == 'team' else None + + activating_user = (model.get_user(activating_username) + if activating_username else None) + delegate_user = (model.get_user(delegate_username) + if delegate_username else None) + delegate_team = (model.get_organization_team(orgname, delegate_teamname) + if delegate_teamname else None) + + if activating_username and not activating_user: + return request_error(message='Unknown activating user') + + if not delegate_user and not delegate_team: + return request_error(message='Missing delegate user or team') + + role_name = details['role'] + + prototype = model.add_prototype_permission(org, role_name, activating_user, + delegate_user, delegate_team) + log_prototype_action('create_prototype_permission', orgname, prototype) + org_members = model.get_organization_member_set(orgname) + return prototype_view(prototype, org_members) + + abort(403) + + +@resource('/v1/organization//prototypes/') +class PermissionPrototype(ApiResource): + """ Resource for managingin individual permission prototypes. """ + schemas = { + 'PrototypeUpdate': { + 'id': 'PrototypeUpdate', + 'type': 'object', + 'description': 'Description of a the new prototype role', + 'required': True, + 'properties': { + 'role': { + 'type': 'string', + 'description': 'Role that should be applied to the permission', + 'required': True, + 'enum': [ + 'read', + 'write', + 'admin', + ], + }, + }, + }, + } + + @nickname('deleteOrganizationPrototypePermission') + def delete(self, orgname, prototypeid): + """ Delete an existing permission prototype. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + try: + org = model.get_organization(orgname) + except model.InvalidOrganizationException: + abort(404) + + prototype = model.delete_prototype_permission(org, prototypeid) + if not prototype: + abort(404) + + log_prototype_action('delete_prototype_permission', orgname, prototype) + + return 'Deleted', 204 + + abort(403) + + @nickname('updateOrganizationPrototypePermission') + @validate_json_request('PrototypeUpdate') + def put(self, orgname, prototypeid): + """ Update the role of an existing permission prototype. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + try: + org = model.get_organization(orgname) + except model.InvalidOrganizationException: + abort(404) + + existing = model.get_prototype_permission(org, prototypeid) + if not existing: + abort(404) + + details = request.get_json() + role_name = details['role'] + prototype = model.update_prototype_permission(org, prototypeid, role_name) + if not prototype: + abort(404) + + log_prototype_action('modify_prototype_permission', orgname, prototype, + original_role=existing.role.name) + org_members = model.get_organization_member_set(orgname) + return prototype_view(prototype, org_members) + + abort(403)