Implement complex permissions for worklists and boards
We default to two levels of permissions, each with potential for containing multiple users. This commit adds a database migration which adds two tables (worklist_permissions and board_permissions) to represent the relationships between worklists and permissions and boards and permissions. Board permissions override worklist permissions if the worklist is a lane in that board. Existing boards and worklists will have permissions created such that the creator has full permissions on the board/worklist. Change-Id: I4168572cc98a93a59d8fe6f35d48432959ac7adf
This commit is contained in:
parent
f7c2d2320c
commit
03d2a20fbe
@ -15,7 +15,6 @@
|
||||
|
||||
from oslo_config import cfg
|
||||
from pecan import abort
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
from pecan import response
|
||||
from pecan import rest
|
||||
@ -39,8 +38,9 @@ def visible(board, user=None):
|
||||
if not board:
|
||||
return False
|
||||
if user and board.private:
|
||||
# TODO(SotK): Permissions
|
||||
return user == board.creator_id
|
||||
permissions = boards_api.get_permissions(board.id, user)
|
||||
return any(name in permissions
|
||||
for name in ['edit_board', 'move_cards'])
|
||||
return not board.private
|
||||
|
||||
|
||||
@ -49,8 +49,7 @@ def editable(board, user=None):
|
||||
return False
|
||||
if not user:
|
||||
return False
|
||||
# TODO(SotK): Permissions
|
||||
return user == board.creator_id
|
||||
return 'edit_board' in boards_api.get_permissions(board.id, user)
|
||||
|
||||
|
||||
def get_lane(list_id, board):
|
||||
@ -79,6 +78,47 @@ def update_lanes(board_dict, board_id):
|
||||
del board_dict['lanes']
|
||||
|
||||
|
||||
class PermissionsController(rest.RestController):
|
||||
"""Manages operations on board permissions."""
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose([wtypes.text], int)
|
||||
def get(self, board_id):
|
||||
"""Get board permissions for the current user.
|
||||
|
||||
:param board_id: The ID of the board.
|
||||
|
||||
"""
|
||||
return boards_api.get_permissions(board_id, request.current_user_id)
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.authenticated)
|
||||
@wsme_pecan.wsexpose(wtypes.text, int,
|
||||
body=wtypes.DictType(wtypes.text, wtypes.text))
|
||||
def post(self, board_id, permission):
|
||||
"""Add a new permission to the board.
|
||||
|
||||
:param board_id: The ID of the board.
|
||||
:param permission: The dict to use to create the permission.
|
||||
|
||||
"""
|
||||
return boards_api.create_permission(board_id)
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.authenticated)
|
||||
@wsme_pecan.wsexpose(wtypes.text, int,
|
||||
body=wtypes.DictType(wtypes.text, wtypes.text))
|
||||
def put(self, board_id, permission):
|
||||
"""Update a permission of the board.
|
||||
|
||||
:param board_id: The ID of the board.
|
||||
:param permission: The new contents of the permission.
|
||||
|
||||
"""
|
||||
return boards_api.update_permission(board_id, permission).codename
|
||||
|
||||
|
||||
class BoardsController(rest.RestController):
|
||||
"""Manages operations on boards."""
|
||||
|
||||
@ -98,6 +138,8 @@ class BoardsController(rest.RestController):
|
||||
board_model = wmodels.Board.from_db_model(board)
|
||||
board_model.lanes = [wmodels.Lane.from_db_model(lane)
|
||||
for lane in board.lanes]
|
||||
board_model.owners = boards_api.get_owners(id)
|
||||
board_model.users = boards_api.get_users(id)
|
||||
return board_model
|
||||
else:
|
||||
raise exc.NotFound(_("Board %s not found") % id)
|
||||
@ -105,9 +147,10 @@ class BoardsController(rest.RestController):
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose([wmodels.Board], wtypes.text, int, int,
|
||||
bool, wtypes.text, wtypes.text)
|
||||
bool, int, wtypes.text, wtypes.text)
|
||||
def get_all(self, title=None, creator_id=None, project_id=None,
|
||||
archived=False, sort_field='id', sort_dir='asc'):
|
||||
archived=False, user_id=None, sort_field='id',
|
||||
sort_dir='asc'):
|
||||
"""Retrieve definitions of all of the boards.
|
||||
|
||||
:param title: A string to filter the title by.
|
||||
@ -120,6 +163,7 @@ class BoardsController(rest.RestController):
|
||||
"""
|
||||
boards = boards_api.get_all(title=title,
|
||||
creator_id=creator_id,
|
||||
user_id=user_id,
|
||||
project_id=project_id,
|
||||
sort_field=sort_field,
|
||||
sort_dir=sort_dir)
|
||||
@ -131,6 +175,8 @@ class BoardsController(rest.RestController):
|
||||
board_model = wmodels.Board.from_db_model(board)
|
||||
board_model.lanes = [wmodels.Lane.from_db_model(lane)
|
||||
for lane in board.lanes]
|
||||
board_model.owners = boards_api.get_owners(board.id)
|
||||
board_model.users = boards_api.get_users(board.id)
|
||||
visible_boards.append(board_model)
|
||||
|
||||
# Apply the query response headers
|
||||
@ -154,11 +200,30 @@ class BoardsController(rest.RestController):
|
||||
abort(400, _("You can't select the creator of a board."))
|
||||
board_dict.update({"creator_id": user_id})
|
||||
lanes = board_dict.pop('lanes')
|
||||
owners = board_dict.pop('owners')
|
||||
users = board_dict.pop('users')
|
||||
if not owners:
|
||||
owners = [user_id]
|
||||
if not users:
|
||||
users = []
|
||||
|
||||
created_board = boards_api.create(board_dict)
|
||||
for lane in lanes:
|
||||
boards_api.add_lane(created_board.id, lane.as_dict())
|
||||
|
||||
edit_permission = {
|
||||
'name': 'edit_board_%d' % created_board.id,
|
||||
'codename': 'edit_board',
|
||||
'users': owners
|
||||
}
|
||||
move_permission = {
|
||||
'name': 'move_cards_%d' % created_board.id,
|
||||
'codename': 'move_cards',
|
||||
'users': users
|
||||
}
|
||||
boards_api.create_permission(created_board.id, edit_permission)
|
||||
boards_api.create_permission(created_board.id, move_permission)
|
||||
|
||||
return wmodels.Board.from_db_model(created_board)
|
||||
|
||||
@decorators.db_exceptions
|
||||
@ -177,13 +242,15 @@ class BoardsController(rest.RestController):
|
||||
|
||||
board_dict = board.as_dict(omit_unset=True)
|
||||
update_lanes(board_dict, id)
|
||||
boards_api.update(id, board_dict)
|
||||
updated_board = boards_api.update(id, board_dict)
|
||||
|
||||
if visible(board, user_id):
|
||||
board_model = wmodels.Board.from_db_model(board)
|
||||
board_model = wmodels.Board.from_db_model(updated_board)
|
||||
if board.lanes:
|
||||
board_model.lanes = [wmodels.Lane.from_db_model(lane)
|
||||
for lane in board.lanes]
|
||||
board_model.owners = boards_api.get_owners(id)
|
||||
board_model.users = boards_api.get_users(id)
|
||||
return board_model
|
||||
else:
|
||||
raise exc.NotFound(_("Board %s not found") % id)
|
||||
@ -202,40 +269,9 @@ class BoardsController(rest.RestController):
|
||||
if not editable(board, user_id):
|
||||
raise exc.NotFound(_("Board %s not found") % id)
|
||||
|
||||
board_dict = wmodels.Board.from_db_model(board).as_dict(
|
||||
omit_unset=True)
|
||||
board_dict['lanes'] = board.lanes
|
||||
board_dict.update({"archived": True})
|
||||
boards_api.update(id, board_dict)
|
||||
boards_api.update(id, {"archived": True})
|
||||
|
||||
for lane in board_dict['lanes']:
|
||||
worklist = lane.worklist
|
||||
worklist_dict = wmodels.Worklist.from_db_model(worklist).as_dict(
|
||||
omit_unset=True)
|
||||
worklist_dict.update({'archived': True})
|
||||
worklists_api.update(worklist.id, worklist_dict)
|
||||
for lane in board.lanes:
|
||||
worklists_api.update(lane.worklist.id, {"archived": True})
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose(wtypes.DictType(str, bool), int)
|
||||
def permissions(self, id):
|
||||
"""Get the permissions the current user has for the board.
|
||||
|
||||
:param id: The ID of the board to check permissions for.
|
||||
|
||||
"""
|
||||
board = boards_api.get(id)
|
||||
user_id = request.current_user_id
|
||||
return {
|
||||
'edit_board': editable(board, user_id),
|
||||
'move_cards': editable(board, user_id) # TODO(SotK): check lanes
|
||||
}
|
||||
|
||||
@expose()
|
||||
def _route(self, args, request):
|
||||
if request.method == 'GET' and len(args) > 1:
|
||||
if args[1] == "permissions":
|
||||
# Request to a permissions endpoint
|
||||
return self.permissions, [args[0]]
|
||||
|
||||
return super(BoardsController, self)._route(args, request)
|
||||
permissions = PermissionsController()
|
||||
|
@ -472,6 +472,12 @@ class Worklist(base.APIBase):
|
||||
"""A flag to identify whether the contents are obtained by a filter or are
|
||||
stored in the database."""
|
||||
|
||||
owners = wtypes.ArrayType(int)
|
||||
"""A list of the IDs of the users who have full permissions."""
|
||||
|
||||
users = wtypes.ArrayType(int)
|
||||
"""A list of the IDs of the users who can move items in the worklist."""
|
||||
|
||||
|
||||
# NOTE(SotK): Criteria/Criterion is used as the existing code in the webclient
|
||||
# refers to such filters as Criteria.
|
||||
@ -549,3 +555,9 @@ class Board(base.APIBase):
|
||||
|
||||
lanes = wtypes.ArrayType(Lane)
|
||||
"""A list containing the representions of the lanes in this board."""
|
||||
|
||||
owners = wtypes.ArrayType(int)
|
||||
"""A list of the IDs of the users who have full permissions."""
|
||||
|
||||
users = wtypes.ArrayType(int)
|
||||
"""A list of the IDs of the users who can move cards in the board."""
|
||||
|
@ -26,6 +26,7 @@ from storyboard.api.auth import authorization_checks as checks
|
||||
from storyboard.api.v1 import wmodels
|
||||
from storyboard.common import decorators
|
||||
from storyboard.common import exception as exc
|
||||
from storyboard.db.api import boards as boards_api
|
||||
from storyboard.db.api import worklists as worklists_api
|
||||
from storyboard.openstack.common.gettextutils import _ # noqa
|
||||
|
||||
@ -39,9 +40,17 @@ def visible(worklist, user=None, hide_lanes=False):
|
||||
return False
|
||||
if not worklist:
|
||||
return False
|
||||
if worklists_api.is_lane(worklist):
|
||||
board = boards_api.get_from_lane(worklist)
|
||||
permissions = boards_api.get_permissions(board.id, user)
|
||||
if board.private:
|
||||
return any(name in permissions
|
||||
for name in ['edit_board', 'move_cards'])
|
||||
return not board.private
|
||||
if user and worklist.private:
|
||||
# TODO(SotK): Permissions
|
||||
return user == worklist.creator_id
|
||||
permissions = worklists_api.get_permissions(worklist.id, user)
|
||||
return any(name in permissions
|
||||
for name in ['edit_worklist', 'move_items'])
|
||||
return not worklist.private
|
||||
|
||||
|
||||
@ -50,8 +59,55 @@ def editable(worklist, user=None):
|
||||
return False
|
||||
if not user:
|
||||
return False
|
||||
# TODO(SotK): Permissions
|
||||
return user == worklist.creator_id
|
||||
if worklists_api.is_lane(worklist):
|
||||
board = boards_api.get_from_lane(worklist)
|
||||
permissions = boards_api.get_permissions(board.id, user)
|
||||
return any(name in permissions
|
||||
for name in ['edit_board', 'move_cards'])
|
||||
return 'edit_worklist' in worklists_api.get_permissions(worklist.id, user)
|
||||
|
||||
|
||||
class PermissionsController(rest.RestController):
|
||||
"""Manages operations on worklist permissions."""
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose([wtypes.text], int)
|
||||
def get(self, worklist_id):
|
||||
"""Get worklist permissions for the current user.
|
||||
|
||||
:param worklist_id: The ID of the worklist.
|
||||
|
||||
"""
|
||||
return worklists_api.get_permissions(
|
||||
worklist_id, request.current_user_id)
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.authenticated)
|
||||
@wsme_pecan.wsexpose(wtypes.text, int,
|
||||
body=wtypes.DictType(wtypes.text, wtypes.text))
|
||||
def post(self, worklist_id, permission):
|
||||
"""Add a new permission to the worklist.
|
||||
|
||||
:param worklist_id: The ID of the worklist.
|
||||
:param permission: The dict to use to create the permission.
|
||||
|
||||
"""
|
||||
return worklists_api.create_permission(worklist_id)
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.authenticated)
|
||||
@wsme_pecan.wsexpose(wtypes.text, int,
|
||||
body=wtypes.DictType(wtypes.text, wtypes.text))
|
||||
def put(self, worklist_id, permission):
|
||||
"""Update a permission of the worklist.
|
||||
|
||||
:param worklist_id: The ID of the worklist.
|
||||
:param permission: The new contents of the permission.
|
||||
|
||||
"""
|
||||
return worklists_api.update_permission(
|
||||
worklist_id, permission).codename
|
||||
|
||||
|
||||
class ItemsSubcontroller(rest.RestController):
|
||||
@ -149,23 +205,27 @@ class WorklistsController(rest.RestController):
|
||||
|
||||
user_id = request.current_user_id
|
||||
if worklist and visible(worklist, user_id):
|
||||
return wmodels.Worklist.from_db_model(worklist)
|
||||
worklist_model = wmodels.Worklist.from_db_model(worklist)
|
||||
worklist_model.owners = worklists_api.get_owners(worklist.id)
|
||||
worklist_model.users = worklists_api.get_users(worklist.id)
|
||||
return worklist_model
|
||||
else:
|
||||
raise exc.NotFound(_("Worklist %s not found") % worklist_id)
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose([wmodels.Worklist], wtypes.text, int, int,
|
||||
bool, bool, wtypes.text, wtypes.text, int)
|
||||
bool, int, bool, wtypes.text, wtypes.text, int)
|
||||
def get_all(self, title=None, creator_id=None, project_id=None,
|
||||
archived=False, hide_lanes=True, sort_field='id',
|
||||
sort_dir='asc', board_id=None):
|
||||
archived=False, user_id=None, hide_lanes=True,
|
||||
sort_field='id', sort_dir='asc', board_id=None):
|
||||
"""Retrieve definitions of all of the worklists.
|
||||
|
||||
:param title: A string to filter the title by.
|
||||
:param creator_id: Filter worklists by their creator.
|
||||
:param project_id: Filter worklists by project ID.
|
||||
:param archived: Filter worklists by whether they are archived or not.
|
||||
:param user_id: Filter worklists by the users with permissions.
|
||||
:param hide_lanes: If true, don't return worklists which are lanes in
|
||||
a board.
|
||||
:param sort_field: The name of the field to sort on.
|
||||
@ -179,14 +239,19 @@ class WorklistsController(rest.RestController):
|
||||
project_id=project_id,
|
||||
archived=archived,
|
||||
board_id=board_id,
|
||||
user_id=user_id,
|
||||
sort_field=sort_field,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
user_id = request.current_user_id
|
||||
visible_worklists = [wmodels.Worklist.from_db_model(w)
|
||||
for w in worklists
|
||||
if w.archived == archived
|
||||
and visible(w, user_id, hide_lanes)]
|
||||
visible_worklists = []
|
||||
for worklist in worklists:
|
||||
if (visible(worklist, user_id, hide_lanes) and
|
||||
worklist.archived == archived):
|
||||
worklist_model = wmodels.Worklist.from_db_model(worklist)
|
||||
worklist_model.owners = worklists_api.get_owners(worklist.id)
|
||||
worklist_model.users = worklists_api.get_users(worklist.id)
|
||||
visible_worklists.append(worklist_model)
|
||||
|
||||
# Apply the query response headers
|
||||
response.headers['X-Total'] = str(len(visible_worklists))
|
||||
@ -208,9 +273,28 @@ class WorklistsController(rest.RestController):
|
||||
if worklist.creator_id and worklist.creator_id != user_id:
|
||||
abort(400, _("You can't select the creator of a worklist."))
|
||||
worklist_dict.update({"creator_id": user_id})
|
||||
owners = worklist_dict.pop('owners')
|
||||
users = worklist_dict.pop('users')
|
||||
if not owners:
|
||||
owners = [user_id]
|
||||
if not users:
|
||||
users = []
|
||||
|
||||
created_worklist = worklists_api.create(worklist_dict)
|
||||
|
||||
edit_permission = {
|
||||
'name': 'edit_worklist_%d' % created_worklist.id,
|
||||
'codename': 'edit_worklist',
|
||||
'users': owners
|
||||
}
|
||||
move_permission = {
|
||||
'name': 'move_items_%d' % created_worklist.id,
|
||||
'codename': 'move_items',
|
||||
'users': users
|
||||
}
|
||||
worklists_api.create_permission(created_worklist.id, edit_permission)
|
||||
worklists_api.create_permission(created_worklist.id, move_permission)
|
||||
|
||||
return wmodels.Worklist.from_db_model(created_worklist)
|
||||
|
||||
@decorators.db_exceptions
|
||||
@ -230,7 +314,14 @@ class WorklistsController(rest.RestController):
|
||||
updated_worklist = worklists_api.update(
|
||||
id, worklist.as_dict(omit_unset=True))
|
||||
|
||||
return wmodels.Worklist.from_db_model(updated_worklist)
|
||||
if visible(updated_worklist, user_id):
|
||||
worklist_model = wmodels.Worklist.from_db_model(updated_worklist)
|
||||
worklist_model.owners = worklists_api.get_owners(
|
||||
updated_worklist.id)
|
||||
worklist_model.users = worklists_api.get_users(updated_worklist.id)
|
||||
return worklist_model
|
||||
else:
|
||||
raise exc.NotFound(_("Worklist %s not found"))
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.authenticated)
|
||||
@ -246,8 +337,7 @@ class WorklistsController(rest.RestController):
|
||||
if not editable(worklist, user_id):
|
||||
raise exc.NotFound(_("Worklist %s not found") % worklist_id)
|
||||
|
||||
worklist_dict = wmodels.Worklist.from_db_model(worklist).as_dict()
|
||||
worklist_dict.update({"archived": True})
|
||||
worklists_api.update(worklist_id, worklist_dict)
|
||||
worklists_api.update(worklist_id, {"archived": True})
|
||||
|
||||
items = ItemsSubcontroller()
|
||||
permissions = PermissionsController()
|
||||
|
@ -18,6 +18,7 @@ from wsme.exc import ClientSideError
|
||||
|
||||
from storyboard.common import exception as exc
|
||||
from storyboard.db.api import base as api_base
|
||||
from storyboard.db.api import users as users_api
|
||||
from storyboard.db import models
|
||||
from storyboard.openstack.common.gettextutils import _ # noqa
|
||||
|
||||
@ -35,8 +36,17 @@ def get(id):
|
||||
return _board_get(id)
|
||||
|
||||
|
||||
def get_all(title=None, creator_id=None, project_id=None,
|
||||
def get_all(title=None, creator_id=None, user_id=None, project_id=None,
|
||||
sort_field=None, sort_dir=None, **kwargs):
|
||||
if user_id is not None:
|
||||
user = users_api.user_get(user_id)
|
||||
boards = []
|
||||
for board in get_all():
|
||||
if any(permission in board.permissions
|
||||
for permission in user.permissions):
|
||||
boards.append(board)
|
||||
return boards
|
||||
|
||||
return api_base.entity_get_all(models.Board,
|
||||
title=title,
|
||||
creator_id=creator_id,
|
||||
@ -82,3 +92,62 @@ def update_lane(board_id, lane, new_lane):
|
||||
raise ClientSideError(_("A lane must have a worklist_id."))
|
||||
|
||||
api_base.entity_update(models.BoardWorklist, lane.id, new_lane)
|
||||
|
||||
|
||||
def get_from_lane(worklist):
|
||||
for board in get_all():
|
||||
if worklist.id in [lane.list_id for lane in board.lanes]:
|
||||
return board
|
||||
|
||||
|
||||
def get_owners(board_id):
|
||||
board = _board_get(board_id)
|
||||
for permission in board.permissions:
|
||||
if permission.codename == 'edit_board':
|
||||
return [user.id for user in permission.users]
|
||||
|
||||
|
||||
def get_users(board_id):
|
||||
board = _board_get(board_id)
|
||||
for permission in board.permissions:
|
||||
if permission.codename == 'move_cards':
|
||||
return [user.id for user in permission.users]
|
||||
|
||||
|
||||
def get_permissions(board_id, user_id):
|
||||
board = _board_get(board_id)
|
||||
user = users_api.user_get(user_id)
|
||||
if user is not None:
|
||||
return [permission.codename for permission in board.permissions
|
||||
if permission in user.permissions]
|
||||
return []
|
||||
|
||||
|
||||
def create_permission(board_id, permission_dict, session=None):
|
||||
board = _board_get(board_id, session=session)
|
||||
users = permission_dict.pop('users')
|
||||
permission = api_base.entity_create(
|
||||
models.Permission, permission_dict, session=session)
|
||||
board.permissions.append(permission)
|
||||
for user_id in users:
|
||||
user = users_api.user_get(user_id, session=session)
|
||||
user.permissions.append(permission)
|
||||
return permission
|
||||
|
||||
|
||||
def update_permission(board_id, permission_dict):
|
||||
board = _board_get(board_id)
|
||||
id = None
|
||||
for permission in board.permissions:
|
||||
if permission.codename == permission_dict['codename']:
|
||||
id = permission.id
|
||||
users = permission_dict.pop('users')
|
||||
permission_dict['users'] = []
|
||||
for user_id in users:
|
||||
user = users_api.user_get(user_id)
|
||||
permission_dict['users'].append(user)
|
||||
|
||||
if id is None:
|
||||
raise ClientSideError(_("Permission %s does not exist")
|
||||
% permission_dict['codename'])
|
||||
return api_base.entity_update(models.Permission, id, permission_dict)
|
||||
|
@ -18,9 +18,10 @@ from storyboard.db import models
|
||||
from storyboard.plugin.user_preferences import PREFERENCE_DEFAULTS
|
||||
|
||||
|
||||
def user_get(user_id, filter_non_public=False):
|
||||
def user_get(user_id, filter_non_public=False, session=None):
|
||||
entity = api_base.entity_get(models.User, user_id,
|
||||
filter_non_public=filter_non_public)
|
||||
filter_non_public=filter_non_public,
|
||||
session=session)
|
||||
|
||||
return entity
|
||||
|
||||
|
@ -21,6 +21,7 @@ from storyboard.db.api import base as api_base
|
||||
from storyboard.db.api import boards
|
||||
from storyboard.db.api import stories
|
||||
from storyboard.db.api import tasks
|
||||
from storyboard.db.api import users as users_api
|
||||
from storyboard.db import models
|
||||
from storyboard.openstack.common.gettextutils import _ # noqa
|
||||
|
||||
@ -38,9 +39,17 @@ def get(worklist_id):
|
||||
return _worklist_get(worklist_id)
|
||||
|
||||
|
||||
def get_all(title=None, creator_id=None, project_id=None,
|
||||
board_id=None, sort_field=None, sort_dir=None,
|
||||
**kwargs):
|
||||
def get_all(title=None, creator_id=None, project_id=None, board_id=None,
|
||||
user_id=None, sort_field=None, sort_dir=None, **kwargs):
|
||||
if user_id is not None:
|
||||
user = users_api.user_get(user_id)
|
||||
worklists = []
|
||||
for worklist in get_all():
|
||||
if any(permission in worklist.permissions
|
||||
for permission in user.permissions):
|
||||
worklists.append(worklist)
|
||||
return worklists
|
||||
|
||||
if board_id is not None:
|
||||
board = boards.get(board_id)
|
||||
return [lane.worklist for lane in board.lanes]
|
||||
@ -168,3 +177,56 @@ def is_lane(worklist):
|
||||
if lanes:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_owners(worklist_id):
|
||||
worklist = _worklist_get(worklist_id)
|
||||
for permission in worklist.permissions:
|
||||
if permission.codename == 'edit_worklist':
|
||||
return [user.id for user in permission.users]
|
||||
|
||||
|
||||
def get_users(worklist_id):
|
||||
worklist = _worklist_get(worklist_id)
|
||||
for permission in worklist.permissions:
|
||||
if permission.codename == 'move_items':
|
||||
return [user.id for user in permission.users]
|
||||
|
||||
|
||||
def get_permissions(worklist_id, user_id):
|
||||
worklist = _worklist_get(worklist_id)
|
||||
user = users_api.user_get(user_id)
|
||||
if user is not None:
|
||||
return [permission.codename for permission in worklist.permissions
|
||||
if permission in user.permissions]
|
||||
return []
|
||||
|
||||
|
||||
def create_permission(worklist_id, permission_dict, session=None):
|
||||
worklist = _worklist_get(worklist_id, session=session)
|
||||
users = permission_dict.pop('users')
|
||||
permission = api_base.entity_create(
|
||||
models.Permission, permission_dict, session=session)
|
||||
worklist.permissions.append(permission)
|
||||
for user_id in users:
|
||||
user = users_api.user_get(user_id, session=session)
|
||||
user.permissions.append(permission)
|
||||
return permission
|
||||
|
||||
|
||||
def update_permission(worklist_id, permission_dict):
|
||||
worklist = _worklist_get(worklist_id)
|
||||
id = None
|
||||
for permission in worklist.permissions:
|
||||
if permission.codename == permission_dict['codename']:
|
||||
id = permission.id
|
||||
users = permission_dict.pop('users')
|
||||
permission_dict['users'] = []
|
||||
for user_id in users:
|
||||
user = users_api.user_get(user_id)
|
||||
permission_dict['users'].append(user)
|
||||
|
||||
if id is None:
|
||||
raise ClientSideError(_("Permission %s does not exist")
|
||||
% permission_dict['codename'])
|
||||
return api_base.entity_update(models.Permission, id, permission_dict)
|
||||
|
@ -0,0 +1,118 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Add detailed permissions to boards and worklists
|
||||
|
||||
Revision ID: 050
|
||||
Revises: 049
|
||||
Create Date: 2015-10-09 10:25:47.338906
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '050'
|
||||
down_revision = '049'
|
||||
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
from storyboard.db.api import base as api_base
|
||||
from storyboard.db.api import boards
|
||||
from storyboard.db.api import worklists
|
||||
from storyboard.db import models
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
op.create_table('board_permissions',
|
||||
sa.Column('board_id', sa.Integer(), nullable=True),
|
||||
sa.Column('permission_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['board_id'], ['boards.id'], ),
|
||||
sa.ForeignKeyConstraint(['permission_id'], ['permissions.id'], )
|
||||
)
|
||||
op.create_table('worklist_permissions',
|
||||
sa.Column('worklist_id', sa.Integer(), nullable=True),
|
||||
sa.Column('permission_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['permission_id'], ['permissions.id'], ),
|
||||
sa.ForeignKeyConstraint(['worklist_id'], ['worklists.id'], )
|
||||
)
|
||||
|
||||
session = api_base.get_session(in_request=False)
|
||||
for board in boards.get_all(session=session):
|
||||
edit_permission = {
|
||||
'name': 'edit_board_%d' % board.id,
|
||||
'codename': 'edit_board',
|
||||
'users': [board.creator_id]
|
||||
}
|
||||
move_permission = {
|
||||
'name': 'move_cards_%d' % board.id,
|
||||
'codename': 'move_cards',
|
||||
'users': []
|
||||
}
|
||||
print('Creating permissions for Board with id: %d' % board.id)
|
||||
boards.create_permission(board.id, edit_permission, session=session)
|
||||
boards.create_permission(board.id, move_permission, session=session)
|
||||
|
||||
for worklist in worklists.get_all(session=session):
|
||||
edit_permission = {
|
||||
'name': 'edit_worklist_%d' % worklist.id,
|
||||
'codename': 'edit_worklist',
|
||||
'users': [worklist.creator_id]
|
||||
}
|
||||
move_permission = {
|
||||
'name': 'move_items_%d' % worklist.id,
|
||||
'codename': 'move_items',
|
||||
'users': []
|
||||
}
|
||||
print('Creating permissions for Worklist with id: %d' % worklist.id)
|
||||
worklists.create_permission(
|
||||
worklist.id, edit_permission, session=session)
|
||||
worklists.create_permission(
|
||||
worklist.id, move_permission, session=session)
|
||||
session.flush()
|
||||
|
||||
op.drop_constraint(u'boards_ibfk_2', 'boards', type_='foreignkey')
|
||||
op.drop_column(u'boards', 'permission_id')
|
||||
op.drop_constraint(u'worklists_ibfk_2', 'worklists', type_='foreignkey')
|
||||
op.drop_column(u'worklists', 'permission_id')
|
||||
|
||||
|
||||
def downgrade(active_plugins=None, options=None):
|
||||
op.add_column(u'worklists', sa.Column('permission_id',
|
||||
mysql.INTEGER(display_width=11), autoincrement=False,
|
||||
nullable=True))
|
||||
op.create_foreign_key(u'worklists_ibfk_2', 'worklists', 'permissions',
|
||||
['permission_id'], ['id'])
|
||||
op.add_column(u'boards', sa.Column('permission_id',
|
||||
mysql.INTEGER(display_width=11), autoincrement=False,
|
||||
nullable=True))
|
||||
op.create_foreign_key(u'boards_ibfk_2', 'boards', 'permissions',
|
||||
['permission_id'], ['id'])
|
||||
|
||||
session = api_base.get_session(in_request=False)
|
||||
to_delete = []
|
||||
for board in boards.get_all(session=session):
|
||||
for permission in board.permissions:
|
||||
to_delete.append(permission)
|
||||
|
||||
for worklist in worklists.get_all(session=session):
|
||||
for permission in worklist.permissions:
|
||||
to_delete.append(permission)
|
||||
|
||||
op.drop_table('worklist_permissions')
|
||||
op.drop_table('board_permissions')
|
||||
|
||||
for permission in to_delete:
|
||||
api_base.entity_hard_delete(
|
||||
models.Permission, permission.id, session=session)
|
@ -150,7 +150,8 @@ class User(FullText, ModelBuilder, Base):
|
||||
is_superuser = Column(Boolean, default=False)
|
||||
last_login = Column(UTCDateTime)
|
||||
teams = relationship("Team", secondary="team_membership")
|
||||
permissions = relationship("Permission", secondary="user_permissions")
|
||||
permissions = relationship(
|
||||
"Permission", secondary="user_permissions", backref="users")
|
||||
enable_login = Column(Boolean, default=True)
|
||||
|
||||
preferences = relationship("UserPreference")
|
||||
@ -544,12 +545,12 @@ class Worklist(FullText, ModelBuilder, Base):
|
||||
title = Column(Unicode(CommonLength.top_middle_length), nullable=True)
|
||||
creator_id = Column(Integer, ForeignKey('users.id'))
|
||||
project_id = Column(Integer, ForeignKey('projects.id'))
|
||||
permission_id = Column(Integer, ForeignKey('permissions.id'))
|
||||
private = Column(Boolean, default=False)
|
||||
archived = Column(Boolean, default=False)
|
||||
automatic = Column(Boolean, default=False)
|
||||
items = relationship(WorklistItem)
|
||||
criteria = relationship(WorklistCriteria)
|
||||
permissions = relationship("Permission", secondary="worklist_permissions")
|
||||
|
||||
_public_fields = ["id", "title", "creator_id", "project_id",
|
||||
"permission_id", "private", "archived", "automatic"]
|
||||
@ -574,10 +575,22 @@ class Board(FullText, ModelBuilder, Base):
|
||||
description = Column(UnicodeText(), nullable=True)
|
||||
creator_id = Column(Integer, ForeignKey('users.id'))
|
||||
project_id = Column(Integer, ForeignKey('projects.id'))
|
||||
permission_id = Column(Integer, ForeignKey('permissions.id'))
|
||||
private = Column(Boolean, default=False)
|
||||
archived = Column(Boolean, default=False)
|
||||
lanes = relationship(BoardWorklist)
|
||||
permissions = relationship("Permission", secondary="board_permissions")
|
||||
|
||||
_public_fields = ["id", "title", "description", "creator_id",
|
||||
"project_id", "permission_id", "private", "archived"]
|
||||
|
||||
board_permissions = Table(
|
||||
'board_permissions', Base.metadata,
|
||||
Column('board_id', Integer, ForeignKey('boards.id')),
|
||||
Column('permission_id', Integer, ForeignKey('permissions.id')),
|
||||
)
|
||||
|
||||
worklist_permissions = Table(
|
||||
'worklist_permissions', Base.metadata,
|
||||
Column('worklist_id', Integer, ForeignKey('worklists.id')),
|
||||
Column('permission_id', Integer, ForeignKey('permissions.id')),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user