""" InvenioRDM settings for InvenioRDM Starter project, modified for the I-Form data repository. This file was automatically generated by 'invenio-cli init' and manually modified to configure. For the full list of settings and their values, see https://inveniordm.docs.cern.ch/reference/configuration/. """ from copy import deepcopy from datetime import datetime import re from flask import request, url_for import idutils from marshmallow import validate from invenio_rdm_records.services.pids import providers from invenio_rdm_records.services import facets from invenio_rdm_records.contrib.journal import ( JOURNAL_CUSTOM_FIELDS, JOURNAL_CUSTOM_FIELDS_UI, JOURNAL_NAMESPACE, ) from invenio_records_resources.services.custom_fields import TextCF from invenio_records_resources.services.records.queryparser import ( FieldValueMapper, QueryParser, SearchFieldTransformer, ) # from invenio_records_resources.services.records.facets import TermsFacet from invenio_communities.communities.records.models import CommunityMetadata from invenio_db import db from luqum.tree import Phrase from invenio_access import action_factory from invenio_records.dictutils import dict_lookup from invenio_records_permissions.generators import ( ConditionalGenerator, Generator, ) from invenio_administration.generators import Administration from invenio_communities.permissions import CommunityPermissionPolicy from invenio_rdm_records.services.generators import ( AccessGrant, CommunityInclusionReviewers, IfDeleted, IfExternalDOIRecord, IfFileIsLocal, IfNewRecord, IfRecordDeleted, IfRestricted, RecordCommunitiesAction, RecordOwners, ResourceAccessToken, SecretLinks, SubmissionReviewer, ) from invenio_rdm_records.services.permissions import RDMRecordPermissionPolicy from invenio_records_permissions.generators import ( AuthenticatedUser, Disable, IfConfig, SystemProcess, ) def _(x): # needed to avoid start time failure with lazy strings return x RATELIMIT_ENABLED = True RATELIMIT_AUTHENTICATED_USER = "50000 per hour;900 per minute" RATELIMIT_GUEST_USER = "30000 per hour;500 per minute" # Flask # ===== # See https://flask.palletsprojects.com/en/1.1.x/config/ # Define the value of the cache control header `max-age` returned by the server when serving # public files. Files will be cached by the browser for the provided number of seconds. # See flask documentation for more information: # https://flask.palletsprojects.com/en/2.1.x/config/#SEND_FILE_MAX_AGE_DEFAULT SEND_FILE_MAX_AGE_DEFAULT = 300 # Set via env variable # SECRET_KEY= # Since HAProxy and Nginx route all requests no matter the host header # provided, the allowed hosts variable is set to localhost. In production it # should be set to the correct host and it is strongly recommended to only # route correct hosts to the application. APP_ALLOWED_HOSTS = ["localhost", "127.0.0.1"] # Flask-SQLAlchemy # ================ # See https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/ # Set via env variable # SQLALCHEMY_DATABASE_URI= # Invenio-App # =========== # See https://invenio-app.readthedocs.io/en/latest/configuration.html APP_DEFAULT_SECURE_HEADERS = { "content_security_policy": { "default-src": [ "'self'", "data:", # for fonts "'unsafe-inline'", # for inline scripts and styles "blob:", # for pdf preview "fly.storage.tigris.dev", # for S3 object storage "s3.us-east-1.amazonaws.com", # for S3 object storage "s3.eu-central-1.amazonaws.com", # for S3 object storage "s3.eu-west-1.amazonaws.com", # for S3 object storage # Add your own policies here (e.g. analytics) ], "img-src": [ "*", ], }, "force_https": True, "force_https_permanent": False, "force_file_save": False, "frame_options": "sameorigin", "frame_options_allow_from": None, "strict_transport_security": True, "strict_transport_security_preload": False, "strict_transport_security_max_age": 31556926, # One year in seconds "strict_transport_security_include_subdomains": True, "content_security_policy_report_uri": None, "content_security_policy_report_only": False, "session_cookie_secure": True, "session_cookie_http_only": True, } # Flask-Babel # =========== # See https://python-babel.github.io/flask-babel/#configuration # Default locale (language) BABEL_DEFAULT_LOCALE = "en" # Default time zone BABEL_DEFAULT_TIMEZONE = "UTC" # Invenio-I18N # ============ # See https://invenio-i18n.readthedocs.io/en/latest/configuration.html # Other supported languages (do not include BABEL_DEFAULT_LOCALE in list). I18N_LANGUAGES = [ ("de", _("German")), ("es", _("Spanish")), ("fr", _("French")), ("it", _("Italian")), ("pt", _("Portuguese")), ("tr", _("Turkish")), ] # Invenio-Mail # =========== # See https://invenio-mail.readthedocs.io/en/latest/configuration.html # Set this to False when enable email sending. MAIL_SUPPRESS_SEND = True # Invenio-Theme # ============= # See https://invenio-theme.readthedocs.io/en/latest/configuration.html # The name of the site to be used on the header and as a title. THEME_SITENAME = "InvenioRDM Starter" # Frontpage title THEME_FRONTPAGE_TITLE = "InvenioRDM Starter" # Frontpage subtitle THEME_FRONTPAGE_SUBTITLE = ( "A starter project for the turn-key research data management repository." ) # Header logo THEME_LOGO = "images/starter.svg" # Invenio-App-RDM # =============== # See https://invenio-app-rdm.readthedocs.io/en/latest/configuration.html # Instance's theme entrypoint file. Path relative to the ``assets/`` folder. INSTANCE_THEME_FILE = "./less/theme.less" # Invenio-Files-Rest # ================== FILES_REST_STORAGE_FACTORY = "invenio_s3.s3fs_storage_factory" # Invenio-S3 # ========== S3_ENDPOINT_URL = "http://localhost:9000/" S3_ACCESS_KEY_ID = "CHANGE_ME" S3_SECRET_ACCESS_KEY = "CHANGE_ME" S3_REGION_NAME = "eu-west-1" # Invenio-Records-Resources # ========================= # See https://github.com/inveniosoftware/invenio-records-resources/blob/master/invenio_records_resources/config.py SITE_UI_URL = "https://localhost" SITE_API_URL = "https://localhost/api" APP_RDM_DEPOSIT_FORM_DEFAULTS = { "publication_date": lambda: datetime.now().strftime("%Y-%m-%d"), "rights": [ { "id": "cc-by-4.0", "title": "Creative Commons Attribution 4.0 International", "description": ( "The Creative Commons Attribution license allows " "re-distribution and re-use of a licensed work " "on the condition that the creator is " "appropriately credited." ), "link": "https://creativecommons.org/licenses/by/4.0/legalcode", } ], "resource_type": { "id": "publication-preprint", }, } # See https://github.com/inveniosoftware/invenio-app-rdm/blob/master/invenio_app_rdm/config.py APP_RDM_DEPOSIT_FORM_AUTOCOMPLETE_NAMES = "search" # "search_only" or "off" # Invenio-RDM-Records # =================== # See https://inveniordm.docs.cern.ch/customize/dois/ DATACITE_ENABLED = False DATACITE_PREFIX = "10.1234" # # Persistent identifiers configuration # RDM_PERSISTENT_IDENTIFIER_PROVIDERS = [ # DOI provider for externally managed DOIs providers.ExternalPIDProvider( "external", "doi", validators=[providers.BlockedPrefixes(config_names=["DATACITE_PREFIX"])], label=_("DOI"), ), # OAI identifier providers.OAIPIDProvider( "oai", label=_("OAI ID"), ), ] """A list of configured persistent identifier providers.""" RDM_PERSISTENT_IDENTIFIERS = { "doi": { "providers": ["external"], "required": False, "label": _("DOI"), "validator": idutils.is_doi, "normalizer": idutils.normalize_doi, "is_enabled": providers.ExternalPIDProvider.is_enabled, }, "oai": { "providers": ["oai"], "required": True, "label": _("OAI"), "is_enabled": providers.OAIPIDProvider.is_enabled, }, } """The configured persistent identifiers for records.""" RDM_PARENT_PERSISTENT_IDENTIFIERS = {} def always_valid(identifier): """Gives every identifier as valid.""" return True RDM_RECORDS_IDENTIFIERS_SCHEMES = { "ark": {"label": _("ARK"), "validator": idutils.is_ark, "datacite": "ARK"}, "arxiv": {"label": _("arXiv"), "validator": idutils.is_arxiv, "datacite": "arXiv"}, "ads": { "label": _("Bibcode"), "validator": idutils.is_ads, "datacite": "bibcode", }, "crossreffunderid": { "label": _("Crossref Funder ID"), "validator": always_valid, "datacite": "Crossref Funder ID", }, "doi": {"label": _("DOI"), "validator": idutils.is_doi, "datacite": "DOI"}, "ean13": {"label": _("EAN13"), "validator": idutils.is_ean13, "datacite": "EAN13"}, "eissn": {"label": _("EISSN"), "validator": idutils.is_issn, "datacite": "EISSN"}, "grid": {"label": _("GRID"), "validator": always_valid, "datacite": "GRID"}, "handle": { "label": _("Handle"), "validator": idutils.is_handle, "datacite": "Handle", }, "igsn": {"label": _("IGSN"), "validator": always_valid, "datacite": "IGSN"}, "isbn": {"label": _("ISBN"), "validator": idutils.is_isbn, "datacite": "ISBN"}, "isni": {"label": _("ISNI"), "validator": idutils.is_isni, "datacite": "ISNI"}, "issn": {"label": _("ISSN"), "validator": idutils.is_issn, "datacite": "ISSN"}, "istc": {"label": _("ISTC"), "validator": idutils.is_istc, "datacite": "ISTC"}, "lissn": {"label": _("LISSN"), "validator": idutils.is_issn, "datacite": "LISSN"}, "lsid": {"label": _("LSID"), "validator": idutils.is_lsid, "datacite": "LSID"}, "pmid": {"label": _("PMID"), "validator": idutils.is_pmid, "datacite": "PMID"}, "purl": {"label": _("PURL"), "validator": idutils.is_purl, "datacite": "PURL"}, "upc": {"label": _("UPC"), "validator": always_valid, "datacite": "UPC"}, "url": {"label": _("URL"), "validator": idutils.is_url, "datacite": "URL"}, "urn": {"label": _("URN"), "validator": idutils.is_urn, "datacite": "URN"}, "w3id": {"label": _("W3ID"), "validator": always_valid, "datacite": "w3id"}, # Custom identifiers added for InvenioRDM Starter "uuid": {"label": _("UUID"), "validator": always_valid, "datacite": "UUID"}, "guid": {"label": _("GUID"), "validator": always_valid, "datacite": "GUID"}, "other": {"label": _("Other"), "validator": always_valid, "datacite": "Other"}, } """These are used for references, main, alternate and related identifiers.""" # Authentication - Invenio-Accounts and Invenio-OAuthclient # ========================================================= # See: https://inveniordm.docs.cern.ch/customize/authentication/ # Invenio-Accounts # ---------------- # See https://github.com/inveniosoftware/invenio-accounts/blob/master/invenio_accounts/config.py ACCOUNTS_LOCAL_LOGIN_ENABLED = True # enable local login ACCOUNTS_DEFAULT_USER_VISIBILITY = "public" SECURITY_REGISTERABLE = True # local login: allow users to register SECURITY_RECOVERABLE = True # local login: allow users to reset the password SECURITY_CHANGEABLE = True # local login: allow users to change psw SECURITY_CONFIRMABLE = True # local login: users can confirm e-mail address SECURITY_LOGIN_WITHOUT_CONFIRMATION = ( False # require users to confirm email before being able to login ) # Enable optional custom fields RDM_NAMESPACES = { **JOURNAL_NAMESPACE, "rs": None, } RDM_CUSTOM_FIELDS = [ *JOURNAL_CUSTOM_FIELDS, TextCF( # content in markdown format name="rs:content_text", ), TextCF( # feature image url name="rs:image", field_args={ "validate": validate.URL(), }, multiple=False, ), ] RDM_CUSTOM_FIELDS_UI = [ { "section": _("Publishing information"), "hide_from_landing_page": True, "fields": [ # journal *JOURNAL_CUSTOM_FIELDS_UI["fields"], ], }, { "section": _("Images"), "hide_from_landing_page": True, "fields": [ dict( field="rs:image", ui_widget="Input", props=dict( label="Feature Image URL", icon="image", required=False, ), ), ], }, ] RDM_SORT_OPTIONS = { "bestmatch": dict( title=_("Best match"), fields=["_score"], ), "newest": dict( title=_("Newest"), fields=["-metadata.publication_date", "-metadata.dates.date"], ), "oldest": dict( title=_("Oldest"), fields=["metadata.publication_date", "metadata.dates.date"], ), "version": dict( title=_("Version"), fields=["-versions.index"], ), "updated-desc": dict( title=_("Recently updated"), fields=["-updated"], ), "updated-asc": dict( title=_("Least recently updated"), fields=["updated"], ), "created-desc": dict( title=_("Recently added"), fields=["-created"], ), "created-asc": dict( title=_("Least recently added"), fields=["created"], ), } # def orcid = TermsFacet( # field="is_published", # label=_("ORCID"), # value_labels={"true": _("Yes"), "false": _("No")}, # ) RDM_FACETS = { "access_status": { "facet": facets.access_status, "ui": { "field": "access.status", }, }, "is_published": { "facet": facets.is_published, "ui": { "field": "is_published", }, }, "file_type": { "facet": facets.filetype, "ui": { "field": "files.types", }, }, "language": { "facet": facets.language, "ui": { "field": "languages", }, }, "resource_type": { "facet": facets.resource_type, "ui": { "field": "resource_type.type", "childAgg": { "field": "resource_type.subtype", }, }, }, "subject": { "facet": facets.subject, "ui": { "field": "subjects.subject", }, }, } # from https://github.com/zenodo/zenodo-rdm/blob/master/site/zenodo_rdm/queryparser.py def word_doi(node): """Quote DOIs.""" if not node.value.startswith("10."): return node return Phrase(f'"{node.value}"') def word_communities(node): """Resolve community slugs to IDs.""" slug = node.value uuid = ( db.session.query(CommunityMetadata.id) .filter(CommunityMetadata.slug == slug) .scalar() ) return Phrase(f'"{uuid}"') RDM_SEARCH = { # Supported values from RDM_FACETS "facets": ["language", "subject"], # Supported values from RDM_SORT_OPTIONS "sort": [ "bestmatch", "newest", "oldest", "updated-desc", "updated-asc", "created-desc", "created-asc", ], "query_parser_cls": QueryParser.factory( mapping={ # shortcuts "title": "metadata.title", "subject": "metadata.subjects.subject", "contributor": "metadata.creators.person_or_org.name", # taken from Zenodo "doi": FieldValueMapper("pids.doi.identifier", word=word_doi), "communities": FieldValueMapper( "parent.communities.ids", word=word_communities ), # Persistent identifiers "orcid": "metadata.creators.person_or_org.identifiers.identifier", "ror": "metadata.creators.affiliations.id", "issn": "custom_fields.journal\:journal.issn", # Specific to InveniRDM Starter "content": "custom_fields.rs\:content_text", }, tree_transformer_cls=SearchFieldTransformer, ), } COMMUNITIES_RECORDS_SEARCH = deepcopy(RDM_SEARCH) """Communities record search config is the same as the main record search.""" RDM_SEARCH_DRAFTS = { "facets": ["is_published", "language", "subject"], "sort": [ "bestmatch", "newest", "oldest", "updated-desc", "updated-asc", "created-desc", "created-asc", ], } """User records search configuration (i.e. list of uploads).""" # Toggle to show or hide the 'Browse' menu entry for communities. COMMUNITIES_SHOW_BROWSE_MENU_ENTRY = True # Enable featured communities COMMUNITIES_ADMINISTRATION_DISABLED = False # Invenio-OAuthclient # ------------------- # See https://github.com/inveniosoftware/invenio-oauthclient/blob/master/invenio_oauthclient/config.py # from invenio_oauthclient.contrib.orcid import ORCIDOAuthSettingsHelper # from invenio_github.oauth.remote_app import github_app as github_remote_app # # _orcid_app = ORCIDOAuthSettingsHelper( # title="ORCID", # description="ORCID - Connecting Research and Researchers.", # base_url="https://pub.orcid.org/", # access_token_url="https://orcid.org/oauth/token", # authorize_url="https://orcid.org/oauth/authorize#show_login", # ) # # OAUTHCLIENT_REMOTE_APPS = { # "orcid": _orcid_app.remote_app, # "github": github_remote_app, # } # # # Set via env variable # ORCID_APP_CREDENTIALS = { # "consumer_key": "CHANGE ME", # "consumer_secret": "CHANGE ME", # } # GITHUB_APP_CREDENTIALS = { # "consumer_key": "CHANGE ME", # "consumer_secret": "CHANGE", # } # # from invenio_oauthclient.views.client import auto_redirect_login # ACCOUNTS_LOGIN_VIEW_FUNCTION = auto_redirect_login # autoredirect to external login if enabled # OAUTHCLIENT_AUTO_REDIRECT_TO_EXTERNAL_LOGIN = False # autoredirect to external login # # # Invenio-UserProfiles # # -------------------- # USERPROFILES_READ_ONLY = False # allow users to change profile info (name, email, etc...) # OAI-PMH # ======= # See https://github.com/inveniosoftware/invenio-oaiserver/blob/master/invenio_oaiserver/config.py OAISERVER_ID_PREFIX = "invenio-rdm" """The prefix that will be applied to the generated OAI-PMH ids.""" # Invenio-Search # -------------- SEARCH_INDEX_PREFIX = "invenio-rdm-" # See https://inveniordm.docs.cern.ch/reference/configuration/ RDM_CITATION_STYLES = [ ("apa", _("APA")), ("harvard-cite-them-right", _("Harvard")), ("ieee", _("IEEE")), ("modern-language-association", _("MLA")), ("vancouver", _("Vancouver")), ("chicago-author-date", _("Chicago")), ("american-chemical-society", _("ACS")), ] RDM_DEFAULT_CITATION_STYLE = "apa" # Redirection of legacy URLs # -------------------------- REDIRECTOR_RULES = { # "redirect_name": { # "source": "/blogs", # "target": redirect_function, # }, } """InvenioRDMStarter permissions.""" # these are defined here as there is a circular dependency otherwise with the # permissions.py file media_files_management_action = action_factory("manage-media-files") manage_external_doi_files_action = action_factory("manage-external-doi-files") class IfFilesRestrictedForCommunity(IfRestricted): """Conditional generator for files restriction for community.""" def __init__(self, then_, else_): """Constructor.""" super().__init__("files", then_, else_) def _condition(self, record, **kwargs): """Check if community can access restricted files of the migrated record.""" try: can_community_read_files = dict_lookup( record.parent, "permission_flags.can_community_read_files" ) except KeyError: can_community_read_files = True is_restricted = super()._condition(record, **kwargs) if is_restricted: return not can_community_read_files else: return False class MediaFilesManager(Generator): """Allows media files management.""" def __init__(self): """Constructor.""" super(MediaFilesManager, self).__init__() def needs(self, **kwargs): """Enabling Needs.""" return [media_files_management_action] class ExternalDOIFilesManager(Generator): """Allows to manage files for exteranl DOI records.""" def __init__(self): """Initialize generator.""" super(ExternalDOIFilesManager, self).__init__() def needs(self, **kwargs): """Enable Needs.""" return [manage_external_doi_files_action] class IfRecordManagementAllowedForCommunity(ConditionalGenerator): """Conditional generator for community access to record management.""" def _condition(self, record, **kwargs): """Check if community can manage the migrated record.""" if record is None: return False try: can_community_manage_record = dict_lookup( record.parent, "permission_flags.can_community_manage_record" ) except KeyError: can_community_manage_record = True return can_community_manage_record def query_filter(self, **kwargs): """Filters for current identity as super user.""" then_query = self._make_query(self.then_, **kwargs) else_query = self._make_query(self.else_, **kwargs) return then_query if then_query else else_query class InvenioRDMStarterRecordPermissionPolicy(RDMRecordPermissionPolicy): """Access control configuration for records.""" # # High-level permissions (used by low-level) # can_manage = [ IfRecordManagementAllowedForCommunity( then_=RDMRecordPermissionPolicy.can_manage, else_=[ RecordOwners(), AccessGrant("manage"), SystemProcess(), ], ) ] can_curate = can_manage + [SystemProcess()] can_review = can_curate + [SystemProcess()] can_preview = can_curate + [SystemProcess()] # # Records # # Used for search filtering of deleted records # cannot be implemented inside can_read - otherwise permission will # kick in before tombstone renders can_create = [AuthenticatedUser(), SystemProcess()] can_read_deleted = [SystemProcess()] can_read_deleted_files = can_read_deleted can_media_read_deleted_files = can_read_deleted_files # # Drafts # # Allow reading metadata of a draft can_read_draft = can_preview # Allow reading files of a draft can_draft_read_files = can_preview + [SystemProcess()] # Allow updating metadata of a draft can_update_draft = can_manage # Allow uploading, updating and deleting files in drafts can_draft_create_files = can_manage can_draft_set_content_files = can_manage + [SystemProcess()] can_draft_get_content_files = can_manage + [SystemProcess()] can_draft_commit_files = can_manage + [SystemProcess()] can_draft_update_files = can_manage can_draft_delete_files = can_manage can_manage_record_access = can_manage # # 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 # # Allow to put a record in edit mode (create a draft from record) can_edit = can_manage + [SystemProcess()] # Allow deleting/discarding a draft and all associated files can_delete_draft = can_manage + [SystemProcess()] # Allow creating a new version of an existing published record. can_new_version = can_manage + [SystemProcess()] # Allow publishing a new record or changes to an existing record. can_publish = can_manage + [SystemProcess()] # Allow lifting a record or draft. can_lift_embargo = can_manage + [SystemProcess()] # # Record communities # # Who can add record to a community can_add_community = can_review # Media files can_draft_media_create_files = can_manage + [MediaFilesManager(), SystemProcess()] can_draft_media_read_files = can_draft_media_create_files can_draft_media_set_content_files = can_manage + [SystemProcess()] can_draft_media_commit_files = can_manage + [SystemProcess()] can_draft_media_update_files = can_draft_media_create_files can_draft_media_delete_files = can_draft_media_create_files can_moderate = can_manage + [SystemProcess()] can_media_create_files = can_manage + [SystemProcess()] can_media_set_content_files = can_manage + [SystemProcess()] can_media_commit_files = can_manage + [SystemProcess()] can_media_update_files = can_manage + [SystemProcess()] can_media_delete_files = can_manage + [SystemProcess()] can_modify_locked_files = can_manage + [SystemProcess()] class InvenioRDMStarterCommunityPermissionPolicy(CommunityPermissionPolicy): """Permissions for Community CRUD operations. We start with limited permissions for community creation and moderation. """ can_create = [SystemProcess()] can_moderate = [SystemProcess()] can_rename = [SystemProcess()] can_submit_record = [SystemProcess()] can_include_directly = [SystemProcess()] RDM_PERMISSION_POLICY = InvenioRDMStarterRecordPermissionPolicy """InvenioRDMStarter record permission policy.""" COMMUNITIES_ALLOW_RESTRICTED = False """Don't allow restricted records in communities.""" COMMUNITIES_PERMISSION_POLICY = InvenioRDMStarterCommunityPermissionPolicy """InvenioRDMStarter community permission policy."""