Add a user info scope and thread it through the code. Protect the org modification API.
This commit is contained in:
parent
89556172d5
commit
64071b9e8e
13 changed files with 144 additions and 115 deletions
|
@ -12,9 +12,9 @@ from jsonschema import validate, ValidationError
|
|||
|
||||
from data import model
|
||||
from util.names import parse_namespace_repository
|
||||
from auth.permissions import (ReadRepositoryPermission,
|
||||
ModifyRepositoryPermission,
|
||||
AdministerRepositoryPermission)
|
||||
from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission,
|
||||
AdministerRepositoryPermission, UserReadPermission,
|
||||
UserAdminPermission)
|
||||
from auth import scopes
|
||||
from auth.auth_context import get_authenticated_user, get_validated_oauth_token
|
||||
from auth.auth import process_oauth
|
||||
|
@ -183,6 +183,29 @@ require_repo_write = require_repo_permission(ModifyRepositoryPermission, scopes.
|
|||
require_repo_admin = require_repo_permission(AdministerRepositoryPermission, scopes.ADMIN_REPO)
|
||||
|
||||
|
||||
def require_user_permission(permission_class, scope=None):
|
||||
def wrapper(func):
|
||||
@add_method_metadata('oauth2_scope', scope)
|
||||
@wraps(func)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
logger.debug('User is anonymous.')
|
||||
raise InvalidToken('Method requires an auth token or user login.')
|
||||
|
||||
logger.debug('Checking permission %s for user', permission_class, user.username)
|
||||
permission = permission_class(user.username)
|
||||
if permission.can():
|
||||
return func(self, *args, **kwargs)
|
||||
raise Unauthorized()
|
||||
return wrapped
|
||||
return wrapper
|
||||
|
||||
|
||||
require_user_read = require_user_permission(UserReadPermission, scopes.USER_READ)
|
||||
require_user_admin = require_user_permission(UserAdminPermission, None)
|
||||
|
||||
|
||||
def require_scope(scope_object):
|
||||
def wrapper(func):
|
||||
@add_method_metadata('oauth2_scope', scope_object['scope'])
|
||||
|
|
|
@ -3,7 +3,8 @@ import stripe
|
|||
from flask import request
|
||||
|
||||
from endpoints.api import (resource, nickname, ApiResource, validate_json_request, log_action,
|
||||
related_user_resource, internal_only, Unauthorized, NotFound)
|
||||
related_user_resource, internal_only, Unauthorized, NotFound,
|
||||
require_user_admin)
|
||||
from endpoints.api.subscribe import subscribe, subscription_view
|
||||
from auth.permissions import AdministerOrganizationPermission
|
||||
from auth.auth_context import get_authenticated_user
|
||||
|
@ -109,23 +110,19 @@ class UserCard(ApiResource):
|
|||
},
|
||||
}
|
||||
|
||||
@require_user_admin
|
||||
@nickname('getUserCard')
|
||||
def get(self):
|
||||
""" Get the user's credit card. """
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
return get_card(user)
|
||||
|
||||
@require_user_admin
|
||||
@nickname('setUserCard')
|
||||
@validate_json_request('UserCard')
|
||||
def post(self):
|
||||
""" Update the user's credit card. """
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
token = request.get_json()['token']
|
||||
response = set_card(user, token)
|
||||
log_action('account_change_cc', user.username)
|
||||
|
@ -204,6 +201,7 @@ class UserPlan(ApiResource):
|
|||
},
|
||||
}
|
||||
|
||||
@require_user_admin
|
||||
@nickname('updateUserSubscription')
|
||||
@validate_json_request('UserSubscription')
|
||||
def put(self):
|
||||
|
@ -212,18 +210,13 @@ class UserPlan(ApiResource):
|
|||
plan = request_data['plan']
|
||||
token = request_data['token'] if 'token' in request_data else None
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
return subscribe(user, plan, token, False) # Business features not required
|
||||
|
||||
@require_user_admin
|
||||
@nickname('getUserSubscription')
|
||||
def get(self):
|
||||
""" Fetch any existing subscription for the user. """
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
private_repos = model.get_private_repo_count(user.username)
|
||||
|
||||
if user.stripe_id:
|
||||
|
@ -302,13 +295,11 @@ class OrganizationPlan(ApiResource):
|
|||
@resource('/v1/user/invoices')
|
||||
class UserInvoiceList(ApiResource):
|
||||
""" Resource for listing a user's invoices. """
|
||||
@require_user_admin
|
||||
@nickname('listUserInvoices')
|
||||
def get(self):
|
||||
""" List the invoices for the current user. """
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
if not user.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from datetime import datetime, timedelta
|
|||
|
||||
from endpoints.api import (resource, nickname, ApiResource, query_param, parse_args,
|
||||
RepositoryParamResource, require_repo_admin, related_user_resource,
|
||||
format_date, Unauthorized, NotFound)
|
||||
format_date, Unauthorized, NotFound, require_user_admin)
|
||||
from auth.permissions import AdministerOrganizationPermission, AdministerOrganizationPermission
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from data import model
|
||||
|
@ -84,6 +84,7 @@ class RepositoryLogs(RepositoryParamResource):
|
|||
@resource('/v1/user/logs')
|
||||
class UserLogs(ApiResource):
|
||||
""" Resource for fetching logs for the current user. """
|
||||
@require_user_admin
|
||||
@nickname('listUserLogs')
|
||||
@parse_args
|
||||
@query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str)
|
||||
|
@ -96,9 +97,6 @@ class UserLogs(ApiResource):
|
|||
end_time = args['endtime']
|
||||
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
return get_logs(user.username, start_time, end_time, performer_name=performer_name)
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ 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)
|
||||
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,
|
||||
|
@ -62,14 +63,12 @@ class OrganizationList(ApiResource):
|
|||
},
|
||||
}
|
||||
|
||||
@require_user_admin
|
||||
@nickname('createOrganization')
|
||||
@validate_json_request('NewOrg')
|
||||
def post(self):
|
||||
""" Create a new organization. """
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
org_data = request.get_json()
|
||||
existing = None
|
||||
|
||||
|
@ -135,26 +134,29 @@ class Organization(ApiResource):
|
|||
@validate_json_request('UpdateOrg')
|
||||
def put(self, orgname):
|
||||
""" Change the details for the specified organization. """
|
||||
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'])
|
||||
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)
|
||||
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')
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import logging
|
||||
import json
|
||||
|
||||
from flask import current_app, request
|
||||
from flask import request
|
||||
|
||||
from data import model
|
||||
from endpoints.api import (truthy_bool, format_date, nickname, log_action, validate_json_request,
|
||||
require_repo_read, require_repo_write, require_repo_admin,
|
||||
RepositoryParamResource, resource, query_param, parse_args, ApiResource,
|
||||
request_error, require_scope, Unauthorized, NotFound)
|
||||
from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission,
|
||||
AdministerRepositoryPermission, CreateRepositoryPermission)
|
||||
from auth.auth import process_auth
|
||||
request_error, require_scope, Unauthorized, NotFound, InvalidRequest)
|
||||
from auth.permissions import (ModifyRepositoryPermission, AdministerRepositoryPermission,
|
||||
CreateRepositoryPermission)
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from auth import scopes
|
||||
|
||||
|
@ -63,10 +62,11 @@ class RepositoryList(ApiResource):
|
|||
def post(self):
|
||||
"""Create a new repository."""
|
||||
owner = get_authenticated_user()
|
||||
if not owner:
|
||||
raise Unauthorized()
|
||||
|
||||
req = request.get_json()
|
||||
|
||||
if owner is None and 'namespace' not in 'req':
|
||||
raise InvalidRequest('Must provide a namespace or must be logged in.')
|
||||
|
||||
namespace_name = req['namespace'] if 'namespace' in req else owner.username
|
||||
|
||||
permission = CreateRepositoryPermission(namespace_name)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from endpoints.api import (resource, nickname, ApiResource, log_action, related_user_resource,
|
||||
Unauthorized)
|
||||
Unauthorized, require_user_admin)
|
||||
from auth.permissions import AdministerOrganizationPermission, OrganizationMemberPermission
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from data import model
|
||||
|
@ -16,13 +16,11 @@ def robot_view(name, token):
|
|||
@resource('/v1/user/robots')
|
||||
class UserRobotList(ApiResource):
|
||||
""" Resource for listing user robots. """
|
||||
@require_user_admin
|
||||
@nickname('getUserRobots')
|
||||
def get(self):
|
||||
""" List the available robots for the user. """
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
robots = model.list_entity_robots(user.username)
|
||||
return {
|
||||
'robots': [robot_view(name, password) for name, password in robots]
|
||||
|
@ -32,24 +30,20 @@ class UserRobotList(ApiResource):
|
|||
@resource('/v1/user/robots/<robot_shortname>')
|
||||
class UserRobot(ApiResource):
|
||||
""" Resource for managing a user's robots. """
|
||||
@require_user_admin
|
||||
@nickname('createUserRobot')
|
||||
def put(self, robot_shortname):
|
||||
""" Create a new user robot with the specified name. """
|
||||
parent = get_authenticated_user()
|
||||
if not parent:
|
||||
raise Unauthorized()
|
||||
|
||||
robot, password = model.create_robot(robot_shortname, parent)
|
||||
log_action('create_robot', parent.username, {'robot': robot_shortname})
|
||||
return robot_view(robot.username, password), 201
|
||||
|
||||
@require_user_admin
|
||||
@nickname('deleteUserRobot')
|
||||
def delete(self, robot_shortname):
|
||||
""" Delete an existing robot. """
|
||||
parent = get_authenticated_user()
|
||||
if not parent:
|
||||
raise Unauthorized()
|
||||
|
||||
model.delete_robot(format_robot_username(parent.username, robot_shortname))
|
||||
log_action('delete_robot', parent.username, {'robot': robot_shortname})
|
||||
return 'Deleted', 204
|
||||
|
|
|
@ -14,7 +14,7 @@ from endpoints.common import start_build
|
|||
from endpoints.trigger import (BuildTrigger, TriggerDeactivationException,
|
||||
TriggerActivationException, EmptyRepositoryException)
|
||||
from data import model
|
||||
from auth.permissions import UserPermission
|
||||
from auth.permissions import UserAdminPermission
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -103,7 +103,7 @@ class BuildTriggerSubdirs(RepositoryParamResource):
|
|||
raise NotFound()
|
||||
|
||||
handler = BuildTrigger.get_trigger_for_service(trigger.service.name)
|
||||
user_permission = UserPermission(trigger.connected_user.username)
|
||||
user_permission = UserAdminPermission(trigger.connected_user.username)
|
||||
if user_permission.can():
|
||||
new_config_dict = request.get_json()
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ from flask.ext.principal import identity_changed, AnonymousIdentity
|
|||
|
||||
from app import app
|
||||
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
|
||||
log_action, internal_only, NotFound, Unauthorized)
|
||||
log_action, internal_only, NotFound, Unauthorized, require_user_admin,
|
||||
require_user_read, InvalidToken)
|
||||
from endpoints.api.subscribe import subscribe
|
||||
from endpoints.common import common_login
|
||||
from data import model
|
||||
|
@ -107,24 +108,23 @@ class User(ApiResource):
|
|||
},
|
||||
}
|
||||
|
||||
@require_user_read
|
||||
@nickname('getLoggedInUser')
|
||||
def get(self):
|
||||
""" Get user information for the authenticated user. """
|
||||
user = get_authenticated_user()
|
||||
if user is None or user.organization:
|
||||
return {'anonymous': True}
|
||||
if user.organization:
|
||||
raise InvalidToken('User must not be an organization.')
|
||||
|
||||
return user_view(user)
|
||||
|
||||
@require_user_admin
|
||||
@nickname('changeUserDetails')
|
||||
@internal_only
|
||||
@validate_json_request('UpdateUser')
|
||||
def put(self):
|
||||
""" Update a users details such as password or email. """
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
user_data = request.get_json()
|
||||
|
||||
try:
|
||||
|
@ -173,18 +173,15 @@ class User(ApiResource):
|
|||
except model.DataModelException as ex:
|
||||
raise request_error(exception=ex)
|
||||
|
||||
|
||||
@resource('/v1/user/private')
|
||||
class PrivateRepositories(ApiResource):
|
||||
""" Operations dealing with the available count of private repositories. """
|
||||
@require_user_admin
|
||||
@nickname('getUserPrivateAllowed')
|
||||
def get(self):
|
||||
""" Get the number of private repos this user has, and whether they are allowed to create more.
|
||||
"""
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
private_repos = model.get_private_repo_count(user.username)
|
||||
repos_allowed = 0
|
||||
|
||||
|
@ -252,14 +249,12 @@ class ConvertToOrganization(ApiResource):
|
|||
},
|
||||
}
|
||||
|
||||
@require_user_admin
|
||||
@nickname('convertUserToOrganization')
|
||||
@validate_json_request('ConvertUser')
|
||||
def post(self):
|
||||
""" Convert the user to an organization. """
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
convert_data = request.get_json()
|
||||
|
||||
# Ensure that the new admin user is the not user being converted.
|
||||
|
|
Reference in a new issue