Using capabilities instead of roles for access control.

This commit is contained in:
2017-08-24 14:21:33 +02:00
parent c4071c1e03
commit c7b83d2d8b
5 changed files with 29 additions and 33 deletions

View File

@@ -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()

View File

@@ -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.

View File

@@ -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

View File

@@ -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()

View File

@@ -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()