diff --git a/auth/permissions.py b/auth/permissions.py index e53dd6fe8..435a29523 100644 --- a/auth/permissions.py +++ b/auth/permissions.py @@ -27,7 +27,7 @@ _SuperUserNeed = partial(namedtuple('superuserneed', ['type']), 'superuser') REPO_ROLES = [None, 'read', 'write', 'admin'] TEAM_ROLES = [None, 'member', 'creator', 'admin'] -USER_ROLES = [None, 'read', 'admin'] +USER_ROLES = [None, 'read', 'admin', 'superuser'] TEAM_REPO_ROLES = { 'admin': 'admin', @@ -54,6 +54,7 @@ SCOPE_MAX_USER_ROLES = defaultdict(lambda: None) SCOPE_MAX_USER_ROLES.update({ scopes.READ_USER: 'read', scopes.DIRECT_LOGIN: 'admin', + scopes.SUPERUSER: 'superuser', }) @@ -113,8 +114,14 @@ class QuayDeferredPermissionUser(Identity): if user_object is None: return super(QuayDeferredPermissionUser, self).can(permission) - # Add the superuser need, if applicable. - if superusers.is_superuser(user_object.username): + # Add the superuser need, if applicable: + # - If the user's role is an admin (direct login) and they are a superuser + # - If the user has granted the superuser scope + superuser_role = self._user_role_for_scopes('superuser') + + if superuser_role == 'admin' and superusers.is_superuser(user_object.username): + self.provides.add(_SuperUserNeed()) + elif superuser_role == 'superuser': self.provides.add(_SuperUserNeed()) # Add the user specific permissions, only for non-oauth permission diff --git a/auth/scopes.py b/auth/scopes.py index 128f99b98..23587d1b3 100644 --- a/auth/scopes.py +++ b/auth/scopes.py @@ -1,5 +1,5 @@ from collections import namedtuple - +import features Scope = namedtuple('scope', ['scope', 'icon', 'dangerous', 'title', 'description']) @@ -59,6 +59,15 @@ DIRECT_LOGIN = Scope(scope='direct_user_login', description=('This scope should not be available to OAuth applications. ' 'Never approve a request for this scope!')) +SUPERUSER = Scope(scope='super:user', + icon='fa-street-view', + dangerous=True, + title='Super User Access', + description=('This application will be able to administer your installation ' + 'including managing users, managing organizations and other ' + 'features found in the superuser panel. You should have ' + 'absolute trust in the requesting application before granting this ' + 'permission.')) ALL_SCOPES = {scope.scope:scope for scope in (READ_REPO, WRITE_REPO, ADMIN_REPO, CREATE_REPO, READ_USER, ORG_ADMIN)} @@ -73,6 +82,9 @@ IMPLIED_SCOPES = { None: set(), } +if features.SUPER_USERS: + ALL_SCOPES[SUPERUSER.scope] = SUPERUSER + IMPLIED_SCOPES[SUPERUSER] = {SUPERUSER} def scopes_from_scope_string(scopes): if not scopes: diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index 9a235d6a0..df122fdb0 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -308,6 +308,10 @@ def require_fresh_login(func): if not user: raise Unauthorized() + oauth_token = get_validated_oauth_token() + if oauth_token: + return func(*args, **kwargs) + logger.debug('Checking fresh login for user %s', user.username) last_login = session.get('login_time', datetime.datetime.min) diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index 4f70f0761..2ec28c56a 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -19,6 +19,7 @@ from endpoints.api.logs import get_logs from data import model from auth.permissions import SuperUserPermission from auth.auth_context import get_authenticated_user +from auth import scopes from util.useremails import send_confirmation_email, send_recovery_email import features @@ -42,6 +43,7 @@ class SuperUserGetLogsForService(ApiResource): @require_fresh_login @verify_not_prod @nickname('getSystemLogs') + @require_scope(scopes.SUPERUSER) def get(self, service): """ Returns the logs for the specific service. """ if SuperUserPermission().can(): @@ -70,6 +72,7 @@ class SuperUserSystemLogServices(ApiResource): @require_fresh_login @verify_not_prod @nickname('listSystemLogServices') + @require_scope(scopes.SUPERUSER) def get(self): """ List the system logs for the current system. """ if SuperUserPermission().can(): @@ -93,6 +96,7 @@ class SuperUserLogs(ApiResource): @query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('performer', 'Username for which to filter logs.', type=str) + @require_scope(scopes.SUPERUSER) def get(self, args): """ List the usage logs for the current system. """ if SuperUserPermission().can(): @@ -129,6 +133,7 @@ class ChangeLog(ApiResource): @require_fresh_login @verify_not_prod @nickname('getChangeLog') + @require_scope(scopes.SUPERUSER) def get(self): """ Returns the change log for this installation. """ if SuperUserPermission().can(): @@ -149,6 +154,7 @@ class SuperUserOrganizationList(ApiResource): @require_fresh_login @verify_not_prod @nickname('listAllOrganizations') + @require_scope(scopes.SUPERUSER) def get(self): """ Returns a list of all organizations in the system. """ if SuperUserPermission().can(): @@ -187,6 +193,7 @@ class SuperUserList(ApiResource): @require_fresh_login @verify_not_prod @nickname('listAllUsers') + @require_scope(scopes.SUPERUSER) def get(self): """ Returns a list of all users in the system. """ if SuperUserPermission().can(): @@ -202,6 +209,7 @@ class SuperUserList(ApiResource): @verify_not_prod @nickname('createInstallUser') @validate_json_request('CreateInstallUser') + @require_scope(scopes.SUPERUSER) def post(self): """ Creates a new user. """ user_information = request.get_json() @@ -239,6 +247,7 @@ class SuperUserSendRecoveryEmail(ApiResource): @require_fresh_login @verify_not_prod @nickname('sendInstallUserRecoveryEmail') + @require_scope(scopes.SUPERUSER) def post(self, username): if SuperUserPermission().can(): user = model.get_nonrobot_user(username) @@ -288,6 +297,7 @@ class SuperUserManagement(ApiResource): @require_fresh_login @verify_not_prod @nickname('getInstallUser') + @require_scope(scopes.SUPERUSER) def get(self, username): """ Returns information about the specified user. """ if SuperUserPermission().can(): @@ -302,6 +312,7 @@ class SuperUserManagement(ApiResource): @require_fresh_login @verify_not_prod @nickname('deleteInstallUser') + @require_scope(scopes.SUPERUSER) def delete(self, username): """ Deletes the specified user. """ if SuperUserPermission().can(): @@ -321,6 +332,7 @@ class SuperUserManagement(ApiResource): @verify_not_prod @nickname('changeInstallUser') @validate_json_request('UpdateUser') + @require_scope(scopes.SUPERUSER) def put(self, username): """ Updates information about the specified user. """ if SuperUserPermission().can(): @@ -371,6 +383,7 @@ class SuperUserOrganizationManagement(ApiResource): @require_fresh_login @verify_not_prod @nickname('deleteOrganization') + @require_scope(scopes.SUPERUSER) def delete(self, name): """ Deletes the specified organization. """ if SuperUserPermission().can(): @@ -385,6 +398,7 @@ class SuperUserOrganizationManagement(ApiResource): @verify_not_prod @nickname('changeOrganization') @validate_json_request('UpdateOrg') + @require_scope(scopes.SUPERUSER) def put(self, name): """ Updates information about the specified user. """ if SuperUserPermission().can():