Compare commits

..

58 Commits

Author SHA1 Message Date
Christoph Ladurner
5e4fcca0ed release v0.12.3 2024-07-25 23:04:28 +02:00
Christoph Ladurner
c934a4952b fix(tugraz_authenticated): missmatch of role name 2024-07-25 23:04:01 +02:00
Christoph Ladurner
c91d056a56 release v0.12.2 2024-07-19 09:27:39 +02:00
Christoph Ladurner
583a67d0cf setup: introduce ruff
* remove unused .tx. the translation is done without transifex

* remove unused files

* remove unused checks because ruff took over
2024-07-19 09:25:08 +02:00
Christoph Ladurner
760363b4a5 perm: implement single-ip and ip-network
* with that addition it is possible to restrict records to an special ip
  or an ip network
2024-07-19 09:25:08 +02:00
Martin Obersteiner
52fb93cc43 utils: add invenio_saml-compatible account-setup 2024-07-08 10:03:01 +02:00
Martin Obersteiner
41db3186df add new permission-policy, add new role 2024-06-25 14:35:08 +02:00
Martin Obersteiner
20bdff0b79 fix deprecated before_app_first_request 2024-06-11 13:27:08 +02:00
Christoph Ladurner
99705d7a25 setup: add support for python3.11 and 3.12
* pytest-black -> pytest-black-ng, former unsupported

* add invenio-app to test install requires
2024-04-02 14:06:44 +02:00
Christoph Ladurner
d4df756ebf release v0.12.1 2024-03-08 12:57:11 +01:00
Christoph Ladurner
a10dccba22 setup: remove upper limit of rdm-records 2024-03-08 12:56:37 +01:00
Christoph Ladurner
8b84077e83 release v0.12.0 2023-11-10 09:01:40 +01:00
Christoph Ladurner
08d745d367 setup: remove python3.8 support 2023-11-10 09:01:16 +01:00
Christoph Ladurner
bf62abff3f global: make it compatible with v12 2023-11-10 09:01:16 +01:00
Christoph Ladurner
40a47ed36d release v0.11.0 2023-04-20 22:23:20 +02:00
mojib
129b331603 global: make package compatible with v11 2023-04-20 22:22:23 +02:00
Christoph Ladurner
328abd1306 release v0.10.4 2023-02-10 10:41:27 +01:00
Christoph Ladurner
7ce124cdb9 setup: invenio-search is not optional 2023-02-10 10:36:44 +01:00
Christoph Ladurner
75d78cf09a setup: change to reusable github workflows
* move check_manifest configuration to setup.cfg

* remove upper constraint for pytest-black
2023-02-10 10:36:44 +01:00
Christoph Ladurner
2e5fbcb4f9 setup: remove 3.7 and add 3.10 support 2023-02-10 10:36:44 +01:00
Christoph Ladurner
4c4279965a guide: update 2023-02-10 10:36:44 +01:00
Christoph Ladurner
0ee0df4ee1 release v0.10.2 2023-02-02 09:03:42 +01:00
mojib
8df08c09bf change version name 2023-02-01 15:36:57 +01:00
mojib
3a508ac3f0 footer: update guid 2023-02-01 15:36:57 +01:00
Christoph Ladurner
087cafa3ae release v0.10.1 2022-11-17 10:00:20 +01:00
Christoph Ladurner
14e9e0557a global: add function
* this function was moved from invenio-alma
2022-11-17 09:55:56 +01:00
Mojib Wali
8669f5dcda release: v0.10.0 2022-10-13 11:23:15 +02:00
Mojib Wali
62256b346f global: migrate to v10 2022-10-13 11:12:14 +02:00
Mojib Wali
4a8b02ec4a release: v0.9.1 2022-05-30 14:18:33 +02:00
Mojib Wali
8a592e3fdf ci(publish): ping babel version (#99) 2022-05-30 14:11:36 +02:00
Mojib Wali
42d5e2db05 release: v0.9.0 2022-05-30 13:38:10 +02:00
Mojib Wali
3db870784b config: adds accounts config var 2022-05-30 13:28:34 +02:00
Mojib Wali
73bc8b4575 dep: compatible to v9 rdm
black: formated
2022-05-25 18:49:16 +02:00
David
7fd5a7df3f config: add deposit form quota variable (#91) 2022-05-25 18:39:32 +02:00
Christoph Ladurner
79fe24511a bump invenio-rdm-records version 2022-05-25 12:46:45 +02:00
Christoph Ladurner
5b7a1718fc migrate setup py to cfg (#94)
* global: migrate setup.py to setup.cfg

* global: clean up copyright notices and tests

* migrate to use black as opinionated auto formater

* add .git-blame-ignore-revs
2022-05-12 08:58:50 +02:00
David
9192107e99 fix: update email welcome template with SITE_UI_URL (#93) 2022-04-20 08:31:42 +02:00
Mojib Wali
c43c36ece3 v0.8.4 2022-03-11 10:47:10 +01:00
Mojib Wali
3acbaf65ef revert: use gettext 2022-03-11 10:40:58 +01:00
Mojib Wali
408bdc47b1 v0.8.3 2022-03-10 10:25:35 +01:00
Mojib Wali
6c6138b682 config: fix comment & import 2022-03-10 09:31:15 +01:00
Mojib Wali
cc2c462057 README: update links 2022-03-03 10:17:52 +01:00
Mojib Wali
3f2cf9f800 v0.8.2 2022-03-03 10:12:43 +01:00
Mojib Wali
db0c7a4e21 config: introduced in v8 of invenioRDM 2022-03-03 10:03:20 +01:00
Mojib Wali
91464bbd7c v0.8.1 2022-02-28 15:05:59 +01:00
Mojib Wali
d7fe2926c7 gloabl: changes to pre-v8
* config: set samesite cookie to strict
* dep: bump in base
2022-02-28 14:55:20 +01:00
Mojib Wali
d5fcf60cf7 v0.8.0 2022-02-09 16:29:35 +01:00
David Eckhard
772b21c93a config: add OAISERVER_ADMIN_EMAIL 2021-12-16 11:54:46 +01:00
mb-wali
c39221378f v0.7.1 2021-12-07 09:54:26 +01:00
mb-wali
a42f86fcdf configs: adds new & changed configs for v7 2021-12-06 14:44:05 +01:00
mb-wali
0dd0db04e2 v0.7.0 2021-12-06 09:45:11 +01:00
David Eckhard
b02ce8a755 fix: update blueprint reorder 2021-11-08 10:21:28 +01:00
Mojib
41dcb8f437 docs: adjust sphinx to flask 2
* Add werkzeug to intersphinx for additional type hints
* Describe type hints in bullet points rather than function signatures
* Remove 'warnings as errors' flag from documentation building, as some type hints aren't resolved properly and result in a warning
2021-11-04 10:55:33 +01:00
Mojib
35854691bd config: adds oai prefix 2021-11-04 10:55:33 +01:00
Mojib
f2e18b95c3 dep: upgrade rdm-records version
migrated allowed host to gitlab
2021-11-04 10:55:33 +01:00
Mojib Wali
16c10593d6 v0.6.2 2021-08-05 08:22:37 +02:00
mb-wali
dbd870d106 doi: remove doi 'datacite' suffix 2021-08-04 16:21:04 +02:00
mb-wali
f02e992acd dep: global dep bump
config: adds i18n vars & extracted from gitlab configs
config: form defaults and tighter CSP
2021-08-04 16:21:04 +02:00
37 changed files with 1050 additions and 974 deletions

View File

@@ -15,15 +15,6 @@ insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
charset = utf-8 charset = utf-8
# Python files
[*.py]
indent_size = 4
# isort plugin configuration
known_first_party = invenio_config_tugraz
multi_line_output = 2
default_section = THIRDPARTY
skip = .eggs
# RST files (used by sphinx) # RST files (used by sphinx)
[*.rst] [*.rst]
indent_size = 4 indent_size = 4

1
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1 @@
766b2cafae4dc74393b103389e6978eca5a9cfd2

View File

@@ -5,22 +5,5 @@ on:
jobs: jobs:
build-n-publish: build-n-publish:
runs-on: ubuntu-latest uses: tu-graz-library/.github/.github/workflows/pypi-publish.yml@main
steps: secrets: inherit
- 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

@@ -16,72 +16,5 @@ on:
default: 'Manual trigger' default: 'Manual trigger'
jobs: jobs:
Tests: tests:
runs-on: ubuntu-20.04 uses: tu-graz-library/.github/.github/workflows/tests.yml@main
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
requirements-level: [min, pypi]
db-service: [postgresql12]
search-service: [elasticsearch7]
exclude:
- python-version: 3.6
requirements-level: pypi
- python-version: 3.7
requirements-level: min
- python-version: 3.8
requirements-level: min
- python-version: 3.9
requirements-level: min
- db-service: postgresql12
requirements-level: min
- search-service: elasticsearch7
requirements-level: min
include:
- db-service: postgresql12
DB_EXTRAS: "postgresql"
- search-service: elasticsearch7
SEARCH_EXTRAS: "elasticsearch7"
env:
DB: ${{ matrix.db-service }}
SEARCH: ${{ matrix.search-service }}
EXTRAS: all,${{ matrix.DB_EXTRAS }},${{ matrix.SEARCH_EXTRAS }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Generate dependencies
run: |
python -m pip install --upgrade pip setuptools py wheel requirements-builder
requirements-builder -e "$EXTRAS" --level=${{ matrix.requirements-level }} setup.py > .${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('.${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt') }}
- name: Install dependencies
run: |
pip install -r .${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt
pip install ".[$EXTRAS]"
pip freeze
docker --version
docker-compose --version
- name: Run tests
run: |
./run-tests.sh

View File

@@ -1,33 +0,0 @@
# -*- 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.
# TODO: Transifex integration
#
# 1) Create message catalog:
# $ python setup.py extract_messages
# $ python setup.py init_catalog -l <lang>
# $ python setup.py compile_catalog
# 2) Ensure project has been created on Transifex under the inveniosoftware
# organisation.
# 3) Install the transifex-client
# $ pip install transifex-client
# 4) Push source (.pot) and translations (.po) to Transifex
# $ tx push -s -t
# 5) Pull translations for a single language from Transifex
# $ tx pull -l <lang>
# 6) Pull translations for all languages from Transifex
# $ tx pull -a
[main]
host = https://www.transifex.com
[invenio.invenio-config-tugraz-messages]
file_filter = invenio_config_tugraz/translations/<lang>/LC_MESSAGES/messages.po
source_file = invenio_config_tugraz/translations/messages.pot
source_lang = en
type = PO

View File

@@ -1,5 +1,5 @@
.. ..
Copyright (C) 2020 Mojib Wali. Copyright (C) 2020 - 2022 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,98 @@
Changes Changes
======= =======
Version v0.12.3 (release 2024-07-25)
- fix(tugraz_authenticated): missmatch of role name
Version v0.12.2 (release 2024-07-19)
- setup: introduce ruff
- perm: implement single-ip and ip-network
- utils: add invenio_saml-compatible account-setup
- add new permission-policy, add new role
- fix deprecated `before_app_first_request`
- setup: add support for python3.11 and 3.12
Version v0.12.1 (release 2024-03-08)
- setup: remove upper limit of rdm-records
Version v0.12.0 (release 2023-11-10)
- setup: remove python3.8 support
- global: make it compatible with v12
Version v0.11.0 (release 2023-04-20)
- global: make package compatible with v11
Version v0.10.4 (release 2023-02-10)
Version v0.10.2 (release 2023-02-02)
- change version name
- footer: update guid
Version v0.10.1 (release 2022-11-17)
- global: add function
Version 0.10.0 (released 2022-10-13)
- global: migrate to v10 (#101)
Version 0.9.1 (released 2022-05-30)
- ci(publish): ping babel version (#99)
Version 0.9.0 (released 2022-05-30)
- config: adds new introduced configs v9
- dep: compatible to v9 rdm
- config: add deposit form quota variable (#91)
- migrate setup py to cfg (#94)
- fix: update email welcome template with SITE_UI_URL (#93)
Version 0.8.4 (released 2022-03-11)
- config: use gettext
Version 0.8.3 (released 2022-03-10)
- config: fix comment & import
Version 0.8.2 (released 2022-03-03)
- config: new introduced to v8 of invenioRDM
Version 0.8.1 (released 2022-02-28)
- config: set samesite cookie to strict
- dep: bump in base dependencies
Version 0.8.0 (released 2022-02-09)
- dep: bump rdm-records version
Version 0.7.1 (released 2021-12-07)
- configs: adds new & changed configs for v7 #76
Version 0.7.0 (released 2021-12-06)
- fix: update blueprint reorder #74
- dep: upgrade rdm-records version & OAI #72
Version 0.1.0 (released TBD) Version 0.1.0 (released TBD)
- Initial public release. - Initial public release.

View File

@@ -52,3 +52,4 @@ recursive-include invenio_config_tugraz *.csv
# added by check-manifest # added by check-manifest
recursive-include invenio_config_tugraz *.pdf recursive-include invenio_config_tugraz *.pdf
include .git-blame-ignore-revs

View File

@@ -16,10 +16,10 @@
:target: https://pypi.python.org/pypi/invenio-config-tugraz :target: https://pypi.python.org/pypi/invenio-config-tugraz
.. image:: https://img.shields.io/github/tag/tu-graz-library/invenio-config-tugraz.svg .. image:: https://img.shields.io/github/tag/tu-graz-library/invenio-config-tugraz.svg
:target: https://github.com/mb-wali/invenio-config-tugraz/releases :target: https://github.com/tu-graz-library/invenio-config-tugraz/releases
.. image:: https://img.shields.io/github/license/tu-graz-library/invenio-config-tugraz.svg .. image:: https://img.shields.io/github/license/tu-graz-library/invenio-config-tugraz.svg
:target: https://github.com/mb-wali/invenio-config-tugraz/blob/master/LICENSE :target: https://github.com/tu-graz-library/invenio-config-tugraz/blob/master/LICENSE
.. image:: https://readthedocs.org/projects/invenio-config-tugraz/badge/?version=latest .. image:: https://readthedocs.org/projects/invenio-config-tugraz/badge/?version=latest
:target: https://invenio-config-tugraz.readthedocs.io/en/latest/?badge=latest :target: https://invenio-config-tugraz.readthedocs.io/en/latest/?badge=latest

View File

@@ -15,7 +15,6 @@ encoding = utf-8
[jinja2: **/templates/**.*] [jinja2: **/templates/**.*]
encoding = utf-8 encoding = utf-8
extensions = jinja2.ext.autoescape, jinja2.ext.with_
# Extraction from JavaScript files # Extraction from JavaScript files

View File

@@ -8,7 +8,7 @@
"""Sphinx configuration.""" """Sphinx configuration."""
import os from invenio_config_tugraz import __version__
# import sphinx.environment # import sphinx.environment
@@ -46,9 +46,9 @@ source_suffix = ".rst"
master_doc = "index" master_doc = "index"
# General information about the project. # General information about the project.
project = u"invenio-config-tugraz" project = "invenio-config-tugraz"
copyright = u"2020, Mojib Wali" copyright = "2022, TU Graz"
author = u"Mojib Wali" author = "TU Graz"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
@@ -56,26 +56,15 @@ author = u"Mojib Wali"
# #
# The short X.Y version. # The short X.Y version.
# Get the version string. Cannot be done with import!
g = {}
with open(
os.path.join(
os.path.dirname(__file__), "..", "invenio_config_tugraz", "version.py"
),
"rt",
) as fp:
exec(fp.read(), g)
version = g["__version__"]
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = version release = __version__
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = None language = "en"
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
@@ -254,8 +243,8 @@ latex_documents = [
( (
master_doc, master_doc,
"invenio-config-tugraz.tex", "invenio-config-tugraz.tex",
u"invenio-config-tugraz Documentation", "invenio-config-tugraz Documentation",
u"Mojib Wali", "Mojib Wali",
"manual", "manual",
), ),
] ]
@@ -289,7 +278,7 @@ man_pages = [
( (
master_doc, master_doc,
"invenio-config-tugraz", "invenio-config-tugraz",
u"invenio-config-tugraz Documentation", "invenio-config-tugraz Documentation",
[author], [author],
1, 1,
) )
@@ -308,7 +297,7 @@ texinfo_documents = [
( (
master_doc, master_doc,
"invenio-config-tugraz", "invenio-config-tugraz",
u"invenio-config-tugraz Documentation", "invenio-config-tugraz Documentation",
author, author,
"invenio-config-tugraz", "invenio-config-tugraz",
"invenio module that adds tugraz configs.", "invenio module that adds tugraz configs.",
@@ -332,6 +321,8 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = { intersphinx_mapping = {
"python": ("https://docs.python.org/", None), "python": ("https://docs.python.org/", None),
"flask": ("https://flask.palletsprojects.com/", None),
"werkzeug": ("https://werkzeug.palletsprojects.com/", None),
# TODO: Configure external documentation references, eg: # TODO: Configure external documentation references, eg:
# 'Flask-Admin': ('https://flask-admin.readthedocs.io/en/latest/', None), # 'Flask-Admin': ('https://flask-admin.readthedocs.io/en/latest/', None),
} }

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020 Mojib Wali. # Copyright (C) 2020-2024 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
@@ -9,7 +9,12 @@
"""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 .utils import get_identity_from_user_by_email
from .version import __version__
__all__ = ("__version__", "InvenioConfigTugraz", "RecordIp") __version__ = "0.12.3"
__all__ = (
"__version__",
"InvenioConfigTugraz",
"get_identity_from_user_by_email",
)

View File

@@ -1,88 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2021 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 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 records.
"""
# from invenio_records_permissions.generators import (
# Admin,
# AnyUser,
# AnyUserIfPublic,
# 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

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020-2021 Graz University of Technology. # Copyright (C) 2020-2024 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,27 +8,29 @@
"""invenio module that adds tugraz configs.""" """invenio module that adds tugraz configs."""
from os.path import abspath, dirname, join from invenio_i18n import gettext as _
from flask_babelex import gettext as _ CONFIG_TUGRAZ_SHIBBOLETH = False
INVENIO_CONFIG_TUGRAZ_SHIBBOLETH = False
"""Set True if SAML is configured""" """Set True if SAML is configured"""
INVENIO_CONFIG_TUGRAZ_SINGLE_IP = [] CONFIG_TUGRAZ_SINGLE_IPS = []
"""Allows access to users whose IP address is listed. """Allows access to users whose IP address is listed.
INVENIO_CONFIG_TUGRAZ_SINGLE_IP = INVENIO_CONFIG_TUGRAZ_SINGLE_IPS =
["127.0.0.1", "127.0.0.2"] ["127.0.0.1", "127.0.0.2"]
""" """
INVENIO_CONFIG_TUGRAZ_IP_RANGES = [] CONFIG_TUGRAZ_IP_RANGES = []
"""Allows access to users whose range of IP address is listed. """Allows access to users whose range of IP address is listed.
INVENIO_CONFIG_TUGRAZ_IP_RANGES = INVENIO_CONFIG_TUGRAZ_IP_RANGES =
[["127.0.0.2", "127.0.0.99"], ["127.0.1.3", "127.0.1.5"]] [["127.0.0.2", "127.0.0.99"], ["127.0.1.3", "127.0.1.5"]]
""" """
CONFIG_TUGRAZ_IP_NETWORK = ""
"""Allows access to users who are in the IP network."""
CONFIG_TUGRAZ_ROUTES = { CONFIG_TUGRAZ_ROUTES = {
"guide": "/guide", "guide": "/guide",
"terms": "/terms", "terms": "/terms",
@@ -40,25 +42,12 @@ CONFIG_TUGRAZ_ROUTES = {
# =========== # ===========
# See https://invenio-app.readthedocs.io/en/latest/configuration.html # See https://invenio-app.readthedocs.io/en/latest/configuration.html
APP_ALLOWED_HOSTS = [
"0.0.0.0",
"localhost",
"127.0.0.1",
"invenio-dev01.tugraz.at",
"invenio-test.tugraz.at",
"repository.tugraz.at",
]
"""Allowed Hosts"""
APP_DEFAULT_SECURE_HEADERS = { APP_DEFAULT_SECURE_HEADERS = {
"content_security_policy": { "content_security_policy": {
"default-src": [ "default-src": [
"'self'", "'self'",
"fonts.googleapis.com",
"*.gstatic.com",
"data:", "data:",
"'unsafe-inline'", "'unsafe-inline'",
"'unsafe-eval'",
"blob:", "blob:",
"ub-support.tugraz.at", # zammad contact form "ub-support.tugraz.at", # zammad contact form
], ],
@@ -78,6 +67,15 @@ APP_DEFAULT_SECURE_HEADERS = {
"strict_transport_security_preload": False, "strict_transport_security_preload": False,
} }
# Invenio-I18N
# ============
# See https://invenio-i18n.readthedocs.io/en/latest/configuration.html
BABEL_DEFAULT_LOCALE = "en"
# Default time zone
BABEL_DEFAULT_TIMEZONE = "Europe/Vienna"
# Other supported languages (do not include BABEL_DEFAULT_LOCALE in list).
I18N_LANGUAGES = [("de", _("German"))]
# Invenio-Mail # Invenio-Mail
# =========== # ===========
# See https://invenio-mail.readthedocs.io/en/latest/configuration.html # See https://invenio-mail.readthedocs.io/en/latest/configuration.html
@@ -183,6 +181,9 @@ SECURITY_CONFIRMABLE = False
Instead user will get a welcome email. Instead user will get a welcome email.
""" """
SECURITY_LOGIN_WITHOUT_CONFIRMATION = False
"""Require users to confirm email before being able to login."""
# Flask-Security # Flask-Security
# ============= # =============
# See https://pythonhosted.org/Flask-Security/configuration.html # See https://pythonhosted.org/Flask-Security/configuration.html
@@ -239,10 +240,22 @@ password from ``users.yaml`` will be used. If that is also absent, a password
will be generated randomly. will be generated randomly.
""" """
DATACITE_FORMAT = "{prefix}/{id}"
"""Customize the generated DOI string."""
DATACITE_DATACENTER_SYMBOL = ""
""""The OAI-PMH server's metadata format oai_datacite
that allows you to harvest record from InvenioRDM in DataCite XML needs
to be configured with your DataCite data center symbol.
This is only required if you want your records to be harvestable in DataCite XML format.
"""
# Invenio-app-rdm # Invenio-app-rdm
# ========================= # =========================
# See https://github.com/inveniosoftware/invenio-app-rdm/blob/master/invenio_app_rdm/config.py # See https://github.com/inveniosoftware/invenio-app-rdm/blob/master/invenio_app_rdm/config.py
APP_RDM_DEPOSIT_FORM_DEFAULTS = {} APP_RDM_DEPOSIT_FORM_DEFAULTS = {
"publisher": "Graz University of Technology",
}
"""Default values for new records in the deposit UI. """Default values for new records in the deposit UI.
The keys denote the dot-separated path, where in the record's metadata The keys denote the dot-separated path, where in the record's metadata
@@ -251,6 +264,28 @@ If the value is callable, its return value will be used for the field
(e.g. lambda/function for dynamic calculation of values). (e.g. lambda/function for dynamic calculation of values).
""" """
APP_RDM_DEPOSIT_FORM_AUTOCOMPLETE_NAMES = "off"
"""Behavior for autocomplete names search field for creators/contributors.
Available options:
- ``search`` (default): Show search field and form always.
- ``search_only``: Only show search field. Form displayed after selection or
explicit "manual" entry.
- ``off``: Only show person form (no search field).
"""
APP_RDM_DEPOSIT_FORM_QUOTA = {
"maxFiles": 100,
# Easiest way to set this to a certain amount is to start from 1 Gb
# and go from there:
# 1 Gb: 10 ** 9
# 50 Gb: 10 ** 9 * 50
# 100 Mb: 10 ** 9 * 0.1
"maxStorage": 10**9 * 10,
}
"""Deposit file upload quota """
SQLALCHEMY_ECHO = False SQLALCHEMY_ECHO = False
"""Enable to see all SQL queries.""" """Enable to see all SQL queries."""
@@ -312,3 +347,31 @@ reopened regularly.
See https://docs.sqlalchemy.org/en/latest/core/engines.html. See https://docs.sqlalchemy.org/en/latest/core/engines.html.
""" """
# Redis (cache)
# ========
# Cache or Redis configurations
RATELIMIT_AUTHENTICATED_USER = "25000 per hour;1000 per minute"
"""Increase defaults for authenticated users."""
RATELIMIT_GUEST_USER = "5000 per hour;500 per minute"
"""Increase defaults for guest users."""
SESSION_COOKIE_SAMESITE = "Strict"
"""Sets cookie with the samesite flag to 'Strict' by default."""
# OAI-PMH
# =======
# See https://github.com/inveniosoftware/invenio-oaiserver/blob/master/invenio_oaiserver/config.py
OAISERVER_ID_PREFIX = "repository.tugraz.at"
"""The prefix that will be applied to the generated OAI-PMH ids."""
OAISERVER_ADMIN_EMAILS = [
"oai@repository.tugraz.at",
]
"""The e-mail addresses of administrators of the repository.
It **must** include one or more instances.
"""

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2024 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.
"""Custom fields."""
from invenio_records_resources.services.custom_fields import BooleanCF
ip_network = BooleanCF(name="ip_network")
single_ip = BooleanCF(name="single_ip")

View File

@@ -1,32 +1,61 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020 Mojib Wali. # Copyright (C) 2020-2024 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.
"""invenio module that adds tugraz configs.""" """invenio module that adds tugraz configs."""
from flask import Blueprint
from flask import Flask
from . import config from . import config
from .custom_fields import ip_network, single_ip
class InvenioConfigTugraz(object): class InvenioConfigTugraz:
"""invenio-config-tugraz extension.""" """invenio-config-tugraz extension."""
def __init__(self, app=None): def __init__(self, app: Flask = None) -> None:
"""Extension initialization.""" """Extension initialization."""
if app: if app:
self.init_app(app) self.init_app(app)
def init_app(self, app): def init_app(self, app: Flask) -> None:
"""Flask application initialization.""" """Flask application initialization."""
self.init_config(app) self.init_config(app)
self.add_custom_fields(app)
app.extensions["invenio-config-tugraz"] = self app.extensions["invenio-config-tugraz"] = self
def init_config(self, app): def init_config(self, app: Flask) -> None:
"""Initialize configuration.""" """Initialize configuration."""
for k in dir(config): for k in dir(config):
if k.startswith("INVENIO_CONFIG_TUGRAZ_"): if k.startswith("INVENIO_CONFIG_TUGRAZ_"):
app.config.setdefault(k, getattr(config, k)) app.config.setdefault(k, getattr(config, k))
def add_custom_fields(self, app: Flask) -> None:
"""Add custom fields."""
app.config.setdefault("RDM_CUSTOM_FIELDS", [])
app.config["RDM_CUSTOM_FIELDS"].append(ip_network)
app.config["RDM_CUSTOM_FIELDS"].append(single_ip)
def finalize_app(app: Flask) -> None:
"""Finalize app."""
rank_blueprint_higher(app)
def rank_blueprint_higher(app: Flask) -> None:
"""Rank this module's blueprint higher than blueprint of security module.
Needed in order to overwrite email templates.
Since the blueprints are in a dict and the order of insertion is
retained, popping and reinserting all items (except ours), ensures
our blueprint will be in front.
"""
bps = app.blueprints
for blueprint_name in list(bps.keys()):
if blueprint_name != "invenio_config_tugraz":
bps.update({blueprint_name: bps.pop(blueprint_name)})

View File

@@ -1,223 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2021 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.
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_access.permissions import any_user, superuser_access
from invenio_records_permissions.generators import Generator
class RecordIp(Generator):
"""Allowed any user with accessing with the IP."""
def needs(self, record=None, **kwargs):
"""Enabling Needs, Set of Needs granting permission."""
if record is None:
return []
# check if singleip is in the records restriction
is_single_ip = record.get("access", {}).get("access_right") == "singleip"
# check if the user ip is on list
visible = self.check_permission()
if not is_single_ip:
# if record does not have singleip - return any_user
return [any_user]
# if record has singleip, then check the ip of user - if ip user is on list - return any_user
elif visible:
return [any_user]
else:
# non of the above - return empty
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, *args, **kwargs):
"""Filters for singleip records."""
# check if the user ip is on list
visible = self.check_permission()
if not visible:
# 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")
def check_permission(self):
"""Check for User IP address in config variable."""
# Get user IP
user_ip = request.remote_addr
# Checks if the user IP is among single IPs
if user_ip in current_app.config["INVENIO_CONFIG_TUGRAZ_SINGLE_IP"]:
return True
return False

View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2024 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.
"""Permission-policies and roles, based on `flask-principal`."""
from .policies import TUGrazRDMRecordPermissionPolicy
__all__ = ("TUGrazRDMRecordPermissionPolicy",)

View File

@@ -0,0 +1,209 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2024 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.
r"""Permission generators for permission policies.
invenio's permissions build on
`flask-principal <https://pythonhosted.org/Flask-Principal>`_ .
In `flask-principal`, an action's `Need`s are checked
against current user's `Need`s to determine permissions.
For example, the action of deleting a record is only
permitted to users with `Need(method='role', value='admin')`.
Not all `Need`s can be known before the app is running.
For example, permissions for reading a record depend on whether
the record is public/private, so the set of `Need`s necessary
for reading a record must be computed dynamically at runtime.
This is the use case for
invenio's :py:class:`~invenio_records_permissions.generators.Generator`:
it generates `Need`s necessary for an action at runtime.
A `Generator` object defines 3 methods in addition to its constructor:
- ``needs(self, **kwargs)``: returns `Need`s, one of which a provider is
required to have to be allowed
- ``excludes(self, **kwargs)``: returns a list of `Need`s 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.
.. Note::
Exclusion has priority over inclusion. If a `Need` is returned by both
``needs`` and ``excludes``, providers of that `Need` will be **excluded**.
"""
from ipaddress import ip_address, ip_network
from typing import Any
from flask import current_app, request
from flask_principal import Need
from invenio_access.permissions import any_user
from invenio_records_permissions.generators import Generator
from invenio_search.engine import dsl
from .roles import tugraz_authenticated_user
class RecordSingleIP(Generator):
"""Allowed any user with accessing with the IP."""
def needs(self, record: dict | None = None, **__: dict) -> list[Need]:
"""Set of Needs granting permission. Enabling Needs."""
if record is None:
return []
# if record does not have singleip - return any_user
if not record.get("custom_fields", {}).get("single_ip", False):
return [any_user]
# if record has singleip, and the ip of the user matches the allowed ip
if self.check_permission():
return [any_user]
# non of the above - return empty
return []
def excludes(self, **kwargs: dict) -> list[Need]:
"""Set of Needs denying permission. Preventing Needs.
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.
"""
try:
if (
kwargs["record"]["custom_fields"]["single_ip"]
and not self.check_permission()
):
return [any_user]
except KeyError:
return []
else:
return []
def query_filter(self, *_: dict, **__: dict) -> Any: # noqa: ANN401
"""Filter for singleip records."""
if not self.check_permission():
# If user ip is not on the list, and If the record contains 'singleip' will not be seen
return ~dsl.Q("match", **{"custom_fields.single_ip": True})
# Lists all records
return dsl.Q("match_all")
def check_permission(self) -> bool:
"""Check for User IP address in config variable.
If the user ip is in the configured list return True.
"""
try:
user_ip = request.remote_addr
except RuntimeError:
return False
single_ips = current_app.config["CONFIG_TUGRAZ_SINGLE_IPS"]
return user_ip in single_ips
class AllowedFromIPNetwork(Generator):
"""Allowed from ip range."""
def needs(self, record: dict | None = None, **__: dict) -> list[Need]:
"""Set of Needs granting permission. Enabling Needs."""
if record is None:
return []
# if the record doesn't have set the ip range allowance
if not record.get("custom_fields", {}).get("ip_network", False):
return [any_user]
# if the record has set the ip_range allowance and is in the range
if self.check_permission():
return [any_user]
# non of the above - return empty
return []
def excludes(self, **kwargs: dict) -> Need:
"""Set of Needs denying permission. Preventing Needs.
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.
"""
try:
if (
kwargs["record"]["custom_fields"]["ip_network"]
and not self.check_permission()
):
return [any_user]
except KeyError:
return []
else:
return []
def query_filter(self, *_: dict, **__: dict) -> Any: # noqa: ANN401
"""Filter for ip range records."""
if not self.check_permission():
return ~dsl.Q("match", **{"custom_fields.ip_network": True})
return dsl.Q("match_all")
def check_permission(self) -> bool:
"""Check for User IP address in the configured network."""
try:
user_ip = request.remote_addr
except RuntimeError:
return False
network = current_app.config["CONFIG_TUGRAZ_IP_NETWORK"]
try:
return ip_address(user_ip) in ip_network(network)
except ValueError:
return False
class TUGrazAuthenticatedUser(Generator):
"""Generates the `tugraz_authenticated_user` role-need."""
def needs(self, **__: dict) -> list[Need]:
"""Generate needs to be checked against current user identity."""
return [tugraz_authenticated_user]

View File

@@ -0,0 +1,253 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2024 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.
"""TU Graz permission-policy for RDMRecordService.
To use, set config-variable `RDM_PERMISSION_POLICY` to `TUGrazRDMRecordPermissionPolicy`.
Policies 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.
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.
"""
from invenio_administration.generators import Administration
from invenio_communities.generators import CommunityCurators
from invenio_rdm_records.services.generators import (
AccessGrant,
CommunityInclusionReviewers,
IfDeleted,
IfExternalDOIRecord,
IfFileIsLocal,
IfNewRecord,
IfRecordDeleted,
IfRestricted,
RecordCommunitiesAction,
RecordOwners,
ResourceAccessToken,
SecretLinks,
SubmissionReviewer,
)
from invenio_records_permissions.generators import (
AnyUser,
Disable,
IfConfig,
SystemProcess,
)
from invenio_records_permissions.policies.records import RecordPermissionPolicy
from invenio_users_resources.services.permissions import UserManager
from .generators import AllowedFromIPNetwork, RecordSingleIP, TUGrazAuthenticatedUser
class TUGrazRDMRecordPermissionPolicy(RecordPermissionPolicy):
"""Overwrite authenticatedness to mean `tugraz_authenticated` rather than *signed up*."""
NEED_LABEL_TO_ACTION = {
"bucket-update": "update_files",
"bucket-read": "read_files",
"object-read": "read_files",
}
#
# General permission-groups, to be used below
#
can_manage = [
RecordOwners(),
RecordCommunitiesAction("curate"),
AccessGrant("manage"),
SystemProcess(),
]
can_curate = can_manage + [AccessGrant("edit"), SecretLinks("edit")]
can_review = can_curate + [SubmissionReviewer()]
can_preview = can_curate + [
AccessGrant("preview"),
SecretLinks("preview"),
SubmissionReviewer(),
UserManager,
]
can_view = can_preview + [
AccessGrant("view"),
SecretLinks("view"),
SubmissionReviewer(),
CommunityInclusionReviewers(),
RecordCommunitiesAction("view"),
AllowedFromIPNetwork(),
RecordSingleIP(),
]
can_tugraz_authenticated = [TUGrazAuthenticatedUser(), SystemProcess()]
can_authenticated = can_tugraz_authenticated
can_all = [
AnyUser(),
SystemProcess(),
AllowedFromIPNetwork(),
RecordSingleIP(),
]
#
# Miscellaneous
#
# Allow for querying of statistics
# - This is currently disabled because it's not needed and could potentially
# open up surface for denial of service attacks
can_query_stats = [Disable()]
#
# Records - reading and creating
#
can_search = can_all
can_read = [IfRestricted("record", then_=can_view, else_=can_all)]
can_read_deleted = [
IfRecordDeleted(then_=[UserManager, SystemProcess()], else_=can_read),
]
can_read_deleted_files = can_read_deleted
can_media_read_deleted_files = can_read_deleted_files
can_read_files = [
IfRestricted("files", then_=can_view, else_=can_all),
ResourceAccessToken("read"),
]
can_get_content_files = [
IfFileIsLocal(then_=can_read_files, else_=[SystemProcess()]),
]
can_create = can_tugraz_authenticated
#
# Drafts
#
can_search_drafts = can_tugraz_authenticated
can_read_draft = can_preview
can_draft_read_files = can_preview + [ResourceAccessToken("read")]
can_update_draft = can_review
can_draft_create_files = can_review
can_draft_set_content_files = [
IfFileIsLocal(then_=can_review, else_=[SystemProcess()]),
]
can_draft_get_content_files = [
IfFileIsLocal(then_=can_draft_read_files, else_=[SystemProcess()]),
]
can_draft_commit_files = [IfFileIsLocal(then_=can_review, else_=[SystemProcess()])]
can_draft_update_files = can_review
can_draft_delete_files = can_review
can_manage_files = [
IfConfig(
"RDM_ALLOW_METADATA_ONLY_RECORDS",
then_=[IfNewRecord(then_=can_tugraz_authenticated, else_=can_review)],
else_=[],
),
]
can_manage_record_access = [
IfConfig(
"RDM_ALLOW_RESTRICTED_RECORDS",
then_=[IfNewRecord(then_=can_tugraz_authenticated, else_=can_review)],
else_=[],
),
]
#
# PIDs
#
can_pid_create = can_review
can_pid_register = can_review
can_pid_update = can_review
can_pid_discard = can_review
can_pid_delete = can_review
#
# Actions
#
can_edit = [IfDeleted(then_=[Disable()], else_=can_curate)]
can_delete_draft = can_curate
can_new_version = [
IfConfig(
"RDM_ALLOW_EXTERNAL_DOI_VERSIONING",
then_=can_curate,
else_=[IfExternalDOIRecord(then_=[Disable()], else_=can_curate)],
),
]
can_publish = can_review
can_lift_embargo = can_manage
#
# Record communities
#
can_add_community = can_manage
can_remove_community = [RecordOwners(), CommunityCurators(), SystemProcess()]
can_remove_record = [CommunityCurators()]
can_bulk_add = [SystemProcess()]
#
# Media files - draft
#
can_draft_media_create_files = can_review
can_draft_media_read_files = can_review
can_draft_media_set_content_files = [
IfFileIsLocal(then_=can_review, else_=[SystemProcess()]),
]
can_draft_media_get_content_files = [
IfFileIsLocal(then_=can_preview, else_=[SystemProcess()]),
]
can_draft_media_commit_files = [
IfFileIsLocal(then_=can_preview, else_=[SystemProcess()]),
]
can_draft_media_delete_files = can_review
can_draft_media_update_files = can_review
#
# Media files - record
#
can_media_read_files = [
IfRestricted("record", then_=can_view, else_=can_all),
ResourceAccessToken("read"),
]
can_media_get_content_files = [
IfFileIsLocal(then_=can_read, else_=[SystemProcess()]),
]
can_media_create_files = [Disable()]
can_media_set_content_files = [Disable()]
can_media_commit_files = [Disable()]
can_media_update_files = [Disable()]
can_media_delete_files = [Disable()]
#
# Record deletetion
#
can_delete = [Administration(), SystemProcess()]
can_delete_files = [SystemProcess()]
can_purge = [SystemProcess()]
#
# Quotas for records/users
#
can_manage_quota = [UserManager, SystemProcess()]
#
# Disabled
#
# - Records/files are updated/deleted via drafts so we don't support
# using below actions.
can_update = [Disable()]
can_create_files = [Disable()]
can_set_content_files = [Disable()]
can_commit_files = [Disable()]
can_update_files = [Disable()]
# Used to hide at the moment the `parent.is_verified` field. It should be set to
# correct permissions based on which the field will be exposed only to moderators
can_moderate = [Disable()]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2024 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.
"""`RoleNeed`s for permission policies.
To use these roles, add them to the database via:
`$ invenio roles create tugraz_authenticated --description "..."`
then add roles to users via:
`$ invenio roles add user@email.com tugraz_authenticated`
"""
from flask_principal import RoleNeed
# using `flask_principal.RoleNeed`` instead of `invenio_access.SystemRoleNeed`,
# because these roles are assigned by an admin rather than automatically by the system
tugraz_authenticated_user = RoleNeed("tugraz_authenticated")

View File

@@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2021 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 RDMRecordPermissionPolicy
# from invenio_rdm_records.services.config import RDMRecordServiceConfig
# from invenio_rdm_records.services.generators import IfDraft, IfRestricted, RecordOwners
# from invenio_records_permissions.generators import (
# Admin,
# AnyUser,
# AuthenticatedUser,
# Disable,
# SuperUser,
# SystemProcess,
# )
# 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.
# Access control configuration for records.
# Note that even if the array is empty, the invenio_access Permission class
# always adds the ``superuser-access``, so admins will always be allowed.
# - Create action given to everyone for now.
# - Read access given to everyone if public record and given to owners
# always. (inherited)
# - Update access given to record owners. (inherited)
# - Delete access given to admins only. (inherited)
# """
# class TUGRAZRDMRecordServiceConfig(RDMRecordServiceConfig):
# """Overriding BibliographicRecordServiceConfig."""

View File

@@ -4,10 +4,10 @@
{{ _('To help you get started, here are some useful links:') }} {{ _('To help you get started, here are some useful links:') }}
- {{ _('Guidelines:')}} {{ _('Repository Guide')}} ({{ _('how to upload files')}}) (https://{{ config.SITE_HOSTNAME }}{{ url_for('invenio_config_tugraz.guide') }}) - {{ _('Guidelines:')}} {{ _('Repository Guide')}} ({{ _('how to upload files')}}) ({{ config.SITE_UI_URL }}{{ url_for('invenio_config_tugraz.guide') }})
- {{ _('Search Guide')}} (https://{{ config.SITE_HOSTNAME }}{{url_for('invenio_app_rdm.help_search')}}) - {{ _('Search Guide')}} ({{ config.SITE_UI_URL }}{{url_for('invenio_app_rdm.help_search')}})
- {{ _('Terms And Conditions') }} (https://{{ config.SITE_HOSTNAME }}{{ url_for('invenio_config_tugraz.terms') }}) - {{ _('Terms And Conditions') }} ({{ config.SITE_UI_URL }}{{ url_for('invenio_config_tugraz.terms') }})
- {{ _('Data Protection Rights')}} (https://{{ config.SITE_HOSTNAME }}{{ url_for('invenio_config_tugraz.gdpr') }}) - {{ _('Data Protection Rights')}} ({{ config.SITE_UI_URL }}{{ url_for('invenio_config_tugraz.gdpr') }})
{% if security.confirmable %} {% if security.confirmable %}
{{ _('You can confirm your email through the link below:') }} {{ _('You can confirm your email through the link below:') }}
{{ confirmation_link }}"> {{ confirmation_link }}">

View File

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2022-2024 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.
"""Utils file."""
import warnings
from flask_principal import Identity
from invenio_access import any_user
from invenio_access.utils import get_identity
from invenio_accounts import current_accounts
def get_identity_from_user_by_email(email: str | None = None) -> Identity:
"""Get the user specified via email or ID."""
warnings.warn("deprecated", DeprecationWarning, stacklevel=2)
if email is None:
msg = "the email has to be set to get a identity"
raise ValueError(msg)
user = current_accounts.datastore.get_user(email)
if user is None:
msg = f"user with {email} not found"
raise LookupError(msg)
identity = get_identity(user)
# TODO: this is a temporary solution. this should be done with data from the db
identity.provides.add(any_user)
return identity
def tugraz_account_setup_extension(user, account_info) -> None: # noqa: ANN001, ARG001
"""Add tugraz_authenticated role to user after SAML-login was acknowledged.
To use, have `acs_handler_factory` call invenio_saml's `default_account_setup` first,
then this function second.
.. code-block:: python
# invenio.cfg
from invenio_saml.handlers import default_account_setup, acs_handler_factory
def tugraz_account_setup(user, account_info):
# links external `account_info` with our database's `user` for future logins
default_account_setup(user, account_info)
tugraz_account_setup_extension(user, account_info)
SSO_SAML_IDPS = {
"my-tugraz-idp": {
...
"acs_handler": acs_handler_factory(
"my-tugraz-idp", account_setup=tugraz_account_setup
)
}
}
For this to work, the role tugraz_authenticated must have been created
(e.g. via `invenio roles create tugraz_authenticated`).
"""
user_email = account_info["user"]["email"]
# NOTE: `datastore.commit`ing will be done by acs_handler that calls this func
# NOTE: this is a No-Op when user_email already has role tugraz_authenticated
current_accounts.datastore.add_role_to_user(user_email, "tugraz_authenticated")

View File

@@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2021 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.
"""Version information for invenio-config-tugraz.
This file is imported by ``invenio_config_tugraz.__init__``,
and parsed by ``setup.py``.
"""
__version__ = "0.6.1"

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020-2021 Graz University of Technology. # Copyright (C) 2020-2024 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,15 +8,12 @@
"""invenio module for TUGRAZ config.""" """invenio module for TUGRAZ config."""
from os import environ from flask import Blueprint, Flask, redirect, url_for
from typing import Dict from invenio_i18n import get_locale
from werkzeug.wrappers import Response as BaseResponse
from elasticsearch_dsl.utils import AttrDict
from flask import Blueprint, current_app, redirect, url_for
from flask_babelex import get_locale
def ui_blueprint(app): def ui_blueprint(app: Flask) -> Blueprint:
"""Blueprint for the routes and resources provided by invenio-config-tugraz.""" """Blueprint for the routes and resources provided by invenio-config-tugraz."""
routes = app.config.get("CONFIG_TUGRAZ_ROUTES") routes = app.config.get("CONFIG_TUGRAZ_ROUTES")
@@ -31,46 +28,40 @@ def ui_blueprint(app):
blueprint.add_url_rule(routes["terms"], view_func=terms) blueprint.add_url_rule(routes["terms"], view_func=terms)
blueprint.add_url_rule(routes["gdpr"], view_func=gdpr) blueprint.add_url_rule(routes["gdpr"], view_func=gdpr)
@blueprint.before_app_first_request
def rank_higher():
"""Rank this modules blueprint higher than blueprint of security module."""
blueprints = current_app._blueprint_order
our_index = None
security_index = None
for index, bp in enumerate(blueprints):
if bp.name == "security":
security_index = index
if bp.name == "invenio_config_tugraz":
our_index = index
if (security_index is not None) and (our_index > security_index):
temp = blueprints[security_index]
blueprints[security_index] = blueprints[our_index]
blueprints[our_index] = temp
return blueprint return blueprint
def guide(): def guide() -> BaseResponse:
"""TUGraz_Repository_Guide.""" """TUGraz_Repository_Guide."""
locale = get_locale() locale = get_locale()
return redirect(url_for('static', return redirect(
filename=f'documents/TUGraz_Repository_Guide_02_{locale}.pdf', url_for(
_external=True)) "static",
filename=f"documents/TUGraz_Repository_Guide_02.1_{locale}.pdf",
_external=True,
),
)
def terms(): def terms() -> BaseResponse:
"""Terms_And_Conditions.""" """Terms_And_Conditions."""
locale = get_locale() locale = get_locale()
return redirect(url_for('static', return redirect(
filename=f'documents/TUGraz_Repository_Terms_And_Conditions_{locale}.pdf', url_for(
_external=True)) "static",
filename=f"documents/TUGraz_Repository_Terms_And_Conditions_{locale}.pdf",
_external=True,
),
)
def gdpr(): def gdpr() -> BaseResponse:
"""General_Data_Protection_Rights.""" """General_Data_Protection_Rights."""
locale = get_locale() locale = get_locale()
return redirect(url_for('static', return redirect(
filename=f'documents/TUGraz_Repository_General_Data_Protection_Rights_{locale}.pdf', url_for(
_external=True)) "static",
filename=f"documents/TUGraz_Repository_General_Data_Protection_Rights_{locale}.pdf",
_external=True,
),
)

22
pyproject.toml Normal file
View File

@@ -0,0 +1,22 @@
[build-system]
requires = ["setuptools", "wheel", "babel>2.8"]
build-backend = "setuptools.build_meta"
[tool.ruff]
exclude = ["docs"]
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN101", "ANN102",
"D203", "D211", "D212", "D213",
"E501",
"ERA001",
"FA102",
"FIX002",
"INP001",
"RUF005", "RUF012",
"S101",
"TD002", "TD003",
"UP009",
]

View File

@@ -1,12 +0,0 @@
# -*- 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.
[pytest]
addopts = --isort --pydocstyle --pycodestyle --doctest-glob="*.rst" --doctest-modules --cov=invenio_config_tugraz --cov-report=term-missing tests invenio_config_tugraz
testpaths = tests invenio_config_tugraz
live_server_scope = module

View File

@@ -1,13 +0,0 @@
# -*- 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.
#
# TODO: Add development versions of some important dependencies here to get a
# warning when there are breaking upstream changes, e.g.:
#
# -e git+git://github.com/mitsuhiko/werkzeug.git#egg=Werkzeug
# -e git+git://github.com/mitsuhiko/jinja2.git#egg=Jinja2

View File

@@ -3,7 +3,7 @@
# #
# Copyright (C) 2019-2020 CERN. # Copyright (C) 2019-2020 CERN.
# Copyright (C) 2019-2020 Northwestern University. # Copyright (C) 2019-2020 Northwestern University.
# Copyright (C) 2020 Graz University of Technology. # Copyright (C) 2020-2024 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
@@ -16,18 +16,8 @@ set -o errexit
# Quit on unbound symbols # Quit on unbound symbols
set -o nounset set -o nounset
# Always bring down docker services ruff check .
function cleanup() { python -m check_manifest
eval "$(docker-services-cli down --env)" python -m sphinx.cmd.build -qnN docs docs/_build/html
}
trap cleanup EXIT
python -m check_manifest --ignore ".*-requirements.txt"
python -m sphinx.cmd.build -qnNW docs docs/_build/html
eval "$(docker-services-cli up --db ${DB:-postgresql} --search ${SEARCH:-elasticsearch} --cache ${CACHE:-redis} --env)"
python -m pytest python -m pytest
tests_exit_code=$?
python -m sphinx.cmd.build -qnNW -b doctest docs docs/_build/doctest
exit "$tests_exit_code"

View File

@@ -1,11 +1,64 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020 Mojib Wali. # Copyright (C) 2020-2024 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.
[metadata]
name = invenio-config-tugraz
version = attr: invenio_config_tugraz.__version__
description = "Invenio module that adds tugraz configs."
long_description = file: README.rst, CHANGES.rst
keywords = invenio config TU-Graz
license = MIT
author = "Graz University of Technology"
author_email = info@tugraz.at
platforms = any
url = https://github.com/tu-graz-library/invenio-config-tugraz
classifiers =
Environment :: Web Environment
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python
Topic :: Internet :: WWW/HTTP :: Dynamic Content
Topic :: Software Development :: Libraries :: Python Modules
Programming Language :: Python :: 3.12
Development Status :: 3 - Alpha
[options]
include_package_data = True
packages = find:
python_requires = >=3.12
zip_safe = False
install_requires =
invenio-cache>=1.1.1
invenio-i18n>=2.0.0
invenio-rdm-records>=4.0.0
[options.extras_require]
tests =
invenio-app>=1.5.0
invenio-search[opensearch2]>=2.1.0,<3.0.0
pytest-black-ng>=0.4.0
pytest-invenio>=2.1.0,<3.0.0
ruff>=0.5.3
Sphinx>=4.5.0
[options.entry_points]
invenio_base.apps =
invenio_config_tugraz = invenio_config_tugraz:InvenioConfigTugraz
invenio_base.blueprints =
invenio_config_tugraz = invenio_config_tugraz.views:ui_blueprint
invenio_i18n.translations =
messages = invenio_config_tugraz
invenio_config.module =
invenio_config_tugraz = invenio_config_tugraz.config
invenio_base.finalize_app =
invenio_config_tugraz = invenio_config_tugraz.ext:finalize_app
[aliases] [aliases]
test = pytest test = pytest
@@ -17,9 +70,6 @@ all_files = 1
[bdist_wheel] [bdist_wheel]
universal = 1 universal = 1
[pydocstyle]
add_ignore = D401
[compile_catalog] [compile_catalog]
directory = invenio_config_tugraz/translations/ directory = invenio_config_tugraz/translations/
@@ -38,19 +88,13 @@ output-dir = invenio_config_tugraz/translations/
input-file = invenio_config_tugraz/translations/messages.pot input-file = invenio_config_tugraz/translations/messages.pot
output-dir = invenio_config_tugraz/translations/ output-dir = invenio_config_tugraz/translations/
[flake8]
max-line-length = 88
extend-ignore = E203
select = C,E,F,W,B,B950
ignore = E501
[isort] [isort]
multi_line_output = 3 profile=black
include_trailing_comma = True
force_grid_wrap = 0
use_parentheses = True
ensure_newline_before_comments = True
line_length = 88
[pycodestyle] [check-manifest]
ignore = E203,E501 ignore = *-requirements.txt
[tool:pytest]
addopts = --black --cov=invenio_config_tugraz --cov-report=term-missing
testpaths = tests invenio_config_tugraz
live_server_scope = module

108
setup.py
View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020-2021 Graz University of Technology. # Copyright (C) 2020-2022 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,108 +8,6 @@
"""invenio module that adds tugraz configs.""" """invenio module that adds tugraz configs."""
import os from setuptools import setup
from setuptools import find_packages, setup setup()
readme = open("README.rst").read()
history = open("CHANGES.rst").read()
tests_require = [
"pytest-invenio>=1.4.0",
"invenio-app>=1.3.0,<2.0.0",
]
# Should follow invenio-app-rdm
invenio_search_version = ">=1.4.0,<1.5.0"
invenio_db_version = ">=1.0.9,<1.1.0"
extras_require = {
"elasticsearch7": [f"invenio-search[elasticsearch7]{invenio_search_version}"],
"mysql": [f"invenio-db[mysql,versioning]{invenio_db_version}"],
"postgresql": [f"invenio-db[postgresql,versioning]{invenio_db_version}"],
"sqlite": [f"invenio-db[versioning]{invenio_db_version}"],
"docs": [
"Sphinx>=3",
],
"tests": tests_require,
}
extras_require["all"] = []
for name, reqs in extras_require.items():
if name[0] == ":" or name in (
"elasticsearch7",
"mysql",
"postgresql",
"sqlite",
):
continue
extras_require["all"].extend(reqs)
setup_requires = [
"Babel>=1.3",
"pytest-runner>=3.0.0,<5",
]
install_requires = [
"Flask-BabelEx>=0.9.4",
# keep this in sync with invenioRDM release
"invenio_app_rdm==4.0.0",
]
packages = find_packages()
# Get the version string. Cannot be done with import!
g = {}
with open(os.path.join("invenio_config_tugraz", "version.py"), "rt") as fp:
exec(fp.read(), g)
version = g["__version__"]
setup(
name="invenio-config-tugraz",
version=version,
description=__doc__,
long_description=readme + "\n\n" + history,
keywords="invenio, config, Tu Graz",
license="MIT",
author="Mojib Wali",
author_email="mb_wali@hotmail.com",
url="https://github.com/tu-graz-library/invenio-config-tugraz",
packages=packages,
zip_safe=False,
include_package_data=True,
platforms="any",
entry_points={
"invenio_base.apps": [
"invenio_config_tugraz = invenio_config_tugraz:InvenioConfigTugraz",
],
"invenio_base.blueprints": [
"invenio_config_tugraz = invenio_config_tugraz.views:ui_blueprint",
],
"invenio_i18n.translations": [
"messages = invenio_config_tugraz",
],
"invenio_config.module": [
"invenio_config_tugraz = invenio_config_tugraz.config",
],
},
extras_require=extras_require,
install_requires=install_requires,
setup_requires=setup_requires,
tests_require=tests_require,
classifiers=[
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Development Status :: 3 - Alpha",
],
)

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020 Mojib Wali. # Copyright (C) 2020 Mojib Wali.
# Copyright (C) 2020-2024 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
@@ -12,152 +13,21 @@ See https://pytest-invenio.readthedocs.io/ for documentation on which test
fixtures are available. fixtures are available.
""" """
import os
import shutil
import tempfile
import pytest import pytest
from flask import Flask from flask import Flask
from flask_babelex import Babel
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
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def celery_config(): def create_app(instance_path: str) -> Flask:
"""Override pytest-invenio fixture. """Application factory fixture."""
TODO: Remove this fixture if you add Celery support. def factory(**config: str) -> Flask:
""" app = Flask("testapp", instance_path=instance_path)
return {} app.config.update(**config)
InvenioConfigTugraz(app)
return app
return factory
@pytest.fixture()
def create_app(request):
"""Basic Flask application."""
instance_path = tempfile.mkdtemp()
app = Flask("testapp")
DB = os.getenv("SQLALCHEMY_DATABASE_URI", "sqlite://")
app.config.update(
INVENIO_CONFIG_TUGRAZ_SINGLE_IP=["127.0.0.1", "127.0.0.2"],
INVENIO_CONFIG_TUGRAZ_IP_RANGES=[
["127.0.0.2", "127.0.0.99"],
["127.0.1.3", "127.0.1.5"],
],
SQLALCHEMY_DATABASE_URI=DB,
SQLALCHEMY_TRACK_MODIFICATIONS=False,
)
Babel(app)
InvenioConfigTugraz(app)
InvenioDB(app)
with app.app_context():
db_url = str(db.engine.url)
if db_url != "sqlite://" and not database_exists(db_url):
create_database(db_url)
db.create_all()
def teardown():
with app.app_context():
db_url = str(db.engine.url)
db.session.close()
if db_url != "sqlite://":
drop_database(db_url)
shutil.rmtree(instance_path)
request.addfinalizer(teardown)
app.test_request_context().push()
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

@@ -1,29 +0,0 @@
# -*- 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.
"""Test Generators."""
from invenio_access.permissions import any_user
from invenio_config_tugraz.generators import RecordIp
def test_recordip(create_app, open_record, singleip_record):
"""Test Generator RecordIp."""
generator = RecordIp()
open_record = open_record
singleiprec = singleip_record
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'}}]}}

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (C) 2020 Mojib Wali. # Copyright (C) 2020-2024 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
@@ -13,14 +13,14 @@ from flask import Flask
from invenio_config_tugraz import InvenioConfigTugraz from invenio_config_tugraz import InvenioConfigTugraz
def test_version(): def test_version() -> None:
"""Test version import.""" """Test version import."""
from invenio_config_tugraz import __version__ from invenio_config_tugraz import __version__
assert __version__ assert __version__
def test_init(): def test_init() -> None:
"""Test extension initialization.""" """Test extension initialization."""
app = Flask("testapp") app = Flask("testapp")
ext = InvenioConfigTugraz(app) ext = InvenioConfigTugraz(app)

88
tests/test_policies.py Normal file
View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2024 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.
"""Tests for permissions-policy."""
from invenio_rdm_records.services.permissions import RDMRecordPermissionPolicy
from invenio_config_tugraz.permissions.policies import TUGrazRDMRecordPermissionPolicy
ALLOWED_DIFFERENCES = {
"can_authenticated",
"can_create",
"can_search",
"can_view",
"can_all",
"can_search_drafts",
"can_tugraz_authenticated",
}
def test_policies_synced() -> None:
"""Make sure our permission-policy stays synced with invenio's."""
tugraz_cans = {
name: getattr(TUGrazRDMRecordPermissionPolicy, name)
for name in dir(TUGrazRDMRecordPermissionPolicy)
if name.startswith("can_")
}
rdm_cans = {
name: getattr(RDMRecordPermissionPolicy, name)
for name in dir(RDMRecordPermissionPolicy)
if name.startswith("can_")
}
# check whether same set of `can_<action>`s`
if extras := set(tugraz_cans) - set(rdm_cans) - ALLOWED_DIFFERENCES:
msg = f"""
TU Graz's permission-policy has additional fields over invenio-rdm's:{extras}
if this is intentional, add to ALLOWED_DIFFERENCES in test-file
otherwise remove extraneous fields from TUGrazRDMRecordPermissionPolicy
"""
raise KeyError(msg)
if missing := set(rdm_cans) - set(tugraz_cans):
msg = f"""
invenio-rdm's permission-policy has fields unhandled by TU Graz's: {missing}
if this is intentional, add to ALLOWED_DIFFERENCES
otherwise set the corresponding fields in TUGrazRDMRecordPermissionPolicy
"""
raise KeyError(msg)
# check whether same permission-generators used for same `can_<action>`
for can_name in rdm_cans.keys() & tugraz_cans.keys():
if can_name in ALLOWED_DIFFERENCES:
continue
tugraz_can = tugraz_cans[can_name]
rdm_can = rdm_cans[can_name]
# permission-Generators don't implement equality checks for their instances
# we can however compare which types (classes) of Generators are used...
if {type(gen) for gen in tugraz_can} != {type(gen) for gen in rdm_can}:
msg = f"""
permission-policy for `{can_name}` differs between TU-Graz and invenio-rdm
if this is intentional, add to ALLOWED_DIFFERENCES in test-file
otherwise fix TUGrazRDMRecordPermissionPolicy
"""
raise ValueError(msg)
# check whether same `NEED_LABEL_TO_ACTION`
tugraz_label_to_action = TUGrazRDMRecordPermissionPolicy.NEED_LABEL_TO_ACTION
rdm_label_to_action = RDMRecordPermissionPolicy.NEED_LABEL_TO_ACTION
for label in tugraz_label_to_action.keys() & rdm_label_to_action.keys():
if label in ALLOWED_DIFFERENCES:
continue
if tugraz_label_to_action.get(label) != rdm_label_to_action.get(label):
msg = f"""
invenio-rdm's NEED_LABEL_TO_ACTION differs from TU Graz's in {label}
if this is intentional, add to ALLOWED_DIFFERENCES in test-file
otherwise fix TUGrazRDMRecordPermissionPolicy.NEED_LABEL_TO_ACTION
"""
raise ValueError(msg)