Migrate teams and orgs.

This commit is contained in:
jakedt 2014-03-14 14:20:51 -04:00
parent ab60a10a93
commit e4e4f8c553
5 changed files with 375 additions and 2 deletions

View file

@ -181,6 +181,8 @@ import endpoints.api.repository
import endpoints.api.repotoken import endpoints.api.repotoken
import endpoints.api.search import endpoints.api.search
import endpoints.api.tag import endpoints.api.tag
import endpoints.api.team
import endpoints.api.trigger import endpoints.api.trigger
import endpoints.api.organization
import endpoints.api.user import endpoints.api.user
import endpoints.api.webhook import endpoints.api.webhook

View file

@ -407,6 +407,7 @@ def team_view(orgname, team):
} }
# Ported
@api_bp.route('/organization/', methods=['POST']) @api_bp.route('/organization/', methods=['POST'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
@ -454,6 +455,7 @@ def org_view(o, teams):
return view return view
# Ported
@api_bp.route('/organization/<orgname>', methods=['GET']) @api_bp.route('/organization/<orgname>', methods=['GET'])
@api_login_required @api_login_required
def get_organization(orgname): def get_organization(orgname):
@ -470,6 +472,7 @@ def get_organization(orgname):
abort(403) abort(403)
# Ported
@api_bp.route('/organization/<orgname>', methods=['PUT']) @api_bp.route('/organization/<orgname>', methods=['PUT'])
@api_login_required @api_login_required
@org_api_call('change_user_details') @org_api_call('change_user_details')
@ -718,6 +721,7 @@ def get_organization_member(orgname, membername):
abort(403) abort(403)
# Ported
@api_bp.route('/organization/<orgname>/private', methods=['GET']) @api_bp.route('/organization/<orgname>/private', methods=['GET'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
@ -758,6 +762,7 @@ def member_view(member):
} }
# Ported
@api_bp.route('/organization/<orgname>/team/<teamname>', @api_bp.route('/organization/<orgname>/team/<teamname>',
methods=['PUT', 'POST']) methods=['PUT', 'POST'])
@api_login_required @api_login_required
@ -804,6 +809,7 @@ def update_organization_team(orgname, teamname):
abort(403) abort(403)
# Ported
@api_bp.route('/organization/<orgname>/team/<teamname>', @api_bp.route('/organization/<orgname>/team/<teamname>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
@ -817,6 +823,7 @@ def delete_organization_team(orgname, teamname):
abort(403) abort(403)
# Ported
@api_bp.route('/organization/<orgname>/team/<teamname>/members', @api_bp.route('/organization/<orgname>/team/<teamname>/members',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
@ -840,6 +847,7 @@ def get_organization_team_members(orgname, teamname):
abort(403) abort(403)
# Ported
@api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>', @api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
methods=['PUT', 'POST']) methods=['PUT', 'POST'])
@api_login_required @api_login_required
@ -869,6 +877,7 @@ def update_organization_team_member(orgname, teamname, membername):
abort(403) abort(403)
# Ported
@api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>', @api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required

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

View file

@ -31,7 +31,7 @@ class RepositoryTokenList(RepositoryParamResource):
'properties': { 'properties': {
'friendlyName': { 'friendlyName': {
'type': 'string', 'type': 'string',
'description': 'Friendly name to help identify the token.', 'description': 'Friendly name to help identify the token',
'required': True, 'required': True,
}, },
}, },
@ -72,7 +72,7 @@ class RepositoryToken(RepositoryParamResource):
'TokenPermission': { 'TokenPermission': {
'id': 'TokenPermission', 'id': 'TokenPermission',
'type': 'object', 'type': 'object',
'description': 'Description of a token permission.', 'description': 'Description of a token permission',
'required': True, 'required': True,
'properties': { 'properties': {
'role': { 'role': {

178
endpoints/api/team.py Normal file
View 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)