Compare commits

..

27 Commits

Author SHA1 Message Date
Mojib Wali
975ac67a8a release; v0.2.1 2020-10-06 09:34:09 +02:00
mb
21d1a9d4c6 Merge branch 'master' of https://github.com/mb-wali/invenio-config-tugraz 2020-10-06 09:30:32 +02:00
mb
3d3354805d translation: extract messages and added required files for i18n, This closes #18 2020-10-06 09:29:52 +02:00
Mojib Wali
b514a38ed9 release: v0.2.0 2020-10-06 09:24:29 +02:00
mb
1556bc7f18 ci:use github actions to publish to pypi
Using github action - passwords are secured in secrets, and it seperated the publishing the package from testing the package. This closes #20
2020-10-06 09:21:16 +02:00
mb
6b175bacf8 test:migrate to latest pytest-invenio and docker-services-cli
This closes #19
2020-10-06 09:04:15 +02:00
mb
89ab18cf04 commented the overriden
uncomment to enable, also excluded some line of code from coverage.
2020-09-29 14:33:34 +02:00
mb-wali
595bf8ca48 Generators for permissions(WIP)
First draft Custom Generators for permissions(WIP)
2020-09-29 13:37:56 +02:00
mb
b4a2f2a36b get user remote_ip
* function to check user ip. * renamed the files.
2020-09-24 10:42:14 +02:00
mb
2f4df9167b seperate file for access control configuration
permissionsPolicy file will handle the access control configurations.
2020-09-23 14:22:02 +02:00
mb
2b71d1af73 Access control configuration
updated the dependencies & documentation for permissions based on new release on invenio-records-permission.
2020-09-22 13:27:50 +02:00
mb
9bd44f269b not relevant to permissions.
by default this variable should be set to True.
2020-09-18 11:52:31 +02:00
mb
6e3e8e24c8 generator documentations. 2020-08-26 13:21:12 +02:00
mb
8a5cef77f4 documentation:how to override the default policies for records
adding custom generator to the overriden policy.
2020-08-20 15:00:48 +02:00
mb
8b3b577d32 how to:implementing generator for RecordIp. 2020-08-20 14:47:30 +02:00
mb-wali
9cd3f33475 configs
extra information on config variables.
2020-08-17 17:47:43 +02:00
mb
24b29ff668 extra information on config variables. 2020-07-31 09:48:34 +02:00
mb
e431ff2eb6 updated tests files
with pytest version 6.0.0, these changes were required.
2020-07-30 11:42:06 +02:00
mb
6cd0fabf9e update config file 2020-07-27 15:52:24 +02:00
mb-wali
f543f1f08c v0.1.5 2020-07-27 11:58:58 +02:00
mb-wali
94c858f412 Update .travis.yml 2020-07-27 11:58:21 +02:00
mb-wali
33fc35272c v0.1.4 2020-07-27 11:47:26 +02:00
mb-wali
02db7a01c9 Update .travis.yml 2020-07-27 11:45:44 +02:00
mb-wali
c579052ade v0.1.3 2020-07-27 11:40:26 +02:00
mb-wali
7146e53fe3 Update .travis.yml 2020-07-27 11:38:16 +02:00
mb-wali
35186ed91f v0.1.2 2020-07-27 11:31:02 +02:00
mb-wali
32c6f1eefc Update .travis.yml 2020-07-27 11:29:53 +02:00
14 changed files with 470 additions and 31 deletions

26
.github/workflows/pypi-publish.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
on:
push:
tags:
- v*
jobs:
build-n-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel
- name: Build package
run: |
python setup.py compile_catalog sdist bdist_wheel
- name: pypi-publish
uses: pypa/gh-action-pypi-publish@v1.3.1
with:
user: __token__
password: ${{ secrets.pypi_password }}

View File

@@ -5,6 +5,9 @@
# 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.
branches:
except:
- /^v\d+\.\d+(\.\d+)?(\S*)?$/
notifications: notifications:
email: false email: false
@@ -48,17 +51,3 @@ script:
after_success: after_success:
- coveralls - coveralls
deploy:
skip_cleanup: true
skip_existing: true
provider: pypi
username: __token__
password:
secure: XJtAAKVZu/bhcS3/2VAYuHmx8QChSvKis22lhAhzArMF6g+T5NDsbrn2EppBbXE2wNqiwSiMAgsebu/9wxXIRIbPbwLqb4T9SF2j/zmnhpz9ORwrniawwsc+eKamZtpdSrEpCVlrSJeat9tGxV3jKy//4T50CHLRdQA/gGGlEfBEybTtorpEyQp08Y5xIbYrFFsidjMz++gQeHiWMJ7ZSWGewJ62exYNu/Hjr6h5hk6J7yC7FhFKtcDdvWrBfiuM6JkerCRxoXBEAQWki/D1tBO0N8w20wa9dw0rzx82T5Br1uqCC1kr68Sqt7jpXKp+nCs/ClHb4cFAnXtHJ3CZd+xRBE1BX2Akgj/YyP99Epw4UIJGuoZzqHCKjoF/Nj5toMiu8VqQeR2cLCuPbzubfIkLTYMJseClLsVIn8bh5fvucIBcvLYlceuW8XU0GFrh4kN/UJTkU5a2T8fdretWrbyrssjRislXFksYMOsJz1B70EGgRqZDkSm+t6otzXbkh0ZenjMRE/U2cYyOEv6zJ7PBQMfqKucTvdv8XrEjU8zLo33K034sVEJelLObO8tXher4ut8erAwZzrD1Up8+1/VqB/TvxPINbaET5ye4y+3Q8qqSQxcLnM3zUHVbEbOBMOvUoWyPfXzpEmo0I0TowTghvgncYQ6oLHr+5TiU/J0=
distributions: sdist bdist_wheel
on:
tags: true
python: "3.6"
repo: mb-wali/invenio-config-tugraz
condition: $DEPLOY = true

View File

@@ -9,6 +9,7 @@
"""invenio module that adds tugraz configs.""" """invenio module that adds tugraz configs."""
from .ext import invenioconfigtugraz from .ext import invenioconfigtugraz
from .generators import RecordIp
from .version import __version__ from .version import __version__
__all__ = ('__version__', 'invenioconfigtugraz') __all__ = ('__version__', 'invenioconfigtugraz', 'RecordIp')

View File

@@ -13,6 +13,20 @@ from flask_babelex import gettext as _
INVENIO_CONFIG_TUGRAZ_SHIBBOLETH = True INVENIO_CONFIG_TUGRAZ_SHIBBOLETH = True
"""Set True if SAML is configured""" """Set True if SAML is configured"""
INVENIO_CONFIG_TUGRAZ_SINGLE_IP = []
"""Allows access to users whose IP address is listed.
INVENIO_CONFIG_TUGRAZ_SINGLE_IP =
["127.0.0.1", "127.0.0.2"]
"""
INVENIO_CONFIG_TUGRAZ_IP_RANGES = []
"""Allows access to users whose range of IP address is listed.
INVENIO_CONFIG_TUGRAZ_IP_RANGES =
[["127.0.0.2", "127.0.0.99"], ["127.0.1.3", "127.0.1.5"]]
"""
# Invenio-App # Invenio-App
# =========== # ===========
# See https://invenio-app.readthedocs.io/en/latest/configuration.html # See https://invenio-app.readthedocs.io/en/latest/configuration.html
@@ -56,7 +70,7 @@ APP_DEFAULT_SECURE_HEADERS = {
# =========== # ===========
# See https://invenio-mail.readthedocs.io/en/latest/configuration.html # See https://invenio-mail.readthedocs.io/en/latest/configuration.html
MAIL_SERVER = '129.27.11.182' MAIL_SERVER = 'localhost'
"""Domain ip where mail server is running.""" """Domain ip where mail server is running."""
SECURITY_EMAIL_SENDER = "info@invenio-test.tugraz.at" SECURITY_EMAIL_SENDER = "info@invenio-test.tugraz.at"
@@ -66,8 +80,11 @@ SECURITY_EMAIL_SENDER = "info@invenio-test.tugraz.at"
SECURITY_EMAIL_SUBJECT_REGISTER = _("Welcome to RDM!") SECURITY_EMAIL_SUBJECT_REGISTER = _("Welcome to RDM!")
"""Email subject for account registration emails.""" """Email subject for account registration emails."""
MAIL_SUPPRESS_SEND = False MAIL_SUPPRESS_SEND = True
"""Enable email sending by default.""" """Enable email sending by default.
Set this to False when sending actual emails.
"""
# CORS - Cross-origin resource sharing # CORS - Cross-origin resource sharing
# =========== # ===========
@@ -98,7 +115,7 @@ when register.
""" """
SSO_SAML_IDPS = {} SSO_SAML_IDPS = {}
"""Configuration of IDPS. Actually values can be find in to invenio.cfg file""" """Configuration of IDPS. Actual values can be find in to invenio.cfg file"""
SSO_SAML_DEFAULT_BLUEPRINT_PREFIX = '/shibboleth' SSO_SAML_DEFAULT_BLUEPRINT_PREFIX = '/shibboleth'
"""Base URL for the extensions endpoint.""" """Base URL for the extensions endpoint."""
@@ -137,4 +154,40 @@ able to register, or to navigate to /sigup page.
""" """
SECURITY_CONFIRMABLE = False SECURITY_CONFIRMABLE = False
"""Allow user to confirm their email address.""" """Allow user to confirm their email address.
Instead user will get a welcome email.
"""
ACCOUNTS = True
"""Tells if the templates should use the accounts module.
If False, you won't be able to login via the web UI.
Instead if you have a overriden template somewhere in your config.py:
like this:
SECURITY_LOGIN_USER_TEMPLATE = 'invenio_theme_tugraz/accounts/login.html'
then you can remove this condition from header_login.htm:
{%- if config.ACCOUNTS %}
to render your overriden login.html
"""
# Accounts
# ========
# Actual values can be find in to invenio.cfg file
#: Recaptcha public key (change to enable).
RECAPTCHA_PUBLIC_KEY = None
#: Recaptcha private key (change to enable).
RECAPTCHA_PRIVATE_KEY = None
# invenio-records-permissions
# =======
# See:
# https://invenio-records-permissions.readthedocs.io/en/latest/configuration.html
#
# Uncomment these to enable overriden
# RECORDS_PERMISSIONS_RECORD_POLICY = (
# 'invenio_config_tugraz.permissions.TUGRAZPermissionPolicy'
# )
"""Access control configuration for records."""

View File

@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 Mojib Wali.
#
# 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.
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 and Policies. You can extend or override them for maximum
control. Thankfully we provide default ones that cover most cases.
Invenio-records-permissions conveniently structures (and relies on)
functionalities from
`invenio-access <https://invenio-access.readthedocs.io>`_ and
`flask-principal <https://pythonhosted.org/Flask-Principal>`_ .
Generators
----------
Generators are the lowest level of abstraction provided by
invenio-records-permissions. A
:py:class:`~invenio_records_permissions.generators.Generator` represents
identities via
`Needs <https://invenio-access.readthedocs.io/en/latest/api.html#needs>`_ that
are allowed or disallowed to act on a kind of object. A Generator does not
specify the action, but it does specify who is allowed and the kind of object
of concern (typically records). Generators *generate* required and forbidden
Needs at the object-of-concern level and *generate* query filters
at the search-for-objects-of-concern level.
A Generator object defines 3 methods in addition to its constructor:
- ``needs(self, **kwargs)``: returns Needs, one of which a provider is
required to have to be allowed
- ``excludes(self, **kwargs)``: returns a list of Needs disallowing any
provider of a single one
- ``query_filter(self, **kwargs)``: returns a query filter to enable retrieval
of records
The ``needs`` and ``excludes`` methods specify access conditions from
the point-of-view of the object-of-concern; whereas, the ``query_filter``
method specifies those from the actor's point-of-view in search scenarios.
A simple example of a Generator is the provided
:py:class:`~invenio_records_permissions.generators.RecordOwners` Generator:
.. code-block:: python
from flask_principal import UserNeed
class RecordOwners(Generator):
'''Allows record owners.'''
def needs(self, record=None, **kwargs):
'''Enabling Needs.'''
return [UserNeed(owner) for owner in record.get('owners', [])]
def query_filter(self, record=None, **kwargs):
'''Filters for current identity as owner.'''
# NOTE: implementation subject to change until permissions metadata
# settled
provides = g.identity.provides
for need in provides:
if need.method == 'id':
return Q('term', owners=need.value)
return []
``RecordOwners`` allows any identity providing a `UserNeed
<https://pythonhosted.org/Flask-Principal/#flask_principal.UserNeed>`_
of value found in the ``owners`` metadata of a record. The
``query_filter(self, **kwargs)``
method outputs a query that returns all owned records of the current user.
Not included in the above, because it doesn't apply to ``RecordOwners``, is
the ``excludes(self, **kwargs)`` method.
.. Note::
Exclusion has priority over inclusion. If a Need is returned by both
``needs`` and ``excludes``, providers of that Need will be **excluded**.
If implementation of Generators seems daunting, fear not! A collection of
them has already been implemented in
:py:mod:`~invenio_records_permissions.generators`
and they cover most cases you may have.
To fully understand how they work, we have to show where Generators are used.
That is in the Policies.
Policies
--------
Classes inheriting from
:py:class:`~invenio_records_permissions.policies.base.BasePermissionPolicy` are
referred to as Policies. They list **what actions** can be done **by whom**
over an implied category of objects (typically records). A Policy is
instantiated on a per action basis and is a descendant of `Permission
<https://invenio-access.readthedocs.io/en/latest/api.html
#invenio_access.permissions.Permission>`_ in
`invenio-access <https://invenio-access.readthedocs.io>`_ .
Generators are used to provide the "by whom" part and the implied category of
object.
Here is an example of a custom record Policy:
.. code-block:: python
from invenio_records_permissions.generators import AnyUser, RecordOwners, \
SuperUser
from invenio_records_permissions.policies.base import BasePermissionPolicy
class ExampleRecordPermissionPolicy(BasePermissionPolicy):
can_create = [AnyUser()]
can_search = [AnyUser()]
can_read = [AnyUser()]
can_update = [RecordOwners()]
can_foo_bar = [SuperUser()]
The actions are class variables of the form: ``can_<action>`` and the
corresponding (dis-)allowed identities are a list of Generator instances.
One can define any action as long as it follows that pattern and
is verified at the moment it is undertaken.
In the example above, any user can create, list and read records, but only
a record's owner can edit it and only super users can perform the "foo_bar"
action.
We recommend you extend the provided
:py:class:`invenio_records_permissions.policies.records.RecordPermissionPolicy`
to customize record permissions for your instance.
This way you benefit from sane defaults.
After you have defined your own Policy, set it in your configuration:
.. code-block:: python
RECORDS_PERMISSIONS_RECORD_POLICY = (
'module.to.ExampleRecordPermissionPolicy'
)
The succinct encoding of the permissions for your instance gives you
- one central location where your permissions are defined
- exact control
- great flexibility by defining your own actions, generators and policies
"""
from elasticsearch_dsl.query import Q
from flask import current_app, request
from invenio_records_permissions.generators import Generator
class RecordIp(Generator):
"""Allowed any user with accessing with the IP."""
# TODO: Implement
def needs(self, **kwargs):
"""Enabling Needs, Set of Needs granting permission.
If ANY of the Needs are matched, permission is granted.
.. note::
``_load_permissions()`` method from `Permission
<https://invenio-access.readthedocs.io/en/latest/api.html
#invenio_access.permissions.Permission>`_ adds by default the
``superuser_access`` Need (if tied to a User or Role) for us.
It also expands ActionNeeds into the Users/Roles that
provide them.
"""
return []
def excludes(self, **kwargs):
"""Preventing Needs, Set of Needs denying permission.
If ANY of the Needs are matched, permission is revoked.
.. note::
``_load_permissions()`` method from `Permission
<https://invenio-access.readthedocs.io/en/latest/api.html
#invenio_access.permissions.Permission>`_ adds by default the
``superuser_access`` Need (if tied to a User or Role) for us.
It also expands ActionNeeds into the Users/Roles that
provide them.
If the same Need is returned by `needs` and `excludes`, then that
Need provider is disallowed.
"""
return []
def query_filter(self, **kwargs):
"""Elasticsearch filters, List of ElasticSearch query filters.
These filters consist of additive queries mapping to what the current
user should be able to retrieve via search.
"""
return Q('match_all')
def check_permission(self):
"""Check for User IP address in config variable."""
# Get user IP
user_ip = request.remote_addr # pragma: no cover
# Checks if the user IP is among single IPs
if user_ip in current_app.config['INVENIO_CONFIG_TUGRAZ_SINGLE_IP']:
return True # pragma: no cover
return False # pragma: no cover

View File

@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 Mojib Wali.
#
# 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 records.
Using Custom Generator for a policy:
.. code-block:: python
from invenio_rdm_records.permissions import RDMRecordPermissionPolicy
from invenio_config_tugraz.generators import RecordIp
class TUGRAZPermissionPolicy(RDMRecordPermissionPolicy):
# Delete access given to RecordIp only.
can_delete = [RecordIp()]
RECORDS_PERMISSIONS_RECORD_POLICY = TUGRAZPermissionPolicy
Permissions for Invenio (RDM) Records.
"""
from invenio_records_permissions.generators import Admin, AnyUser, \
AnyUserIfPublic, Disable, RecordOwners
from invenio_records_permissions.policies.base import BasePermissionPolicy
from .generators import RecordIp
class TUGRAZPermissionPolicy(BasePermissionPolicy):
"""Access control configuration for records.
This overrides the /api/records endpoint.
"""
# Read access to API given to everyone.
can_search = [AnyUser(), RecordIp()]
# Read access given to everyone if public record/files and owners always.
can_read = [AnyUserIfPublic(), RecordOwners(), RecordIp()]
# Create action given to no one (Not even superusers) bc Deposits should
# be used.
can_create = [AnyUser()]
# 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()]

View File

@@ -0,0 +1,25 @@
# German translations for invenio-config-tugraz.
# Copyright (C) 2020 Mojib Wali
# This file is distributed under the same license as the
# invenio-config-tugraz project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: invenio-config-tugraz 0.1.5\n"
"Report-Msgid-Bugs-To: mojib.wali@tugraz.at\n"
"POT-Creation-Date: 2020-10-06 09:28+0200\n"
"PO-Revision-Date: 2020-10-06 09:28+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.8.0\n"
#: invenio_config_tugraz/config.py:80
msgid "Welcome to RDM!"
msgstr ""

View File

@@ -0,0 +1,24 @@
# Translations template for invenio-config-tugraz.
# Copyright (C) 2020 Mojib Wali
# This file is distributed under the same license as the
# invenio-config-tugraz project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: invenio-config-tugraz 0.1.5\n"
"Report-Msgid-Bugs-To: mojib.wali@tugraz.at\n"
"POT-Creation-Date: 2020-10-06 09:28+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.8.0\n"
#: invenio_config_tugraz/config.py:80
msgid "Welcome to RDM!"
msgstr ""

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.1.1' __version__ = '0.2.1'

View File

@@ -7,6 +7,6 @@
# details. # details.
[pytest] [pytest]
pep8ignore = docs/conf.py ALL addopts = --isort --pydocstyle --pycodestyle --doctest-glob="*.rst" --doctest-modules --cov=invenio_config_tugraz --cov-report=term-missing
addopts = --pep8 --doctest-glob="*.rst" --doctest-modules --cov=invenio_config_tugraz --cov-report=term-missing testpaths = tests invenio_config_tugraz
testpaths = docs tests invenio_config_tugraz live_server_scope = module

View File

@@ -7,8 +7,11 @@
# 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.
pydocstyle invenio_config_tugraz tests docs && \ docker-services-cli up postgresql es redis
isort --check-only --diff && \ python -m check_manifest --ignore ".travis-*" && \
check-manifest --ignore ".travis-*" && \ python -m sphinx.cmd.build -qnNW docs docs/_build/html && \
sphinx-build -qnNW docs docs/_build/html && \ docker-services-cli up es postgresql redis
python setup.py test python -m pytest
tests_exit_code=$?
docker-services-cli down
exit "$tests_exit_code"

View File

@@ -16,7 +16,7 @@ readme = open('README.rst').read()
history = open('CHANGES.rst').read() history = open('CHANGES.rst').read()
tests_require = [ tests_require = [
'pytest-invenio>=1.3.2', 'pytest-invenio>=1.4.0',
] ]
extras_require = { extras_require = {
@@ -37,6 +37,9 @@ setup_requires = [
install_requires = [ install_requires = [
'Flask-BabelEx>=0.9.4', 'Flask-BabelEx>=0.9.4',
'elasticsearch_dsl>=7.2.1',
'invenio-rdm-records~=0.18.3',
'invenio_search>=1.3.1',
] ]

17
tests/test_generators.py Normal file
View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 Mojib Wali.
#
# 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.
from invenio_config_tugraz.generators import RecordIp
def test_recordip():
generator = RecordIp()
assert generator.needs() == []
assert generator.excludes() == []
assert generator.query_filter().to_dict() == {'match_all': {}}