Files
attract/attract/auth.py
Sybren A. Stüvel 13dc6fea8e Don't do DB query to inspect current user.
This is especially important for IP ranges on Organizations, which can
change user roles on the fly in memory.
2018-01-24 14:57:30 +01:00

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)