Skip to content

Commit

Permalink
global: cleanup and integration with new resources approach [more]
Browse files Browse the repository at this point in the history
- remove factories
- remove deposit references
- adapt policies to use permission (identity) for filtering
  • Loading branch information
Pablo Panero committed Sep 16, 2020
1 parent 0e9c718 commit 828250d
Show file tree
Hide file tree
Showing 15 changed files with 48 additions and 390 deletions.
7 changes: 0 additions & 7 deletions docs/api.rst
Expand Up @@ -29,10 +29,3 @@ Policies

.. autoclass:: invenio_records_permissions.policies.records.RecordPermissionPolicy
:members:


Factories
---------

.. automodule:: invenio_records_permissions.factories.records
:members:
84 changes: 3 additions & 81 deletions invenio_records_permissions/__init__.py
Expand Up @@ -7,19 +7,14 @@
# and/or modify it under the terms of the MIT License; see LICENSE file for
# more details.

r"""Permission generators, policies and factories for Invenio records.
r"""Permission generators and policies for Invenio records.
Invenio-records-permissions provides a means to fully customize access control
for Invenio records. It does so by defining and providing three layers of
permission constructs that build on each other:
Generators, Policies and Factories. You can extend or override them for maximum
Generators and Policies. You can extend or override them for maximum
control. Thankfully we provide default ones that cover most cases.
Factories make invenio-records-permissions immediately compatible
with any Invenio module requiring permission factories (e.g.,
`invenio-records-rest <https://invenio-records-rest.readthedocs.io>`_ and
`invenio-files-rest <https://invenio-files-rest.readthedocs.io>`_ ).
Invenio-records-permissions conveniently structures (and relies on)
functionalities from
`invenio-access <https://invenio-access.readthedocs.io>`_ and
Expand Down Expand Up @@ -155,88 +150,15 @@ class ExampleRecordPermissionPolicy(BasePermissionPolicy):
- one central location where your permissions are defined
- exact control
- great flexibility by defining your own actions, generators and policies
In turn, to fully understand how Policies fit in an Invenio project, we have to
show where *they* are used. And *that* is in the Factories.
Factories
---------
Most authorization is enforced through permission factories in Invenio:
simple functions that return a `Permission
<https://invenio-access.readthedocs.io/en/latest/api.html
#invenio_access.permissions.Permission>`_ object. Thankfully, Policies are
just that kind of object.
Invenio-records-permissions provides you with pre-made configurable record
permission factories here:
:py:mod:`invenio_records_permissions.factories.records` . You can follow the
pattern there to create other factories you may need.
Pre-made factories
~~~~~~~~~~~~~~~~~~
By setting the following configuration in your instance:
.. code-block:: python
RECORDS_PERMISSIONS_RECORD_POLICY = (
'module.to.ExampleRecordPermissionPolicy'
)
RECORDS_REST_ENDPOINTS = {
"recid": {
# ...
# We only display key-value pairs relevant to this explanation
'read_permission_factory_imp': 'invenio_records_permissions.factories.record_read_permission_factory', # noqa
'list_permission_factory_imp': 'invenio_records_permissions.factories.record_search_permission_factory', # noqa
'create_permission_factory_imp': 'invenio_records_permissions.factories.record_create_permission_factory', # noqa
'update_permission_factory_imp': 'invenio_records_permissions.factories.record_update_permission_factory', # noqa
'delete_permission_factory_imp': 'invenio_records_permissions.factories.record_delete_permission_factory' # noqa
}
}
you will be using the pre-made factories that know to look for their associated
action in ``module.to.ExampleRecordPermissionPolicy``.
Custom factories
~~~~~~~~~~~~~~~~
To implement your own factories, create a factory with the required signature
and return an instance of your custom PermissionPolicy object with the
appropriate action. For example:
.. code-block:: python
def license_delete_permission_factory(license=None):
'''Delete permission factory for license records.'''
return LicensePermissionPolicy(action='delete', license=license)
With that, we covered all you need to know to fully specify access control in
your instance: combine and use permission Generators, Policies and Factories.
"""

from .ext import InvenioRecordsPermissions
from .factories import record_create_permission_factory, \
record_delete_permission_factory, record_read_permission_factory, \
record_search_permission_factory, record_update_permission_factory
from .policies import BasePermissionPolicy, DepositPermissionPolicy, \
RecordPermissionPolicy
from .policies import BasePermissionPolicy, RecordPermissionPolicy
from .version import __version__

__all__ = (
'__version__',
'BasePermissionPolicy',
'DepositPermissionPolicy',
'InvenioRecordsPermissions',
'RecordPermissionPolicy',
# TODO: Hide behind invenio_records_permissions.factories
# if isort PR is merged:
# https://github.com/inveniosoftware/cookiecutter-invenio-module/pull/129
'record_create_permission_factory',
'record_delete_permission_factory',
'record_search_permission_factory',
'record_read_permission_factory',
'record_update_permission_factory',
)
47 changes: 9 additions & 38 deletions invenio_records_permissions/api.py
@@ -1,52 +1,23 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 CERN.
# Copyright (C) 2019 Northwestern University.
# Copyright (C) 2019-2020 CERN.
# Copyright (C) 2019-2020 Northwestern University.
#
# Invenio App RDM is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
# Invenio-Records-Permissions is free software; you can redistribute it
# and/or modify it under the terms of the MIT License; see LICENSE file for
# more details.

"""Invenio Records Permissions API."""

from elasticsearch_dsl.query import Q
from flask import current_app
from invenio_search.api import DefaultFilter, RecordsSearch

from .factories import record_read_permission_factory


def rdm_records_filter():
"""Records filter."""
# TODO: Implement with new permissions metadata
try:
perm_factory = current_app.config["RECORDS_REST_ENDPOINTS"]["recid"][
"read_permission_factory_imp"
]() # noqa
except KeyError:
perm_factory = record_read_permission_factory
# FIXME: this might fail if factory returns None, meaning no "query_filter"
# was implemente in the generators. However, IfPublic should always be
# there.

filters = perm_factory.query_filters
if filters:
def records_permission_filter(permission):
"""Records permission filter."""
if permission:
qf = None
for f in filters:
for f in permission.query_filters:
qf = qf | f if qf else f
return qf
else:
return Q()


# TODO: Move this to invenio-rdm-records and
# * have it provide the permissions OR
# * rely on app's current_search for tests
class RecordsSearch(RecordsSearch):
"""Search class for RDM records."""

class Meta:
"""Default index and filter for frontpage search."""

index = "records"
doc_types = None
default_filter = DefaultFilter(rdm_records_filter)
6 changes: 3 additions & 3 deletions invenio_records_permissions/config.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 CERN.
# Copyright (C) 2019 Northwestern University.
# Copyright (C) 2019-2020 CERN.
# Copyright (C) 2019-2020 Northwestern University.
#
# Invenio-Records-Permissions is free software; you can redistribute it
# and/or modify it under the terms of the MIT License; see LICENSE file for
Expand All @@ -12,4 +12,4 @@
RECORDS_PERMISSIONS_RECORD_POLICY = (
'invenio_records_permissions.policies.RecordPermissionPolicy'
)
"""PermissionPolicy used by provided record permission factories."""
"""PermissionPolicy for records."""
14 changes: 0 additions & 14 deletions invenio_records_permissions/factories/__init__.py

This file was deleted.

37 changes: 0 additions & 37 deletions invenio_records_permissions/factories/deposits.py

This file was deleted.

44 changes: 0 additions & 44 deletions invenio_records_permissions/factories/records.py

This file was deleted.

23 changes: 10 additions & 13 deletions invenio_records_permissions/generators.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 CERN.
# Copyright (C) 2019 Northwestern University.
# Copyright (C) 2019-2020 CERN.
# Copyright (C) 2019-2020 Northwestern University.
#
# Invenio-Records-Permissions is free software; you can redistribute it
# and/or modify it under the terms of the MIT License; see LICENSE file for
Expand All @@ -15,7 +15,6 @@
from itertools import chain

from elasticsearch_dsl.query import Q
from flask import g
from flask_principal import ActionNeed, UserNeed
from invenio_access.permissions import any_user, superuser_access
from invenio_records.api import Record
Expand Down Expand Up @@ -71,7 +70,7 @@ def needs(self, **kwargs):
"""Enabling Needs."""
return [superuser_access]

def query_filter(self, record=None, **kwargs):
def query_filter(self, **kwargs):
"""Filters for current identity as super user."""
# TODO: Implement with new permissions metadata
return []
Expand Down Expand Up @@ -112,11 +111,9 @@ def needs(self, record=None, **kwargs):
"""Enabling Needs."""
return [UserNeed(owner) for owner in record.get('owners', [])]

def query_filter(self, record=None, **kwargs):
def query_filter(self, identity=None, **kwargs):
"""Filters for current identity as owner."""
# TODO: Implement with new permissions metadata
provides = g.identity.provides
for need in provides:
for need in identity.provides:
if need.method == 'id':
return Q('term', owners=need.value)
return []
Expand All @@ -128,19 +125,19 @@ class AnyUserIfPublic(Generator):
TODO: Revisit when dealing with files.
"""

def needs(self, record=None, **rest_over):
def needs(self, record=None, **kwargs):
"""Enabling Needs."""
is_restricted = (
record and
record.get('_access', {}).get('metadata_restricted', False)
)
return [any_user] if not is_restricted else []

def excludes(self, record=None, **rest_over):
def excludes(self, record=None, **kwargs):
"""Preventing Needs."""
return []

def query_filter(self, *args, **kwargs):
def query_filter(self, **kwargs):
"""Filters for non-restricted records."""
# TODO: Implement with new permissions metadata
return Q('term', **{"_access.metadata_restricted": False})
Expand Down Expand Up @@ -188,10 +185,10 @@ def needs(self, record=None, **kwargs):
# TODO: Implement other schemes
]

def query_filter(self, *args, **kwargs):
def query_filter(self, identity=None, **kwargs):
"""Search filter for the current user with this generator."""
id_need = next(
(need for need in g.identity.provides if need.method == 'id'),
(need for need in identity.provides if need.method == 'id'),
None
)

Expand Down
1 change: 0 additions & 1 deletion invenio_records_permissions/policies/__init__.py
Expand Up @@ -10,5 +10,4 @@
"""Invenio Records Permissions Policies."""

from .base import BasePermissionPolicy
from .deposits import DepositPermissionPolicy
from .records import RecordPermissionPolicy, get_record_permission_policy
3 changes: 0 additions & 3 deletions invenio_records_permissions/policies/base.py
Expand Up @@ -45,9 +45,6 @@ class BasePermissionPolicy(Permission):
If `can_<self.action>`
is not defined, no one is allowed (Disable()).
is an empty list, only Super Users are allowed (via NOTE above).
TODO: Recognize a PermissionPolicy class in other modules instead of
individual factory functions to lessen the configuration burden.
"""

can_search = []
Expand Down

0 comments on commit 828250d

Please sign in to comment.