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/organization.py

279 lines
8.7 KiB
Python

import logging
import stripe
from flask import request
from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error,
related_user_resource, internal_only, Unauthorized, NotFound,
require_user_admin)
from endpoints.api.team import team_view
from endpoints.api.user import User, PrivateRepositories
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/')
@internal_only
class OrganizationList(ApiResource):
""" Resource for creating organizations. """
schemas = {
'NewOrg': {
'id': 'NewOrg',
'type': 'object',
'description': 'Description of a new organization.',
'required': [
'name',
'email',
],
'properties': {
'name': {
'type': 'string',
'description': 'Organization username',
},
'email': {
'type': 'string',
'description': 'Organization contact email',
},
},
},
}
@require_user_admin
@nickname('createOrganization')
@validate_json_request('NewOrg')
def post(self):
""" Create a new organization. """
user = get_authenticated_user()
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'
raise request_error(message=msg)
try:
model.create_organization(org_data['name'], org_data['email'], user)
return 'Created', 201
except model.DataModelException as ex:
raise request_error(exception=ex)
@resource('/v1/organization/<orgname>')
@internal_only
@related_user_resource(User)
class Organization(ApiResource):
""" Resource for managing organizations. """
schemas = {
'UpdateOrg': {
'id': 'UpdateOrg',
'type': 'object',
'description': 'Description of updates for an existing organization',
'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:
raise NotFound()
teams = model.get_teams_within_org(org)
return org_view(org, teams)
raise Unauthorized()
@nickname('changeOrganizationDetails')
@validate_json_request('UpdateOrg')
def put(self, orgname):
""" Change the details for the specified organization. """
permission = AdministerOrganizationPermission(orgname)
if permission.can():
try:
org = model.get_organization(orgname)
except model.InvalidOrganizationException:
raise NotFound()
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):
raise 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)
raise Unauthorized()
@resource('/v1/organization/<orgname>/private')
@internal_only
@related_user_resource(PrivateRepositories)
class OrgPrivateRepositories(ApiResource):
""" Custom verb to compute whether additional private repositories are available. """
@nickname('getOrganizationPrivateAllowed')
def get(self, orgname):
""" Return whether or not this org is allowed to create new private repositories. """
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
raise Unauthorized()
@resource('/v1/organization/<orgname>/members')
@internal_only
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:
raise NotFound()
# 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}
raise Unauthorized()
@resource('/v1/organization/<orgname>/members/<membername>')
@internal_only
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:
raise NotFound()
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:
raise NotFound()
return {'member': member_dict}
raise Unauthorized()
@resource('/v1/app/<client_id>')
class ApplicationInformation(ApiResource):
""" Resource that returns public information about a registered application. """
@nickname('getApplicationInformation')
def get(self, client_id):
""" Get information on the specified application. """
application = model.oauth.get_application_for_client_id(client_id)
if not application:
raise NotFound()
return {
'name': application.name,
'description': application.description,
'uri': application.application_uri,
'organization': org_view(application.organization, [])
}