This is especially important for IP ranges on Organizations, which can change user roles on the fly in memory.
104 lines
3.3 KiB
Python
104 lines
3.3 KiB
Python
import enum
|
|
import flask
|
|
|
|
import attr
|
|
import bson
|
|
|
|
from pillar import attrs_extra
|
|
|
|
# Having any of these methods on a project means you can use Attract.
|
|
# Prerequisite: the project is set up for Attract and has a Manager assigned to it.
|
|
PROJECT_METHODS_TO_USE_ATTRACT = {'PUT'}
|
|
|
|
|
|
class Actions(enum.Enum):
|
|
VIEW = 'view'
|
|
USE = 'use'
|
|
|
|
|
|
# Required capability for a given action.
|
|
req_cap = {
|
|
Actions.VIEW: 'attract-view',
|
|
Actions.USE: 'attract-use',
|
|
}
|
|
|
|
|
|
@attr.s
|
|
class Auth(object):
|
|
"""Handles authorization for Attract."""
|
|
|
|
_log = attrs_extra.log('%s.Auth' % __name__)
|
|
Actions = Actions # this allows using current_attract.auth.Actions
|
|
|
|
def current_user_is_attract_user(self) -> bool:
|
|
"""Returns True iff the current user has Attract User role."""
|
|
|
|
from pillar.auth import current_user
|
|
|
|
return current_user.has_cap('attract-use')
|
|
|
|
def user_is_attract_user(self, user_id: bson.ObjectId) -> bool:
|
|
"""Returns True iff the user has Attract User role."""
|
|
|
|
from pillar import current_app
|
|
from pillar.auth import UserClass
|
|
|
|
assert isinstance(user_id, bson.ObjectId)
|
|
|
|
# TODO: move role checking code to Pillar.
|
|
users_coll = current_app.db('users')
|
|
db_user = users_coll.find_one({'_id': user_id}, {'roles': 1})
|
|
if not db_user:
|
|
self._log.debug('user_is_attract_user: User %s not found', user_id)
|
|
return False
|
|
|
|
user = UserClass.construct('', db_user)
|
|
return user.has_cap('attract-use')
|
|
|
|
def current_user_may(self, action: Actions, project_id: bson.ObjectId = None) -> bool:
|
|
"""Returns True iff the user is authorised to use/view Attract on the current project.
|
|
|
|
Requires that determine_user_rights() was called before.
|
|
"""
|
|
|
|
try:
|
|
attract_rights = flask.g.attract_rights
|
|
except AttributeError:
|
|
if not project_id:
|
|
self._log.error('current_user_may() called without previous call '
|
|
'to current_user_rights()')
|
|
return False
|
|
|
|
self.determine_user_rights(project_id)
|
|
attract_rights = flask.g.attract_rights
|
|
|
|
return action in attract_rights
|
|
|
|
def determine_user_rights(self, project_id: bson.ObjectId):
|
|
"""Updates g.attract_rights to reflect the current user's usage rights.
|
|
|
|
g.attract_rights is a frozenset that contains zero or more Actions.
|
|
"""
|
|
|
|
from pillar.auth import current_user
|
|
from pillar.api.projects.utils import user_rights_in_project
|
|
|
|
if current_user.is_anonymous:
|
|
self._log.debug('Anonymous user never has access to Attract.')
|
|
flask.g.attract_rights = frozenset()
|
|
return
|
|
|
|
rights = set()
|
|
for action in Actions:
|
|
cap = req_cap[action]
|
|
if current_user.has_cap(cap):
|
|
rights.add(action)
|
|
|
|
# TODO Sybren: possibly split this up into a manager-fetching func + authorisation func.
|
|
# TODO: possibly store the user rights on the current project in the current_user object?
|
|
allowed_on_proj = user_rights_in_project(project_id)
|
|
if not allowed_on_proj.intersection(PROJECT_METHODS_TO_USE_ATTRACT):
|
|
rights.discard(Actions.USE)
|
|
|
|
flask.g.attract_rights = frozenset(rights)
|