Skip to content

Commit

Permalink
rest API: access restriction by organisation read, write, delete, update
Browse files Browse the repository at this point in the history
* NEW Restricts access to APIs for users of same organisation.

Signed-off-by: Aly Badr <aly.badr@rero.ch>
  • Loading branch information
Aly Badr authored and jma committed May 20, 2019
1 parent 022dfd3 commit 4ce2905
Show file tree
Hide file tree
Showing 19 changed files with 1,032 additions and 7 deletions.
67 changes: 64 additions & 3 deletions rero_ils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from datetime import timedelta
from functools import partial

from flask import request
from invenio_circulation.pidstore.pids import CIRCULATION_LOAN_FETCHER, \
CIRCULATION_LOAN_MINTER, CIRCULATION_LOAN_PID_TYPE
from invenio_circulation.search.api import LoansSearch
Expand All @@ -51,13 +52,21 @@
from rero_ils.modules.api import IlsRecordIndexer
from rero_ils.modules.loans.api import Loan

from .modules.circ_policies.api import CircPolicy
from .modules.documents.api import Document
from .modules.item_types.api import ItemType
from .modules.items.api import Item, ItemsIndexer
from .modules.libraries.api import Library
from .modules.loans.utils import can_be_requested, get_default_loan_duration, \
get_extension_params, is_item_available_for_checkout, \
loan_satisfy_circ_policies
from .modules.locations.api import Location
from .modules.organisations.api import Organisation
from .modules.patron_types.api import PatronType
from .modules.patrons.api import Patron
from .permissions import librarian_delete_permission_factory, \
librarian_permission_factory
librarian_permission_factory, organisation_access_factory, \
organisation_create_factory


def _(x):
Expand Down Expand Up @@ -365,11 +374,18 @@ def _(x):
),
},
list_route='/items/',
record_loaders={
'application/json': lambda: Item(request.get_json()),
},
record_class='rero_ils.modules.items.api:Item',
item_route='/items/<pid(item, record_class="rero_ils.modules.items.api:Item"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:organisation_search_factory',
read_permission_factory_imp=organisation_access_factory,
create_permission_factory_imp=organisation_create_factory,
update_permission_factory_imp=organisation_access_factory,
delete_permission_factory_imp=organisation_access_factory,
),
itty=dict(
pid_type='itty',
Expand All @@ -396,11 +412,18 @@ def _(x):
),
},
list_route='/item_types/',
record_loaders={
'application/json': lambda: ItemType(request.get_json()),
},
record_class='rero_ils.modules.item_types.api:ItemType',
item_route='/item_types/<pid(itty, record_class="rero_ils.modules.item_types.api:ItemType"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:organisation_search_factory',
read_permission_factory_imp=organisation_access_factory,
create_permission_factory_imp=organisation_create_factory,
update_permission_factory_imp=organisation_access_factory,
delete_permission_factory_imp=organisation_access_factory,
),
ptrn=dict(
pid_type='ptrn',
Expand All @@ -427,11 +450,18 @@ def _(x):
),
},
list_route='/patrons/',
record_loaders={
'application/json': lambda: Patron(request.get_json()),
},
record_class='rero_ils.modules.patrons.api:Patron',
item_route='/patrons/<pid(ptrn, record_class="rero_ils.modules.patrons.api:Patron"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:organisation_search_factory',
read_permission_factory_imp=organisation_access_factory,
create_permission_factory_imp=organisation_create_factory,
update_permission_factory_imp=organisation_access_factory,
delete_permission_factory_imp=organisation_access_factory,
),
ptty=dict(
pid_type='ptty',
Expand All @@ -458,11 +488,18 @@ def _(x):
),
},
list_route='/patron_types/',
record_loaders={
'application/json': lambda: PatronType(request.get_json()),
},
record_class='rero_ils.modules.patron_types.api:PatronType',
item_route='/patron_types/<pid(ptty, record_class="rero_ils.modules.patron_types.api:PatronType"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:organisation_search_factory',
read_permission_factory_imp=organisation_access_factory,
create_permission_factory_imp=organisation_create_factory,
update_permission_factory_imp=organisation_access_factory,
delete_permission_factory_imp=organisation_access_factory,
),
org=dict(
pid_type='org',
Expand All @@ -489,14 +526,18 @@ def _(x):
),
},
list_route='/organisations/',
record_loaders={
'application/json': lambda: Organisation(request.get_json()),
},
record_class='rero_ils.modules.organisations.api:Organisation',
item_route='/organisations/<pid(org, record_class="rero_ils.modules.organisations.api:Organisation"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:and_search_factory',
create_permission_factory_imp=deny_all,
update_permission_factory_imp=deny_all,
delete_permission_factory_imp=deny_all
delete_permission_factory_imp=deny_all,
read_permission_factory_imp=organisation_access_factory,
),
lib=dict(
pid_type='lib',
Expand All @@ -523,12 +564,18 @@ def _(x):
),
},
list_route='/libraries/',
record_loaders={
'application/json': lambda: Library(request.get_json()),
},
record_class='rero_ils.modules.libraries.api:Library',
item_route='/libraries/<pid(lib, record_class="rero_ils.modules.libraries.api:Library"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:organisation_search_factory',
# delete_permission_factory_imp=deny_all
read_permission_factory_imp=organisation_access_factory,
create_permission_factory_imp=organisation_create_factory,
update_permission_factory_imp=organisation_access_factory,
delete_permission_factory_imp=organisation_access_factory,
),
loc=dict(
pid_type='loc',
Expand All @@ -555,11 +602,18 @@ def _(x):
),
},
list_route='/locations/',
record_loaders={
'application/json': lambda: Location(request.get_json()),
},
record_class='rero_ils.modules.locations.api:Location',
item_route='/locations/<pid(loc, record_class="rero_ils.modules.locations.api:Location"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:organisation_search_factory',
read_permission_factory_imp=organisation_access_factory,
create_permission_factory_imp=organisation_create_factory,
update_permission_factory_imp=organisation_access_factory,
delete_permission_factory_imp=organisation_access_factory,
),
pers=dict(
pid_type='pers',
Expand Down Expand Up @@ -621,12 +675,19 @@ def _(x):
'rero_ils.modules.serializers' ':can_delete_json_v1_search'
),
},
record_loaders={
'application/json': lambda: CircPolicy(request.get_json()),
},
list_route='/circ_policies/',
record_class='rero_ils.modules.circ_policies.api:CircPolicy',
item_route='/circ_policies/<pid(cipo, record_class="rero_ils.modules.circ_policies.api:CircPolicy"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:organisation_search_factory',
read_permission_factory_imp=organisation_access_factory,
create_permission_factory_imp=organisation_create_factory,
update_permission_factory_imp=organisation_access_factory,
delete_permission_factory_imp=organisation_access_factory,
),
)

Expand Down
7 changes: 7 additions & 0 deletions rero_ils/modules/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,10 @@ def reasons_not_to_delete(self):
def can_delete(self):
"""Record can be deleted."""
return len(self.reasons_not_to_delete()) == 0

@property
def organisation_pid(self):
"""Get organisation pid for circulation policy."""
if self.get('organisation'):
return self.replace_refs()['organisation']['pid']
return None
6 changes: 6 additions & 0 deletions rero_ils/modules/items/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,3 +754,9 @@ def reasons_not_to_delete(self):
if links:
cannot_delete['links'] = links
return cannot_delete

@property
def organisation_pid(self):
"""Get organisation pid for item."""
library = Library.get_record_by_pid(self.library_pid)
return library.organisation_pid
10 changes: 10 additions & 0 deletions rero_ils/modules/loans/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ def is_active(self):
return True
return False

@property
def organisation_pid(self):
"""Get organisation pid for loan."""
from ..items.api import Item

if self.get('item_pid'):
item = Item.get_record_by_pid(self.get('item_pid'))
return item.organisation_pid
return None

def dumps_for_circulation(self):
"""Dumps for circulation."""
loan = self.replace_refs()
Expand Down
13 changes: 13 additions & 0 deletions rero_ils/modules/locations/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,16 @@ def reasons_not_to_delete(self):
if links:
cannot_delete['links'] = links
return cannot_delete

@property
def library_pid(self):
"""Get library pid for location."""
return self.replace_refs()['library']['pid']

@property
def organisation_pid(self):
"""Get organisation pid for location."""
from ..libraries.api import Library

library = Library.get_record_by_pid(self.library_pid)
return library.organisation_pid
5 changes: 5 additions & 0 deletions rero_ils/modules/organisations/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,8 @@ def reasons_not_to_delete(self):
if links:
cannot_delete['links'] = links
return cannot_delete

@property
def organisation_pid(self):
"""Get organisation pid ."""
return self.pid
22 changes: 22 additions & 0 deletions rero_ils/modules/patrons/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,25 @@ def get_organisation(self):
return Organisation.get_record_by_pid(
ptty.replace_refs()['organisation']['pid'])
return None

@property
def library_pid(self):
"""Shortcut for patron library pid."""
if self.get('library'):
library_pid = self.replace_refs().get('library').get('pid')
return library_pid
return None

@property
def organisation_pid(self):
"""Get organisation pid for patron."""
from ..patron_types.api import PatronType

if self.library_pid:
library = Library.get_record_by_pid(self.library_pid)
return library.organisation_pid

if self.patron_type_pid:
patron_type = PatronType.get_record_by_pid(self.patron_type_pid)
return patron_type.organisation_pid
return None
40 changes: 37 additions & 3 deletions rero_ils/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@
from invenio_admin.permissions import \
admin_permission_factory as default_admin_permission_factory

from .modules.patrons.api import Patron

request_item_permission = DynamicPermission(RoleNeed('patron'))


def login_and_librarian():
"""Librarian is logged in."""
if not current_user.is_authenticated:
abort(401)
if not librarian_permission.can():
abort(403)
if not librarian_permission.can():
abort(403)


librarian_permission = DynamicPermission(RoleNeed('librarian'))
Expand All @@ -62,7 +64,7 @@ def librarian_delete_permission_factory(record, *args, **kwargs):
"""User can delete record."""
if record.can_delete:
return librarian_permission
return abort(403)
abort(403)


def admin_permission_factory(admin_view):
Expand All @@ -83,3 +85,35 @@ def can(self):
'Admin & Monitoring']:
return FreeAccess()
return default_admin_permission_factory(admin_view)


def organisation_access_factory(record, *args, **kwargs):
"""User access only records of its organisation."""
def can(self):
if current_user.is_authenticated:
patron = Patron.get_patron_by_user(current_user)
if (
patron and 'librarian' in patron.get('roles') and
patron.organisation_pid == record.organisation_pid
):
return True
return False
return type('Check', (), {'can': can})()


def organisation_create_factory(record, *args, **kwargs):
"""User create only records of its organisation."""
from flask import current_app

def can(self):
if current_user.is_authenticated:
if not record:
return True
patron = Patron.get_patron_by_user(current_user)
if (
patron and 'librarian' in patron.get('roles') and
patron.organisation_pid == record.organisation_pid
):
return True
return False
return type('Check', (), {'can': can})()
Loading

0 comments on commit 4ce2905

Please sign in to comment.