Compare commits

...

8 Commits

Author SHA1 Message Date
Mojib Wali
9d2ad34601 v0.4.1 2021-01-18 10:54:09 +01:00
Mojib Wali
1eb836aec8 permission: restriction to only open access 2021-01-18 10:28:34 +01:00
Mojib Wali
42e7a332d0 v0.4.0 2021-01-05 13:27:16 +01:00
Mojib Wali
5dd1bf0602 refactor(config): #41
* adding domain name for the production
* disable RDM records permission override
2021-01-05 13:24:56 +01:00
Mojib Wali
956a9eea76 feature(permission): AuthenticatedUser Generator #39 2021-01-05 13:23:11 +01:00
mb
4310ca8755 feature: adding domain to allowed host
refactor: disabled RDM permission override

This closes #40
2021-01-05 11:03:46 +01:00
Mojib Wali
ce97c5378c feature(permissions): RecordIp generator #36 2021-01-05 09:44:27 +01:00
Mojib Wali
e2b1c59c5d feature(permission): split the base from rdm permissions
* updated rdm permission policy
* ci: updating test commands to new 1.4.41
2020-12-21 14:54:05 +01:00
12 changed files with 335 additions and 37 deletions

View File

@@ -43,3 +43,6 @@ recursive-include invenio_config_tugraz *.json
recursive-include invenio_config_tugraz *.key recursive-include invenio_config_tugraz *.key
recursive-include invenio_config_tugraz *.xml recursive-include invenio_config_tugraz *.xml
recursive-include invenio_config_tugraz *.gitkeep recursive-include invenio_config_tugraz *.gitkeep
# added by check-manifest
recursive-include invenio_config_tugraz *.csv

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020 Mojib Wali. # Copyright (C) 2020 Graz University of Technology.
# #
# invenio-config-tugraz is free software; you can redistribute it and/or # invenio-config-tugraz is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more # modify it under the terms of the MIT License; see LICENSE file for more
@@ -46,7 +46,7 @@ Using Custom Generator for a policy:
RECORDS_PERMISSIONS_RECORD_POLICY = TUGRAZPermissionPolicy RECORDS_PERMISSIONS_RECORD_POLICY = TUGRAZPermissionPolicy
Permissions for Invenio (RDM) Records. Permissions for Invenio records.
""" """
from invenio_records_permissions.generators import ( from invenio_records_permissions.generators import (

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020 Mojib Wali. # Copyright (C) 2020 Graz University of Technology.
# #
# invenio-config-tugraz is free software; you can redistribute it and/or # invenio-config-tugraz is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more # modify it under the terms of the MIT License; see LICENSE file for more
@@ -8,6 +8,8 @@
"""invenio module that adds tugraz configs.""" """invenio module that adds tugraz configs."""
from os.path import abspath, dirname, join
from flask_babelex import gettext as _ from flask_babelex import gettext as _
INVENIO_CONFIG_TUGRAZ_SHIBBOLETH = True INVENIO_CONFIG_TUGRAZ_SHIBBOLETH = True
@@ -37,6 +39,7 @@ APP_ALLOWED_HOSTS = [
"127.0.0.1", "127.0.0.1",
"invenio-dev01.tugraz.at", "invenio-dev01.tugraz.at",
"invenio-test.tugraz.at", "invenio-test.tugraz.at",
"repository.tugraz.at"
] ]
"""Allowed Hosts""" """Allowed Hosts"""
@@ -193,8 +196,28 @@ RECAPTCHA_PRIVATE_KEY = None
# See: # See:
# https://invenio-records-permissions.readthedocs.io/en/latest/configuration.html # https://invenio-records-permissions.readthedocs.io/en/latest/configuration.html
# #
# Uncomment these to enable overriden # Uncomment these to enable overriding Base permissions - (NOT RECOMMANDED)
# RECORDS_PERMISSIONS_RECORD_POLICY = ( # RECORDS_PERMISSIONS_RECORD_POLICY = (
# 'invenio_config_tugraz.permissions.TUGRAZPermissionPolicy' # 'invenio_config_tugraz.base_permissions.TUGRAZPermissionPolicy'
# )
#
# Uncomment these to enable overriding RDM permissions
# RDM_RECORDS_BIBLIOGRAPHIC_SERVICE_CONFIG = (
# 'invenio_config_tugraz.rdm_permissions.TUGRAZBibliographicRecordServiceConfig'
# ) # )
"""Access control configuration for records.""" """Access control configuration for records."""
# invenio-rdm-records
# =======
# See:
# https://invenio-rdm-records.readthedocs.io/en/latest/configuration.html
#
# Custom Access Right
RDM_RECORDS_CUSTOM_VOCABULARIES = {
'access_right': {
'path': join(
dirname(abspath(__file__)),
'restrictions', 'access_right', 'access_right_limit.csv'
)
}
}

View File

@@ -153,27 +153,32 @@ The succinct encoding of the permissions for your instance gives you
from elasticsearch_dsl.query import Q from elasticsearch_dsl.query import Q
from flask import current_app, request from flask import current_app, request
from invenio_access.permissions import any_user, authenticated_user, superuser_access
from invenio_records_permissions.generators import Generator from invenio_records_permissions.generators import Generator
class RecordIp(Generator): class RecordIp(Generator):
"""Allowed any user with accessing with the IP.""" """Allowed any user with accessing with the IP."""
# TODO: Implement def needs(self, record=None, **kwargs):
def needs(self, **kwargs): """Enabling Needs, Set of Needs granting permission."""
"""Enabling Needs, Set of Needs granting permission. if record is None:
return []
If ANY of the Needs are matched, permission is granted. # check if singleip is in the records restriction
is_single_ip = record.get("access", {}).get("access_right") == "singleip"
.. note:: # check if the user ip is on list
visible = self.check_permission()
``_load_permissions()`` method from `Permission if not is_single_ip:
<https://invenio-access.readthedocs.io/en/latest/api.html # if record does not have singleip - return any_user
#invenio_access.permissions.Permission>`_ adds by default the return [any_user]
``superuser_access`` Need (if tied to a User or Role) for us. # if record has singleip, then check the ip of user - if ip user is on list - return any_user
It also expands ActionNeeds into the Users/Roles that elif visible:
provide them. return [any_user]
""" else:
# non of the above - return empty
return [] return []
def excludes(self, **kwargs): def excludes(self, **kwargs):
@@ -196,19 +201,40 @@ class RecordIp(Generator):
""" """
return [] return []
def query_filter(self, **kwargs): def query_filter(self, *args, **kwargs):
"""Elasticsearch filters, List of ElasticSearch query filters. """Filters for singleip records."""
# check if the user ip is on list
visible = self.check_permission()
These filters consist of additive queries mapping to what the current if not visible:
user should be able to retrieve via search. # If user ip is not on the list, and If the record contains 'singleip' will not be seen
""" return ~Q("match", **{"access.access_right": "singleip"})
# Lists all records
return Q("match_all") return Q("match_all")
def check_permission(self): def check_permission(self):
"""Check for User IP address in config variable.""" """Check for User IP address in config variable."""
# Get user IP # Get user IP
user_ip = request.remote_addr # pragma: no cover user_ip = request.remote_addr
# Checks if the user IP is among single IPs # Checks if the user IP is among single IPs
if user_ip in current_app.config["INVENIO_CONFIG_TUGRAZ_SINGLE_IP"]: # pragma: no cover if user_ip in current_app.config["INVENIO_CONFIG_TUGRAZ_SINGLE_IP"]:
return True # pragma: no cover return True
return False # pragma: no cover return False
class AuthenticatedUser(Generator):
"""Allows authenticated users."""
def __init__(self):
"""Constructor."""
super(AuthenticatedUser, self).__init__()
def needs(self, **kwargs):
"""Enabling Needs."""
return [authenticated_user]
def query_filter(self, **kwargs):
"""Filters for current identity as super user."""
# TODO: Implement with new permissions metadata
return []

View File

@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 Graz University of Technology.
#
# invenio-config-tugraz is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
# details.
"""
Records permission policies.
Default policies for records:
.. code-block:: python
# Read access given to everyone.
can_search = [AnyUser()]
# Create action given to no one (Not even superusers) bc Deposits should
# be used.
can_create = [Disable()]
# Read access given to everyone if public record/files and owners always.
can_read = [AnyUserIfPublic(), RecordOwners()]
# Update access given to record owners.
can_update = [RecordOwners()]
# Delete access given to admins only.
can_delete = [Admin()]
# Associated files permissions (which are really bucket permissions)
can_read_files = [AnyUserIfPublic(), RecordOwners()]
can_update_files = [RecordOwners()]
How to override default policies for rdm-records.
Using Custom Generator for a policy:
.. code-block:: python
from invenio_rdm_records.services import (
BibliographicRecordServiceConfig,
RDMRecordPermissionPolicy,
)
from invenio_config_tugraz.generators import RecordIp
class TUGRAZPermissionPolicy(RDMRecordPermissionPolicy):
# Create access given to SuperUser only.
can_create = [SuperUser()]
RDM_RECORDS_BIBLIOGRAPHIC_SERVICE_CONFIG = TUGRAZBibliographicRecordServiceConfig
Permissions for Invenio (RDM) Records.
"""
from invenio_rdm_records.services import (
BibliographicRecordServiceConfig,
RDMRecordPermissionPolicy,
)
from invenio_records_permissions.generators import (
Admin,
AnyUser,
RecordOwners,
SuperUser,
)
from .generators import AuthenticatedUser, RecordIp
class TUGRAZPermissionPolicy(RDMRecordPermissionPolicy):
"""Access control configuration for rdm records.
This overrides the origin:
https://github.com/inveniosoftware/invenio-rdm-records/blob/master/invenio_rdm_records/services/permissions.py.
"""
# Read access given to:
# TODO:
# AnyUserIfPublic : grant access if record is public
# RecordIp: grant access for single_ip
# RecordOwners: owner of records, enable once the deposit is allowed only for loged-in users.
# CURRENT:
# RecordIp: grant access for single_ip
can_read = [RecordIp()] # RecordOwners()
# Search access given to:
# AnyUser : grant access anyUser
# RecordIp: grant access for single_ip
can_search = [AnyUser(), RecordIp()]
# Update access given to record owners.
can_update = [RecordOwners()]
# Delete access given to admins only.
can_delete = [Admin()]
# Create action given to AuthenticatedUser
# UI - if user is loged in
# API - if user has Access token (Bearer API-TOKEN)
can_create = [AuthenticatedUser()]
# Associated files permissions (which are really bucket permissions)
# can_read_files = [AnyUserIfPublic(), RecordOwners()]
# can_update_files = [RecordOwners()]
class TUGRAZBibliographicRecordServiceConfig(BibliographicRecordServiceConfig):
"""Overriding BibliographicRecordServiceConfig."""
permission_policy_cls = TUGRAZPermissionPolicy

View File

@@ -0,0 +1,6 @@
access_right,access_right_name,icon,notes
open, Open Access, lock open
embargoed, Embargoed, ban
restricted, Restricted, key
closed, Private, lock
singleip, Single Ip, lock
1 access_right,access_right_name,icon,notes
2 open, Open Access, lock open
3 embargoed, Embargoed, ban
4 restricted, Restricted, key
5 closed, Private, lock
6 singleip, Single Ip, lock

View File

@@ -0,0 +1,2 @@
access_right,access_right_name,icon,notes
open, Open Access, lock open
1 access_right,access_right_name,icon,notes
2 open, Open Access, lock open

View File

@@ -12,4 +12,4 @@ This file is imported by ``invenio_config_tugraz.__init__``,
and parsed by ``setup.py``. and parsed by ``setup.py``.
""" """
__version__ = "0.3.0" __version__ = "0.4.1"

View File

@@ -1,16 +1,33 @@
#!/usr/bin/env sh #!/usr/bin/env bash
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020 Mojib Wali. # Copyright (C) 2019-2020 CERN.
# Copyright (C) 2019-2020 Northwestern University.
# Copyright (C) 2020 Graz University of Technology.
# #
# invenio-config-tugraz is free software; you can redistribute it and/or # invenio-config-tugraz is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more # modify it under the terms of the MIT License; see LICENSE file for more
# details. # details.
# Quit on errors
set -o errexit
# Quit on unbound symbols
set -o nounset
# Always bring down docker services
function cleanup() {
eval "$(docker-services-cli down --env)"
}
trap cleanup EXIT
python -m check_manifest --ignore ".*-requirements.txt" python -m check_manifest --ignore ".*-requirements.txt"
python -m sphinx.cmd.build -qnNW docs docs/_build/html python -m sphinx.cmd.build -qnNW docs docs/_build/html
docker-services-cli --verbose up es postgresql redis eval "$(docker-services-cli up --db ${DB:-postgresql} --search ${SEARCH:-elasticsearch} --cache ${CACHE:-redis} --env)"
python -m pytest python -m pytest
tests_exit_code=$? tests_exit_code=$?
docker-services-cli down python -m sphinx.cmd.build -qnNW -b doctest docs docs/_build/doctest
exit "$tests_exit_code" exit "$tests_exit_code"

View File

@@ -20,11 +20,12 @@ tests_require = [
"SQLAlchemy-Utils>=0.33.1,<0.36", "SQLAlchemy-Utils>=0.33.1,<0.36",
"invenio-rdm-records~=0.20.8", "invenio-rdm-records~=0.20.8",
"invenio-search[elasticsearch7]>=1.4.0", "invenio-search[elasticsearch7]>=1.4.0",
"psycopg2-binary>=2.8.6",
] ]
extras_require = { extras_require = {
"docs": [ "docs": [
"Sphinx>=1.5.1", "Sphinx>=3",
], ],
"tests": tests_require, "tests": tests_require,
} }

View File

@@ -20,6 +20,7 @@ import pytest
from flask import Flask from flask import Flask
from flask_babelex import Babel from flask_babelex import Babel
from invenio_db import InvenioDB, db from invenio_db import InvenioDB, db
from sqlalchemy_utils.functions import create_database, database_exists, drop_database
from invenio_config_tugraz import InvenioConfigTugraz from invenio_config_tugraz import InvenioConfigTugraz
@@ -70,3 +71,93 @@ def create_app(request):
app.test_request_context().push() app.test_request_context().push()
return app return app
@pytest.fixture(scope='function')
def open_record():
"""Open record data as dict coming from the external world."""
return {
"access": {
"metadata": False,
"files": False,
"owned_by": [1],
"access_right": "open"
},
"metadata": {
"publication_date": "2020-06-01",
"resource_type": {
"type": "image",
"subtype": "image-photo"
},
# Technically not required
"creators": [{
"name": "Troy Brown",
"type": "personal"
}, {
"name": "Phillip Lester",
"type": "personal",
"identifiers": {"orcid": "0000-0002-1825-0097"},
"affiliations": [{
"name": "Carter-Morris",
"identifiers": {"ror": "03yrm5c26"}
}]
}, {
"name": "Steven Williamson",
"type": "personal",
"identifiers": {"orcid": "0000-0002-1825-0097"},
"affiliations": [{
"name": "Ritter and Sons",
"identifiers": {"ror": "03yrm5c26"}
}, {
"name": "Montgomery, Bush and Madden",
"identifiers": {"ror": "03yrm5c26"}
}]
}],
"title": "A Romans story"
}
}
@pytest.fixture(scope='function')
def singleip_record():
"""Single Ip record data as dict coming from the external world."""
return {
"access": {
"metadata": False,
"files": False,
"owned_by": [1],
"access_right": "singleip"
},
"metadata": {
"publication_date": "2020-06-01",
"resource_type": {
"type": "image",
"subtype": "image-photo"
},
# Technically not required
"creators": [{
"name": "Troy Brown",
"type": "personal"
}, {
"name": "Phillip Lester",
"type": "personal",
"identifiers": {"orcid": "0000-0002-1825-0097"},
"affiliations": [{
"name": "Carter-Morris",
"identifiers": {"ror": "03yrm5c26"}
}]
}, {
"name": "Steven Williamson",
"type": "personal",
"identifiers": {"orcid": "0000-0002-1825-0097"},
"affiliations": [{
"name": "Ritter and Sons",
"identifiers": {"ror": "03yrm5c26"}
}, {
"name": "Montgomery, Bush and Madden",
"identifiers": {"ror": "03yrm5c26"}
}]
}],
"title": "A Romans story"
}
}

View File

@@ -8,13 +8,31 @@
"""Test Generators.""" """Test Generators."""
from invenio_config_tugraz.generators import RecordIp from invenio_access.permissions import any_user, authenticated_user
from invenio_config_tugraz.generators import AuthenticatedUser, RecordIp
def test_recordip(): def test_recordip(create_app, open_record, singleip_record):
"""Test Generator RecordIp.""" """Test Generator RecordIp."""
generator = RecordIp() generator = RecordIp()
open_record = open_record
singleiprec = singleip_record
assert generator.needs() == [] assert generator.needs(record=None) == []
assert generator.needs(record=open_record) == [any_user]
assert generator.needs(record=singleiprec) == []
assert generator.excludes(record=open_record) == []
assert generator.excludes(record=open_record) == []
assert generator.query_filter().to_dict() == {'bool': {'must_not': [{'match': {'access.access_right': 'singleip'}}]}}
def test_authenticateduser():
"""Test Generator AuthenticatedUser."""
generator = AuthenticatedUser()
assert generator.needs() == [authenticated_user]
assert generator.excludes() == [] assert generator.excludes() == []
assert generator.query_filter().to_dict() == {"match_all": {}} assert generator.query_filter() == []