Skip to content

Commit

Permalink
document: allow posting documents using OAuth token
Browse files Browse the repository at this point in the history
With this commit, a new role `document_importer` is created for users
creating document via the document post API only.

Currently, this role is assigned to one invenio user for ngscan tests.

A new cli to create personal OAuth token is added to the utils.

* Fixtures and tests updated accordingly.

Co-Authored-by: Aly Badr <aly.badr@rero.ch>
  • Loading branch information
Aly Badr committed Aug 25, 2020
1 parent e7e7246 commit db9a9d5
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 12 deletions.
72 changes: 72 additions & 0 deletions rero_ils/modules/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
from invenio_accounts.cli import commit, users
from invenio_db import db
from invenio_jsonschemas.proxies import current_jsonschemas
from invenio_oauth2server.cli import process_scopes, process_user
from invenio_oauth2server.models import Client, Token
from invenio_pidstore.models import PersistentIdentifier, PIDStatus
from invenio_records.api import Record
from invenio_records_rest.utils import obj_or_import_string
Expand All @@ -55,6 +57,7 @@
from jsonschema.exceptions import ValidationError
from lxml import etree
from werkzeug.local import LocalProxy
from werkzeug.security import gen_salt

from .api import IlsRecordsIndexer
from .documents.dojson.contrib.marc21tojson import marc21
Expand Down Expand Up @@ -1448,3 +1451,72 @@ def export(verbose, pid_type, outfile, pidfile, indent, schema):
click.echo('ERROR: Can not export pid:{pid}'.format(pid=pid))
outfile.write(output)
outfile.write('\n]\n')


def create_personal(
name, user_id, scopes=None, is_internal=False, access_token=None):
"""Create a personal access token.
A token that is bound to a specific user and which doesn't expire, i.e.
similar to the concept of an API key.
:param name: Client name.
:param user_id: User ID.
:param scopes: The list of permitted scopes. (Default: ``None``)
:param is_internal: If ``True`` it's a internal access token.
(Default: ``False``)
:param access_token: personalized access_token.
:returns: A new access token.
"""
with db.session.begin_nested():
scopes = " ".join(scopes) if scopes else ""

c = Client(
name=name,
user_id=user_id,
is_internal=True,
is_confidential=False,
_default_scopes=scopes
)
c.gen_salt()

if not access_token:
access_token = gen_salt(
current_app.config.get(
'OAUTH2SERVER_TOKEN_PERSONAL_SALT_LEN')
)
t = Token(
client_id=c.client_id,
user_id=user_id,
access_token=access_token,
expires=None,
_scopes=scopes,
is_personal=True,
is_internal=is_internal,
)

db.session.add(c)
db.session.add(t)

return t


@utils.command('tokens_create')
@click.option('-n', '--name', required=True)
@click.option(
'-u', '--user', required=True, callback=process_user,
help='User ID or email.')
@click.option(
'-s', '--scope', 'scopes', multiple=True, callback=process_scopes)
@click.option('-i', '--internal', is_flag=True)
@click.option(
'-t', '--access_token', 'access_token', required=False,
help='personalized access_token.')
@with_appcontext
def tokens_create(name, user, scopes, internal, access_token):
"""Create a personal OAuth token."""
token = create_personal(
name, user.id, scopes=scopes, is_internal=internal,
access_token=access_token)
db.session.commit()
click.secho(token.access_token, fg='blue')
8 changes: 7 additions & 1 deletion rero_ils/modules/documents/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@

"""Permissions for documents."""

from flask_principal import RoleNeed
from invenio_access.permissions import Permission

from rero_ils.modules.patrons.api import current_patron
from rero_ils.modules.permissions import RecordPermission

document_importer_permission = Permission(RoleNeed('document_importer'))


class DocumentPermission(RecordPermission):
"""Document permissions."""
Expand Down Expand Up @@ -59,7 +64,8 @@ def create(cls, user, record=None):
if not current_patron:
return False
# only staff members (lib, sys_lib) are allowed to create any documents
return current_patron.is_librarian
# users with document_importer permission can add new records
return current_patron.is_librarian or document_importer_permission

@classmethod
def update(cls, user, record):
Expand Down
20 changes: 19 additions & 1 deletion scripts/setup
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,14 @@ info_msg "Create new admin user"
eval ${PREFIX} invenio users create -a admin@rero.ch --password administrator
eval ${PREFIX} invenio users create -a editor@rero.ch --password editor

info_msg "Create document_importer user"
eval ${PREFIX} invenio users create -a gipi@ngscan.com --password ngscan

# confirm users
info_msg "Confirm admin creation"
eval ${PREFIX} invenio users confirm admin@rero.ch
eval ${PREFIX} invenio users confirm editor@rero.ch
eval ${PREFIX} invenio users confirm gipi@ngscan.com

# create roles
info_msg "Create roles: admin, patron, librarian and system librarian"
Expand All @@ -214,9 +218,12 @@ eval "${PREFIX} invenio roles create -d 'Librarian' librarian"
# create a role for users qualified as a System librarian
eval "${PREFIX} invenio roles create -d 'System Librarian' system_librarian"

# create a role for users qualified as a System librarian
# create a role for users qualified as a Documentation Editor
eval "${PREFIX} invenio roles create -d 'Documentation Editor' editor"

# create a role for users qualified as a Documment Importing
eval "${PREFIX} invenio roles create -d 'Documment Importing' document_importer"

# grant accesses to action roles
info_msg "Grant access to action roles (admins, superusers)"
eval ${PREFIX} invenio access allow admin-access role admins
Expand All @@ -227,6 +234,7 @@ info_msg "Grant roles to users"
eval ${PREFIX} invenio roles add admin@rero.ch admins
eval ${PREFIX} invenio roles add admin@rero.ch superusers
eval ${PREFIX} invenio roles add editor@rero.ch editor
eval ${PREFIX} invenio roles add gipi@ngscan.com document_importer

# Generate fixtures
info_msg "Generate fixtures:"
Expand Down Expand Up @@ -361,6 +369,16 @@ eval ${PREFIX} invenio fixtures create_loans ${DATA_PATH}/loans.json
# process notifications
eval ${PREFIX} invenio notifications process

# create token access for ngscan (ezpump)
# if the environement variable RERO_ACCESS_TOKEN_NGSCAN is not set a new
# token will be generated
if ${RERO_ACCESS_TOKEN_NGSCAN}
then
eval ${PREFIX} invenio utils tokens_create -n ezpump -u gipi@ngscan.com -t ${RERO_ACCESS_TOKEN_NGSCAN}
else
eval ${PREFIX} invenio utils tokens_create -n ezpump -u gipi@ngscan.com
fi

# # OAI configuration
info_msg "OAI configuration:"
eval ${PREFIX} invenio oaiharvester initconfig ${DATA_PATH}/oaisources.yml
Expand Down
47 changes: 41 additions & 6 deletions tests/api/documents/test_marcxml_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,26 @@
"""Tests POST REST API for MARC21 documents."""


import mock
from utils import VerifyRecordPermissionPatch, postdata
from click.testing import CliRunner
from utils import login_user_via_session, postdata

from rero_ils.modules.cli import tokens_create


@mock.patch('invenio_records_rest.views.verify_record_permission',
mock.MagicMock(return_value=VerifyRecordPermissionPatch))
def test_marcxml_documents_create(
client, document_marcxml, documents_marcxml, rero_marcxml_header):
"""Test post of marcxml document."""
client, document_marcxml, documents_marcxml, rero_marcxml_header,
librarian_martigny_no_email):
"""Test post of marcxml document for logged users."""
res, data = postdata(
client,
'invenio_records_rest.doc_list',
document_marcxml,
headers=rero_marcxml_header,
force_data_as_json=False
)
assert res.status_code == 401

login_user_via_session(client, librarian_martigny_no_email.user)
res, data = postdata(
client,
'invenio_records_rest.doc_list',
Expand All @@ -46,3 +57,27 @@ def test_marcxml_documents_create(
force_data_as_json=False
)
assert res.status_code == 400


def test_marcxml_documents_create_with_a_token(
app, client, document_marcxml, rero_marcxml_header,
librarian_martigny_no_email, script_info):
"""Test post of marcxml document with an access token."""
runner = CliRunner()
res = runner.invoke(
tokens_create,
['-n', 'test', '-u', librarian_martigny_no_email.get('email'),
'-t', 'my_token'],
obj=script_info
)
access_token = res.output.strip().split('\n')[0]
res, data = postdata(
client,
'invenio_records_rest.doc_list',
document_marcxml,
url_data={'access_token': access_token},
headers=rero_marcxml_header,
force_data_as_json=False
)
assert res.status_code == 201
assert data['metadata']['_draft']
1 change: 0 additions & 1 deletion tests/data/document.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<collection xmlns="http://www.loc.gov/MARC21/slim">
<record>
<leader>00975nam a2200277 a 4500</leader>
<controlfield tag="001">REROILS:1</controlfield>
<controlfield tag="005">20170518120100.0</controlfield>
<controlfield tag="008">000918s1999 gw ||| | ||||00| |ger d</controlfield>
<datafield tag="020" ind1=" " ind2=" ">
Expand Down
2 changes: 0 additions & 2 deletions tests/data/documents.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<collection xmlns="http://www.loc.gov/MARC21/slim">
<record>
<leader>00975nam a2200277 a 4500</leader>
<controlfield tag="001">REROILS:1</controlfield>
<controlfield tag="005">20170518120100.0</controlfield>
<controlfield tag="008">000918s1999 gw ||| | ||||00| |ger d</controlfield>
<datafield tag="020" ind1=" " ind2=" ">
Expand Down Expand Up @@ -81,7 +80,6 @@
</record>
<record>
<leader>00975nam a2200277 a 4500</leader>
<controlfield tag="001">REROILS:1</controlfield>
<controlfield tag="005">20170518120100.0</controlfield>
<controlfield tag="008">000918s1999 gw ||| | ||||00| |ger d</controlfield>
<datafield tag="020" ind1=" " ind2=" ">
Expand Down
14 changes: 13 additions & 1 deletion tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from click.testing import CliRunner

from rero_ils.modules.cli import check_validate
from rero_ils.modules.cli import check_validate, tokens_create


def test_cli_validate(app, script_info):
Expand All @@ -39,3 +39,15 @@ def test_cli_validate(app, script_info):
'\tTest record: 1',
'\tTest record: 2'
]


def test_cli_access_token(app, script_info, patron_martigny_no_email):
"""Test access token cli."""
runner = CliRunner()
res = runner.invoke(
tokens_create,
['-n', 'test', '-u', patron_martigny_no_email.get('email'),
'-t', 'my_token'],
obj=script_info
)
assert res.output.strip().split('\n') == ['my_token']

0 comments on commit db9a9d5

Please sign in to comment.