12ff4b107c
Basing sessions on UUIDs must be done in phases. First all users must obtain an UUID. Once a backfill has given all previous users UUIDs and new users are being generated with UUIDs, then we can actually change the session to be based on that value.
249 lines
9.8 KiB
Python
249 lines
9.8 KiB
Python
import logging
|
|
|
|
from flask.ext.principal import identity_loaded, Permission, Identity, identity_changed
|
|
from collections import namedtuple, defaultdict
|
|
from functools import partial
|
|
|
|
import scopes
|
|
|
|
from data import model
|
|
from app import app
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
_ResourceNeed = namedtuple('resource', ['type', 'namespace', 'name', 'role'])
|
|
_RepositoryNeed = partial(_ResourceNeed, 'repository')
|
|
_NamespaceWideNeed = namedtuple('namespacewide', ['type', 'namespace', 'role'])
|
|
_OrganizationNeed = partial(_NamespaceWideNeed, 'organization')
|
|
_OrganizationRepoNeed = partial(_NamespaceWideNeed, 'organizationrepo')
|
|
_TeamTypeNeed = namedtuple('teamwideneed', ['type', 'orgname', 'teamname', 'role'])
|
|
_TeamNeed = partial(_TeamTypeNeed, 'orgteam')
|
|
_UserTypeNeed = namedtuple('userspecificneed', ['type', 'username', 'role'])
|
|
_UserNeed = partial(_UserTypeNeed, 'user')
|
|
_SuperUserNeed = partial(namedtuple('superuserneed', ['type']), 'superuser')
|
|
|
|
|
|
REPO_ROLES = [None, 'read', 'write', 'admin']
|
|
TEAM_ROLES = [None, 'member', 'creator', 'admin']
|
|
USER_ROLES = [None, 'read', 'admin']
|
|
|
|
TEAM_REPO_ROLES = {
|
|
'admin': 'admin',
|
|
'creator': 'read',
|
|
'member': 'read',
|
|
}
|
|
|
|
SCOPE_MAX_REPO_ROLES = defaultdict(lambda: None)
|
|
SCOPE_MAX_REPO_ROLES.update({
|
|
scopes.READ_REPO: 'read',
|
|
scopes.WRITE_REPO: 'write',
|
|
scopes.ADMIN_REPO: 'admin',
|
|
scopes.DIRECT_LOGIN: 'admin',
|
|
})
|
|
|
|
SCOPE_MAX_TEAM_ROLES = defaultdict(lambda: None)
|
|
SCOPE_MAX_TEAM_ROLES.update({
|
|
scopes.CREATE_REPO: 'creator',
|
|
scopes.DIRECT_LOGIN: 'admin',
|
|
scopes.ORG_ADMIN: 'admin',
|
|
})
|
|
|
|
SCOPE_MAX_USER_ROLES = defaultdict(lambda: None)
|
|
SCOPE_MAX_USER_ROLES.update({
|
|
scopes.READ_USER: 'read',
|
|
scopes.DIRECT_LOGIN: 'admin',
|
|
})
|
|
|
|
|
|
class QuayDeferredPermissionUser(Identity):
|
|
def __init__(self, db_id, auth_type, scopes):
|
|
super(QuayDeferredPermissionUser, self).__init__(db_id, auth_type)
|
|
|
|
self._permissions_loaded = False
|
|
self._scope_set = scopes
|
|
|
|
def _translate_role_for_scopes(self, cardinality, max_roles, role):
|
|
if self._scope_set is None:
|
|
return role
|
|
|
|
max_for_scopes = max({cardinality.index(max_roles[scope]) for scope in self._scope_set})
|
|
|
|
if max_for_scopes < cardinality.index(role):
|
|
logger.debug('Translated permission %s -> %s', role, cardinality[max_for_scopes])
|
|
return cardinality[max_for_scopes]
|
|
else:
|
|
return role
|
|
|
|
def _team_role_for_scopes(self, role):
|
|
return self._translate_role_for_scopes(TEAM_ROLES, SCOPE_MAX_TEAM_ROLES, role)
|
|
|
|
def _repo_role_for_scopes(self, role):
|
|
return self._translate_role_for_scopes(REPO_ROLES, SCOPE_MAX_REPO_ROLES, role)
|
|
|
|
def _user_role_for_scopes(self, role):
|
|
return self._translate_role_for_scopes(USER_ROLES, SCOPE_MAX_USER_ROLES, role)
|
|
|
|
def can(self, permission):
|
|
if not self._permissions_loaded:
|
|
logger.debug('Loading user permissions after deferring.')
|
|
user_object = model.get_user_by_id(self.id)
|
|
|
|
# Add the superuser need, if applicable.
|
|
if (user_object.username is not None and
|
|
user_object.username in app.config.get('SUPER_USERS', [])):
|
|
self.provides.add(_SuperUserNeed())
|
|
|
|
# Add the user specific permissions, only for non-oauth permission
|
|
user_grant = _UserNeed(user_object.username, self._user_role_for_scopes('admin'))
|
|
logger.debug('User permission: {0}'.format(user_grant))
|
|
self.provides.add(user_grant)
|
|
|
|
# Every user is the admin of their own 'org'
|
|
user_namespace = _OrganizationNeed(user_object.username, self._team_role_for_scopes('admin'))
|
|
logger.debug('User namespace permission: {0}'.format(user_namespace))
|
|
self.provides.add(user_namespace)
|
|
|
|
# Org repo roles can differ for scopes
|
|
user_repos = _OrganizationRepoNeed(user_object.username, self._repo_role_for_scopes('admin'))
|
|
logger.debug('User namespace repo permission: {0}'.format(user_repos))
|
|
self.provides.add(user_repos)
|
|
|
|
# Add repository permissions
|
|
for perm in model.get_all_user_permissions(user_object):
|
|
repo_grant = _RepositoryNeed(perm.repository.namespace_user.username, perm.repository.name,
|
|
self._repo_role_for_scopes(perm.role.name))
|
|
logger.debug('User added permission: {0}'.format(repo_grant))
|
|
self.provides.add(repo_grant)
|
|
|
|
# Add namespace permissions derived
|
|
for team in model.get_org_wide_permissions(user_object):
|
|
team_org_grant = _OrganizationNeed(team.organization.username,
|
|
self._team_role_for_scopes(team.role.name))
|
|
logger.debug('Organization team added permission: {0}'.format(team_org_grant))
|
|
self.provides.add(team_org_grant)
|
|
|
|
|
|
team_repo_role = TEAM_REPO_ROLES[team.role.name]
|
|
org_repo_grant = _OrganizationRepoNeed(team.organization.username,
|
|
self._repo_role_for_scopes(team_repo_role))
|
|
logger.debug('Organization team added repo permission: {0}'.format(org_repo_grant))
|
|
self.provides.add(org_repo_grant)
|
|
|
|
team_grant = _TeamNeed(team.organization.username, team.name,
|
|
self._team_role_for_scopes(team.role.name))
|
|
logger.debug('Team added permission: {0}'.format(team_grant))
|
|
self.provides.add(team_grant)
|
|
|
|
self._permissions_loaded = True
|
|
|
|
return super(QuayDeferredPermissionUser, self).can(permission)
|
|
|
|
|
|
class ModifyRepositoryPermission(Permission):
|
|
def __init__(self, namespace, name):
|
|
admin_need = _RepositoryNeed(namespace, name, 'admin')
|
|
write_need = _RepositoryNeed(namespace, name, 'write')
|
|
org_admin_need = _OrganizationRepoNeed(namespace, 'admin')
|
|
org_write_need = _OrganizationRepoNeed(namespace, 'write')
|
|
super(ModifyRepositoryPermission, self).__init__(admin_need, write_need, org_admin_need,
|
|
org_write_need)
|
|
|
|
|
|
class ReadRepositoryPermission(Permission):
|
|
def __init__(self, namespace, name):
|
|
admin_need = _RepositoryNeed(namespace, name, 'admin')
|
|
write_need = _RepositoryNeed(namespace, name, 'write')
|
|
read_need = _RepositoryNeed(namespace, name, 'read')
|
|
org_admin_need = _OrganizationRepoNeed(namespace, 'admin')
|
|
org_write_need = _OrganizationRepoNeed(namespace, 'write')
|
|
org_read_need = _OrganizationRepoNeed(namespace, 'read')
|
|
super(ReadRepositoryPermission, self).__init__(admin_need, write_need, read_need,
|
|
org_admin_need, org_read_need, org_write_need)
|
|
|
|
|
|
class AdministerRepositoryPermission(Permission):
|
|
def __init__(self, namespace, name):
|
|
admin_need = _RepositoryNeed(namespace, name, 'admin')
|
|
org_admin_need = _OrganizationRepoNeed(namespace, 'admin')
|
|
super(AdministerRepositoryPermission, self).__init__(admin_need,
|
|
org_admin_need)
|
|
|
|
|
|
class CreateRepositoryPermission(Permission):
|
|
def __init__(self, namespace):
|
|
admin_org = _OrganizationNeed(namespace, 'admin')
|
|
create_repo_org = _OrganizationNeed(namespace, 'creator')
|
|
super(CreateRepositoryPermission, self).__init__(admin_org,
|
|
create_repo_org)
|
|
|
|
class SuperUserPermission(Permission):
|
|
def __init__(self):
|
|
need = _SuperUserNeed()
|
|
super(SuperUserPermission, self).__init__(need)
|
|
|
|
|
|
class UserAdminPermission(Permission):
|
|
def __init__(self, username):
|
|
user_admin = _UserNeed(username, 'admin')
|
|
super(UserAdminPermission, self).__init__(user_admin)
|
|
|
|
|
|
class UserReadPermission(Permission):
|
|
def __init__(self, username):
|
|
user_admin = _UserNeed(username, 'admin')
|
|
user_read = _UserNeed(username, 'read')
|
|
super(UserReadPermission, self).__init__(user_read, user_admin)
|
|
|
|
|
|
class AdministerOrganizationPermission(Permission):
|
|
def __init__(self, org_name):
|
|
admin_org = _OrganizationNeed(org_name, 'admin')
|
|
super(AdministerOrganizationPermission, self).__init__(admin_org)
|
|
|
|
|
|
class OrganizationMemberPermission(Permission):
|
|
def __init__(self, org_name):
|
|
admin_org = _OrganizationNeed(org_name, 'admin')
|
|
repo_creator_org = _OrganizationNeed(org_name, 'creator')
|
|
org_member = _OrganizationNeed(org_name, 'member')
|
|
super(OrganizationMemberPermission, self).__init__(admin_org, org_member,
|
|
repo_creator_org)
|
|
|
|
|
|
class ViewTeamPermission(Permission):
|
|
def __init__(self, org_name, team_name):
|
|
team_admin = _TeamNeed(org_name, team_name, 'admin')
|
|
team_creator = _TeamNeed(org_name, team_name, 'creator')
|
|
team_member = _TeamNeed(org_name, team_name, 'member')
|
|
admin_org = _OrganizationNeed(org_name, 'admin')
|
|
super(ViewTeamPermission, self).__init__(team_admin, team_creator,
|
|
team_member, admin_org)
|
|
|
|
|
|
@identity_loaded.connect_via(app)
|
|
def on_identity_loaded(sender, identity):
|
|
logger.debug('Identity loaded: %s' % identity)
|
|
# We have verified an identity, load in all of the permissions
|
|
|
|
if isinstance(identity, QuayDeferredPermissionUser):
|
|
logger.debug('Deferring permissions for user: %s', identity.id)
|
|
|
|
elif identity.auth_type == 'user_db_id':
|
|
logger.debug('Switching username permission to deferred object: %s', identity.id)
|
|
switch_to_deferred = QuayDeferredPermissionUser(identity.id, 'user_db_id', {scopes.DIRECT_LOGIN})
|
|
identity_changed.send(app, identity=switch_to_deferred)
|
|
|
|
elif identity.auth_type == 'token':
|
|
logger.debug('Loading permissions for token: %s', identity.id)
|
|
token_data = model.load_token_data(identity.id)
|
|
|
|
repo_grant = _RepositoryNeed(token_data.repository.namespace_user.username,
|
|
token_data.repository.name,
|
|
token_data.role.name)
|
|
logger.debug('Delegate token added permission: {0}'.format(repo_grant))
|
|
identity.provides.add(repo_grant)
|
|
|
|
else:
|
|
logger.error('Unknown identity auth type: %s', identity.auth_type)
|