Using capabilities instead of roles for access control.
This commit is contained in:
@@ -15,15 +15,20 @@ import attract.shots_and_assets
|
||||
|
||||
EXTENSION_NAME = 'attract'
|
||||
|
||||
# Roles required to view task or shot details.
|
||||
ROLES_REQUIRED_TO_VIEW_ITEMS = {'demo', 'subscriber', 'admin'}
|
||||
|
||||
|
||||
class AttractExtension(PillarExtension):
|
||||
has_project_settings = True
|
||||
user_roles = {'attract-user'}
|
||||
user_roles_indexable = {'attract-user'}
|
||||
|
||||
user_caps = {
|
||||
'attract-user': {'attract-view', 'attract-use'},
|
||||
'org-attract': {'attract-view', 'attract-use'},
|
||||
'subscriber': {'attract-view'},
|
||||
'demo': {'attract-view'},
|
||||
'admin': {'attract-view', 'attract-use'},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._log = logging.getLogger('%s.AttractExtension' % __name__)
|
||||
self.task_manager = attract.tasks.TaskManager()
|
||||
|
@@ -6,10 +6,6 @@ import bson
|
||||
|
||||
from pillar import attrs_extra
|
||||
|
||||
# Having either of these roles is minimum requirement for using Attract.
|
||||
ROLES_REQUIRED_TO_USE_ATTRACT = {'attract-user', 'admin'}
|
||||
ROLES_REQUIRED_TO_VIEW_ATTRACT = {'admin', 'subscriber', 'demo'}
|
||||
|
||||
# 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'}
|
||||
@@ -20,10 +16,10 @@ class Actions(enum.Enum):
|
||||
USE = 'use'
|
||||
|
||||
|
||||
# Required roles for a given action.
|
||||
req_roles = {
|
||||
Actions.VIEW: ROLES_REQUIRED_TO_VIEW_ATTRACT,
|
||||
Actions.USE: ROLES_REQUIRED_TO_USE_ATTRACT,
|
||||
# Required capability for a given action.
|
||||
req_cap = {
|
||||
Actions.VIEW: 'attract-view',
|
||||
Actions.USE: 'attract-use',
|
||||
}
|
||||
|
||||
|
||||
@@ -45,23 +41,21 @@ class Auth(object):
|
||||
"""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')
|
||||
user = users_coll.find_one({'_id': user_id}, {'roles': 1})
|
||||
if not user:
|
||||
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_roles = set(user.get('roles', []))
|
||||
require_roles = set(ROLES_REQUIRED_TO_USE_ATTRACT)
|
||||
user = UserClass.construct('', db_user)
|
||||
return user.has_cap('attract-use')
|
||||
|
||||
intersection = require_roles.intersection(user_roles)
|
||||
return bool(intersection)
|
||||
|
||||
def current_user_may(self, action: Actions, project_id: bson.ObjectId=None) -> bool:
|
||||
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.
|
||||
@@ -86,20 +80,18 @@ class Auth(object):
|
||||
g.attract_rights is a frozenset that contains zero or more Actions.
|
||||
"""
|
||||
|
||||
from pillar.api.utils.authorization import user_matches_roles
|
||||
from pillar.api.utils.authentication import current_user_id
|
||||
from pillar.auth import current_user
|
||||
from pillar.api.projects.utils import user_rights_in_project
|
||||
|
||||
user_id = current_user_id()
|
||||
if not user_id:
|
||||
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:
|
||||
roles = req_roles[action]
|
||||
if user_matches_roles(roles):
|
||||
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.
|
||||
|
@@ -5,7 +5,7 @@ from flask import Blueprint, render_template, redirect, url_for, request, jsonif
|
||||
import flask_login
|
||||
import werkzeug.exceptions as wz_exceptions
|
||||
|
||||
from pillar.auth import current_web_user as current_user
|
||||
from pillar.auth import current_user as current_user
|
||||
from pillar.api.utils import str2id
|
||||
from pillar.web.utils import attach_project_pictures
|
||||
import pillar.web.subquery
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import logging
|
||||
|
||||
import flask
|
||||
import flask_login
|
||||
import werkzeug.exceptions as wz_exceptions
|
||||
|
||||
import pillarsdk
|
||||
from pillar.web.system_util import pillar_api
|
||||
from pillar.web.utils import get_file
|
||||
from pillar.auth import current_user
|
||||
|
||||
from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS
|
||||
from attract import current_attract
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -53,12 +53,10 @@ def for_project(node_type_name, task_types_for_nt, project, attract_props,
|
||||
|
||||
def view_node(project, node_id, node_type_name):
|
||||
"""Returns the node if the user has access.
|
||||
|
||||
Uses attract.ROLES_REQUIRED_TO_VIEW_ITEMS to check permissions.
|
||||
"""
|
||||
|
||||
# asset list is public, asset details are not.
|
||||
if not flask_login.current_user.has_role(*ROLES_REQUIRED_TO_VIEW_ITEMS):
|
||||
if not current_user.has_cap('attract-view'):
|
||||
raise wz_exceptions.Forbidden()
|
||||
|
||||
api = pillar_api()
|
||||
|
@@ -10,11 +10,12 @@ import pillarsdk
|
||||
from pillar.web.system_util import pillar_api
|
||||
import pillar.api.utils
|
||||
import pillar.web.subquery
|
||||
from pillar.auth import current_user
|
||||
|
||||
from attract.routes import attract_project_view
|
||||
from attract.node_types.task import node_type_task
|
||||
from attract.node_types.shot import node_type_shot
|
||||
from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS, EXTENSION_NAME
|
||||
from attract import current_attract, EXTENSION_NAME
|
||||
|
||||
blueprint = Blueprint('attract.tasks', __name__, url_prefix='/tasks')
|
||||
perproject_blueprint = Blueprint('attract.tasks.perproject', __name__,
|
||||
@@ -65,7 +66,7 @@ def view_task(project, attract_props, task_id):
|
||||
return for_project(project, task_id=task_id)
|
||||
|
||||
# Task list is public, task details are not.
|
||||
if not flask_login.current_user.has_role(*ROLES_REQUIRED_TO_VIEW_ITEMS):
|
||||
if not current_user.has_cap('attract-view'):
|
||||
raise wz_exceptions.Forbidden()
|
||||
|
||||
api = pillar_api()
|
||||
|
Reference in New Issue
Block a user