Migrate teams and orgs.
This commit is contained in:
parent
ab60a10a93
commit
e4e4f8c553
5 changed files with 375 additions and 2 deletions
|
@ -181,6 +181,8 @@ import endpoints.api.repository
|
|||
import endpoints.api.repotoken
|
||||
import endpoints.api.search
|
||||
import endpoints.api.tag
|
||||
import endpoints.api.team
|
||||
import endpoints.api.trigger
|
||||
import endpoints.api.organization
|
||||
import endpoints.api.user
|
||||
import endpoints.api.webhook
|
||||
|
|
|
@ -407,6 +407,7 @@ def team_view(orgname, team):
|
|||
}
|
||||
|
||||
|
||||
# Ported
|
||||
@api_bp.route('/organization/', methods=['POST'])
|
||||
@api_login_required
|
||||
@internal_api_call
|
||||
|
@ -454,6 +455,7 @@ def org_view(o, teams):
|
|||
return view
|
||||
|
||||
|
||||
# Ported
|
||||
@api_bp.route('/organization/<orgname>', methods=['GET'])
|
||||
@api_login_required
|
||||
def get_organization(orgname):
|
||||
|
@ -470,6 +472,7 @@ def get_organization(orgname):
|
|||
abort(403)
|
||||
|
||||
|
||||
# Ported
|
||||
@api_bp.route('/organization/<orgname>', methods=['PUT'])
|
||||
@api_login_required
|
||||
@org_api_call('change_user_details')
|
||||
|
@ -718,6 +721,7 @@ def get_organization_member(orgname, membername):
|
|||
abort(403)
|
||||
|
||||
|
||||
# Ported
|
||||
@api_bp.route('/organization/<orgname>/private', methods=['GET'])
|
||||
@api_login_required
|
||||
@internal_api_call
|
||||
|
@ -758,6 +762,7 @@ def member_view(member):
|
|||
}
|
||||
|
||||
|
||||
# Ported
|
||||
@api_bp.route('/organization/<orgname>/team/<teamname>',
|
||||
methods=['PUT', 'POST'])
|
||||
@api_login_required
|
||||
|
@ -804,6 +809,7 @@ def update_organization_team(orgname, teamname):
|
|||
abort(403)
|
||||
|
||||
|
||||
# Ported
|
||||
@api_bp.route('/organization/<orgname>/team/<teamname>',
|
||||
methods=['DELETE'])
|
||||
@api_login_required
|
||||
|
@ -817,6 +823,7 @@ def delete_organization_team(orgname, teamname):
|
|||
abort(403)
|
||||
|
||||
|
||||
# Ported
|
||||
@api_bp.route('/organization/<orgname>/team/<teamname>/members',
|
||||
methods=['GET'])
|
||||
@api_login_required
|
||||
|
@ -840,6 +847,7 @@ def get_organization_team_members(orgname, teamname):
|
|||
abort(403)
|
||||
|
||||
|
||||
# Ported
|
||||
@api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
|
||||
methods=['PUT', 'POST'])
|
||||
@api_login_required
|
||||
|
@ -869,6 +877,7 @@ def update_organization_team_member(orgname, teamname, membername):
|
|||
abort(403)
|
||||
|
||||
|
||||
# Ported
|
||||
@api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
|
||||
methods=['DELETE'])
|
||||
@api_login_required
|
||||
|
|
184
endpoints/api/organization.py
Normal file
184
endpoints/api/organization.py
Normal file
|
@ -0,0 +1,184 @@
|
|||
import logging
|
||||
import stripe
|
||||
|
||||
from flask import request
|
||||
from flask.ext.restful import abort
|
||||
|
||||
from endpoints.api import resource, nickname, ApiResource, validate_json_request, request_error
|
||||
from endpoints.api.team import team_view
|
||||
from auth.permissions import (AdministerOrganizationPermission, OrganizationMemberPermission,
|
||||
CreateRepositoryPermission)
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from data import model
|
||||
from data.plans import get_plan
|
||||
from util.gravatar import compute_hash
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
def org_view(o, teams):
|
||||
admin_org = AdministerOrganizationPermission(o.username)
|
||||
is_admin = admin_org.can()
|
||||
view = {
|
||||
'name': o.username,
|
||||
'email': o.email if is_admin else '',
|
||||
'gravatar': compute_hash(o.email),
|
||||
'teams': {t.name : team_view(o.username, t) for t in teams},
|
||||
'is_admin': is_admin
|
||||
}
|
||||
|
||||
if is_admin:
|
||||
view['invoice_email'] = o.invoice_email
|
||||
|
||||
return view
|
||||
|
||||
|
||||
@resource('/v1/organization/', methods=['POST'])
|
||||
class OrganizationList(ApiResource):
|
||||
""" Resource for creating organizations. """
|
||||
schemas = {
|
||||
'NewOrg': {
|
||||
'id': 'NewOrg',
|
||||
'type': 'object',
|
||||
'description': 'Description of a new organization.',
|
||||
'required': True,
|
||||
'properties': {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'description': 'Organization username',
|
||||
'required': True,
|
||||
},
|
||||
'email': {
|
||||
'type': 'string',
|
||||
'description': 'Organization contact email',
|
||||
'required': True,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@nickname('createOrganization')
|
||||
@validate_json_request('NewOrg')
|
||||
def post(self):
|
||||
""" Create a new organization. """
|
||||
org_data = request.get_json()
|
||||
existing = None
|
||||
|
||||
try:
|
||||
existing = model.get_organization(org_data['name'])
|
||||
except model.InvalidOrganizationException:
|
||||
pass
|
||||
|
||||
if not existing:
|
||||
try:
|
||||
existing = model.get_user(org_data['name'])
|
||||
except model.InvalidUserException:
|
||||
pass
|
||||
|
||||
if existing:
|
||||
msg = 'A user or organization with this name already exists'
|
||||
return request_error(message=msg)
|
||||
|
||||
try:
|
||||
model.create_organization(org_data['name'], org_data['email'], get_authenticated_user())
|
||||
return 'Created', 201
|
||||
except model.DataModelException as ex:
|
||||
return request_error(exception=ex)
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>', methods=['GET'])
|
||||
class Organization(ApiResource):
|
||||
""" Resource for managing organizations. """
|
||||
schemas = {
|
||||
'UpdateOrg': {
|
||||
'id': 'UpdateOrg',
|
||||
'type': 'object',
|
||||
'description': 'Description of updates for an existing organization',
|
||||
'required': True,
|
||||
'properties': {
|
||||
'email': {
|
||||
'type': 'string',
|
||||
'description': 'Organization contact email',
|
||||
},
|
||||
'invoice_email': {
|
||||
'type': 'boolean',
|
||||
'description': 'Whether the organization desires to receive emails for invoices',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@nickname('getOrganization')
|
||||
def get(self, orgname):
|
||||
""" Get the details for the specified organization """
|
||||
permission = OrganizationMemberPermission(orgname)
|
||||
if permission.can():
|
||||
try:
|
||||
org = model.get_organization(orgname)
|
||||
except model.InvalidOrganizationException:
|
||||
abort(404)
|
||||
|
||||
teams = model.get_teams_within_org(org)
|
||||
return org_view(org, teams)
|
||||
|
||||
abort(403)
|
||||
|
||||
# @org_api_call('change_user_details')
|
||||
@nickname('changeOrganizationDetails')
|
||||
@validate_json_request('UpdateOrg')
|
||||
def put(self, orgname):
|
||||
""" Change the details for the specified organization. """
|
||||
try:
|
||||
org = model.get_organization(orgname)
|
||||
except model.InvalidOrganizationException:
|
||||
abort(404)
|
||||
|
||||
org_data = request.get_json()
|
||||
if 'invoice_email' in org_data:
|
||||
logger.debug('Changing invoice_email for organization: %s', org.username)
|
||||
model.change_invoice_email(org, org_data['invoice_email'])
|
||||
|
||||
if 'email' in org_data and org_data['email'] != org.email:
|
||||
new_email = org_data['email']
|
||||
if model.find_user_by_email(new_email):
|
||||
return request_error(message='E-mail address already used')
|
||||
|
||||
logger.debug('Changing email address for organization: %s', org.username)
|
||||
model.update_email(org, new_email)
|
||||
|
||||
teams = model.get_teams_within_org(org)
|
||||
return org_view(org, teams)
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/private')
|
||||
class OrgPrivateRepositories(ApiResource):
|
||||
""" Custom verb to compute whether additional private repositories are available. """
|
||||
# @org_api_call('get_user_private_allowed')
|
||||
@nickname('getOrganizationPrivateAllowed')
|
||||
def get(self, orgname):
|
||||
permission = CreateRepositoryPermission(orgname)
|
||||
if permission.can():
|
||||
organization = model.get_organization(orgname)
|
||||
private_repos = model.get_private_repo_count(organization.username)
|
||||
data = {
|
||||
'privateAllowed': False
|
||||
}
|
||||
|
||||
if organization.stripe_id:
|
||||
cus = stripe.Customer.retrieve(organization.stripe_id)
|
||||
if cus.subscription:
|
||||
repos_allowed = 0
|
||||
plan = get_plan(cus.subscription.plan.id)
|
||||
if plan:
|
||||
repos_allowed = plan['privateRepos']
|
||||
|
||||
data['privateAllowed'] = (private_repos < repos_allowed)
|
||||
|
||||
|
||||
if AdministerOrganizationPermission(orgname).can():
|
||||
data['privateCount'] = private_repos
|
||||
|
||||
return data
|
||||
|
||||
abort(403)
|
|
@ -31,7 +31,7 @@ class RepositoryTokenList(RepositoryParamResource):
|
|||
'properties': {
|
||||
'friendlyName': {
|
||||
'type': 'string',
|
||||
'description': 'Friendly name to help identify the token.',
|
||||
'description': 'Friendly name to help identify the token',
|
||||
'required': True,
|
||||
},
|
||||
},
|
||||
|
@ -72,7 +72,7 @@ class RepositoryToken(RepositoryParamResource):
|
|||
'TokenPermission': {
|
||||
'id': 'TokenPermission',
|
||||
'type': 'object',
|
||||
'description': 'Description of a token permission.',
|
||||
'description': 'Description of a token permission',
|
||||
'required': True,
|
||||
'properties': {
|
||||
'role': {
|
||||
|
|
178
endpoints/api/team.py
Normal file
178
endpoints/api/team.py
Normal file
|
@ -0,0 +1,178 @@
|
|||
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, ViewTeamPermission
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from data import model
|
||||
|
||||
|
||||
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):
|
||||
return {
|
||||
'name': member.username,
|
||||
'kind': 'user',
|
||||
'is_robot': member.robot,
|
||||
}
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/team/<teamname>',
|
||||
methods=['PUT', 'POST'])
|
||||
class OrganizationTeam(ApiResource):
|
||||
""" Resource for manging an organization's teams. """
|
||||
schemas = {
|
||||
'TeamDescription': {
|
||||
'id': 'TeamDescription',
|
||||
'type': 'object',
|
||||
'description': 'Description of a team',
|
||||
'required': True,
|
||||
'properties': {
|
||||
'role': {
|
||||
'type': 'string',
|
||||
'description': 'Org wide permissions that should apply to the team',
|
||||
'required': True,
|
||||
'enum': [
|
||||
'member',
|
||||
'creator',
|
||||
'admin',
|
||||
],
|
||||
},
|
||||
'description': {
|
||||
'type': 'string',
|
||||
'description': 'Markdown description for the team',
|
||||
'required': True,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@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 # 201 for post
|
||||
|
||||
abort(403)
|
||||
|
||||
@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
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/team/<teamname>/members')
|
||||
class TeamMemberList(ApiResource):
|
||||
""" Resource for managing the list of members for a team. """
|
||||
@nickname('getOrganizationTeamMembers')
|
||||
def get(self, orgname, teamname):
|
||||
""" 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:
|
||||
abort(404)
|
||||
|
||||
members = model.get_organization_team_members(team.id)
|
||||
return {
|
||||
'members': {m.username : member_view(m) for m in members},
|
||||
'can_edit': edit_permission.can()
|
||||
}
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/team/<teamname>/members/<membername>')
|
||||
class TeamMember(ApiResource):
|
||||
""" Resource for managing individual members of a team. """
|
||||
@nickname('updateOrganizationTeamMember')
|
||||
def put(self, orgname, teamname, membername):
|
||||
""" Add a member to an existing team. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
team = None
|
||||
user = None
|
||||
|
||||
# Find the team.
|
||||
try:
|
||||
team = model.get_organization_team(orgname, teamname)
|
||||
except model.InvalidTeamException:
|
||||
abort(404)
|
||||
|
||||
# Find the user.
|
||||
user = model.get_user(membername)
|
||||
if not user:
|
||||
return request_error(message='Unknown user')
|
||||
|
||||
# Add the user to the team.
|
||||
model.add_user_to_team(user, team)
|
||||
log_action('org_add_team_member', orgname, {'member': membername, 'team': teamname})
|
||||
return member_view(user)
|
||||
|
||||
abort(403)
|
||||
|
||||
@nickname('deleteOrganizationTeamMember')
|
||||
def delete(self, orgname, teamname, membername):
|
||||
""" Delete an existing member of a team. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
# Remote the user from the team.
|
||||
invoking_user = get_authenticated_user().username
|
||||
model.remove_user_from_team(orgname, teamname, membername, invoking_user)
|
||||
log_action('org_remove_team_member', orgname, {'member': membername, 'team': teamname})
|
||||
return 'Deleted', 204
|
||||
|
||||
abort(403)
|
Reference in a new issue