This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/endpoints/api/team.py

372 lines
12 KiB
Python
Raw Normal View History

2014-03-14 18:20:51 +00:00
from flask import request
from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error,
log_action, Unauthorized, NotFound, internal_only, require_scope,
query_param, truthy_bool, parse_args, require_user_admin, show_if)
2014-03-14 18:20:51 +00:00
from auth.permissions import AdministerOrganizationPermission, ViewTeamPermission
from auth.auth_context import get_authenticated_user
from auth import scopes
2014-03-14 18:20:51 +00:00
from data import model
from util.useremails import send_org_invite_email
2014-08-16 00:51:31 +00:00
from util.gravatar import compute_hash
import features
2014-09-11 19:45:41 +00:00
def try_accept_invite(code, user):
(team, inviter) = model.confirm_team_invite(code, user)
2014-09-11 19:45:41 +00:00
model.delete_matching_notifications(user, 'org_team_invite', code=code)
orgname = team.organization.username
log_action('org_team_member_invite_accepted', orgname, {
'member': user.username,
'team': team.name,
'inviter': inviter.username
})
return team
def handle_addinvite_team(inviter, team, user=None, email=None):
invite = model.add_or_invite_to_team(inviter, team, user, email,
requires_invite = features.MAILING)
if not invite:
# User was added to the team directly.
return
orgname = team.organization.username
if user:
model.create_notification('org_team_invite', user, metadata = {
'code': invite.invite_token,
'inviter': inviter.username,
'org': orgname,
'team': team.name
})
send_org_invite_email(user.username if user else email, user.email if user else email,
orgname, team.name, inviter.username, invite.invite_token)
return invite
2014-03-14 18:20:51 +00:00
def team_view(orgname, team):
view_permission = ViewTeamPermission(orgname, team.name)
role = model.get_team_org_role(team).name
return {
'id': team.id,
'name': team.name,
'description': team.description,
'can_view': view_permission.can(),
'role': role
}
def member_view(member, invited=False):
2014-03-14 18:20:51 +00:00
return {
'name': member.username,
'kind': 'user',
'is_robot': member.robot,
2014-08-16 00:51:31 +00:00
'gravatar': compute_hash(member.email) if not member.robot else None,
'invited': invited,
2014-03-14 18:20:51 +00:00
}
def invite_view(invite):
if invite.user:
return member_view(invite.user, invited=True)
else:
return {
'email': invite.email,
'kind': 'invite',
2014-08-16 00:51:31 +00:00
'gravatar': compute_hash(invite.email),
'invited': True
}
@resource('/v1/organization/<orgname>/team/<teamname>')
@internal_only
2014-03-14 18:20:51 +00:00
class OrganizationTeam(ApiResource):
""" Resource for manging an organization's teams. """
schemas = {
'TeamDescription': {
'id': 'TeamDescription',
'type': 'object',
'description': 'Description of a team',
'required': [
'role',
],
2014-03-14 18:20:51 +00:00
'properties': {
'role': {
'type': 'string',
'description': 'Org wide permissions that should apply to the team',
'enum': [
'member',
'creator',
'admin',
],
},
'description': {
'type': 'string',
'description': 'Markdown description for the team',
},
},
},
}
@nickname('updateOrganizationTeam')
@validate_json_request('TeamDescription')
def put(self, orgname, teamname):
""" Update the org-wide permission for the specified team. """
edit_permission = AdministerOrganizationPermission(orgname)
if edit_permission.can():
team = None
details = request.get_json()
is_existing = False
try:
team = model.get_organization_team(orgname, teamname)
is_existing = True
except model.InvalidTeamException:
# Create the new team.
description = details['description'] if 'description' in details else ''
role = details['role'] if 'role' in details else 'member'
org = model.get_organization(orgname)
team = model.create_team(teamname, org, role, description)
log_action('org_create_team', orgname, {'team': teamname})
if is_existing:
if ('description' in details and
team.description != details['description']):
team.description = details['description']
team.save()
log_action('org_set_team_description', orgname,
{'team': teamname, 'description': team.description})
if 'role' in details:
role = model.get_team_org_role(team).name
if role != details['role']:
team = model.set_team_org_permission(team, details['role'],
get_authenticated_user().username)
log_action('org_set_team_role', orgname, {'team': teamname, 'role': details['role']})
return team_view(orgname, team), 200
2014-03-14 18:20:51 +00:00
raise Unauthorized()
2014-03-14 18:20:51 +00:00
@nickname('deleteOrganizationTeam')
def delete(self, orgname, teamname):
""" Delete the specified team. """
permission = AdministerOrganizationPermission(orgname)
if permission.can():
model.remove_team(orgname, teamname, get_authenticated_user().username)
log_action('org_delete_team', orgname, {'team': teamname})
return 'Deleted', 204
raise Unauthorized()
2014-03-14 18:20:51 +00:00
@resource('/v1/organization/<orgname>/team/<teamname>/members')
@internal_only
2014-03-14 18:20:51 +00:00
class TeamMemberList(ApiResource):
""" Resource for managing the list of members for a team. """
@parse_args
@query_param('includePending', 'Whether to include pending members', type=truthy_bool, default=False)
2014-03-14 18:20:51 +00:00
@nickname('getOrganizationTeamMembers')
def get(self, args, orgname, teamname):
2014-03-14 18:20:51 +00:00
""" Retrieve the list of members for the specified team. """
view_permission = ViewTeamPermission(orgname, teamname)
edit_permission = AdministerOrganizationPermission(orgname)
if view_permission.can():
team = None
try:
team = model.get_organization_team(orgname, teamname)
except model.InvalidTeamException:
raise NotFound()
2014-03-14 18:20:51 +00:00
members = model.get_organization_team_members(team.id)
2014-08-16 00:51:31 +00:00
invites = []
2014-03-14 18:20:51 +00:00
if args['includePending'] and edit_permission.can():
invites = model.get_organization_team_member_invites(team.id)
2014-08-16 00:51:31 +00:00
data = {
'members': [member_view(m) for m in members] + [invite_view(i) for i in invites],
'can_edit': edit_permission.can()
}
return data
raise Unauthorized()
2014-03-14 18:20:51 +00:00
@resource('/v1/organization/<orgname>/team/<teamname>/members/<membername>')
class TeamMember(ApiResource):
""" Resource for managing individual members of a team. """
@require_scope(scopes.ORG_ADMIN)
2014-03-14 18:20:51 +00:00
@nickname('updateOrganizationTeamMember')
def put(self, orgname, teamname, membername):
""" Adds or invites a member to an existing team. """
2014-03-14 18:20:51 +00:00
permission = AdministerOrganizationPermission(orgname)
if permission.can():
team = None
user = None
# Find the team.
try:
team = model.get_organization_team(orgname, teamname)
except model.InvalidTeamException:
raise NotFound()
2014-03-14 18:20:51 +00:00
# Find the user.
user = model.get_user(membername)
if not user:
raise request_error(message='Unknown user')
2014-03-14 18:20:51 +00:00
# Add or invite the user to the team.
inviter = get_authenticated_user()
invite = handle_addinvite_team(inviter, team, user=user)
if not invite:
log_action('org_add_team_member', orgname, {'member': membername, 'team': teamname})
return member_view(user, invited=False)
# User was invited.
2014-09-08 21:20:01 +00:00
log_action('org_invite_team_member', orgname, {
'user': membername,
'member': membername,
'team': teamname
})
return member_view(user, invited=True)
2014-03-14 18:20:51 +00:00
raise Unauthorized()
2014-03-14 18:20:51 +00:00
@require_scope(scopes.ORG_ADMIN)
2014-03-14 18:20:51 +00:00
@nickname('deleteOrganizationTeamMember')
def delete(self, orgname, teamname, membername):
2014-09-08 21:20:01 +00:00
""" Delete a member of a team. If the user is merely invited to join
the team, then the invite is removed instead.
"""
2014-03-14 18:20:51 +00:00
permission = AdministerOrganizationPermission(orgname)
if permission.can():
# Remote the user from the team.
invoking_user = get_authenticated_user().username
2014-09-08 21:20:01 +00:00
# Find the team.
try:
team = model.get_organization_team(orgname, teamname)
except model.InvalidTeamException:
raise NotFound()
# Find the member.
member = model.get_user(membername)
if not member:
raise NotFound()
# First attempt to delete an invite for the user to this team. If none found,
# then we try to remove the user directly.
if model.delete_team_user_invite(team, member):
log_action('org_delete_team_member_invite', orgname, {
'user': membername,
'team': teamname,
'member': membername
})
return 'Deleted', 204
2014-03-14 18:20:51 +00:00
model.remove_user_from_team(orgname, teamname, membername, invoking_user)
log_action('org_remove_team_member', orgname, {'member': membername, 'team': teamname})
return 'Deleted', 204
raise Unauthorized()
2014-08-29 00:49:11 +00:00
@resource('/v1/organization/<orgname>/team/<teamname>/invite/<email>')
@show_if(features.MAILING)
2014-08-29 00:49:11 +00:00
class InviteTeamMember(ApiResource):
""" Resource for inviting a team member via email address. """
@require_scope(scopes.ORG_ADMIN)
@nickname('inviteTeamMemberEmail')
def put(self, orgname, teamname, email):
""" Invites an email address to an existing team. """
permission = AdministerOrganizationPermission(orgname)
if permission.can():
team = None
# Find the team.
try:
team = model.get_organization_team(orgname, teamname)
except model.InvalidTeamException:
raise NotFound()
# Invite the email to the team.
inviter = get_authenticated_user()
invite = handle_addinvite_team(inviter, team, email=email)
2014-09-08 21:20:01 +00:00
log_action('org_invite_team_member', orgname, {
'email': email,
'team': teamname,
'member': email
})
2014-08-29 00:49:11 +00:00
return invite_view(invite)
raise Unauthorized()
2014-09-08 21:20:01 +00:00
@require_scope(scopes.ORG_ADMIN)
@nickname('deleteTeamMemberEmailInvite')
def delete(self, orgname, teamname, email):
""" Delete an invite of an email address to join a team. """
permission = AdministerOrganizationPermission(orgname)
if permission.can():
team = None
# Find the team.
try:
team = model.get_organization_team(orgname, teamname)
except model.InvalidTeamException:
raise NotFound()
# Delete the invite.
model.delete_team_email_invite(team, email)
log_action('org_delete_team_member_invite', orgname, {
'email': email,
'team': teamname,
'member': email
})
return 'Deleted', 204
raise Unauthorized()
2014-08-29 00:49:11 +00:00
@resource('/v1/teaminvite/<code>')
@internal_only
@show_if(features.MAILING)
class TeamMemberInvite(ApiResource):
""" Resource for managing invites to jon a team. """
@require_user_admin
@nickname('acceptOrganizationTeamInvite')
def put(self, code):
""" Accepts an invite to join a team in an organization. """
# Accept the invite for the current user.
2014-09-11 19:45:41 +00:00
team = try_accept_invite(code, get_authenticated_user())
if not team:
2014-09-08 21:20:01 +00:00
raise NotFound()
orgname = team.organization.username
return {
'org': orgname,
'team': team.name
}
@nickname('declineOrganizationTeamInvite')
@require_user_admin
def delete(self, code):
""" Delete an existing member of a team. """
(team, inviter) = model.delete_team_invite(code, get_authenticated_user())
model.delete_matching_notifications(get_authenticated_user(), 'org_team_invite', code=code)
orgname = team.organization.username
log_action('org_team_member_invite_declined', orgname, {
'member': get_authenticated_user().username,
'team': team.name,
'inviter': inviter.username
})
return 'Deleted', 204