Skip to content

Commit

Permalink
oauth: make some information optional
Browse files Browse the repository at this point in the history
* Fixes patron information REST view crash when the patron type code is
  not present.
* Protects the patron information using the oauth permision.

Co-Authored-by: Johnny Mariéthoz <[email protected]>
  • Loading branch information
jma committed Mar 15, 2022
1 parent e12849b commit 944650a
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 36 deletions.
32 changes: 20 additions & 12 deletions rero_ils/modules/patrons/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from flask_menu import register_menu
from flask_security import utils as security_utils
from invenio_i18n.ext import current_i18n
from invenio_oauth2server.decorators import require_api_auth

from .api import Patron, PatronsSearch, current_librarian, current_patrons
from .permissions import get_allowed_roles_management
Expand Down Expand Up @@ -320,7 +321,7 @@ def patron_authenticate():


@api_blueprint.route('/info', methods=['GET'])
@check_logged_as_patron
@require_api_auth()
def info():
"""Get patron info."""
token_scopes = flask_request.oauth.access_token.scopes
Expand All @@ -342,6 +343,7 @@ def get_institution_code(institution):
:param institution: Institution object.
:returns: Code for the institution.
"""
# TODO: make this non rero specific using a configuration
return institution['code'] if institution['code'] != 'nj' else 'rbnj'

# Process for all patrons
Expand All @@ -357,7 +359,7 @@ def get_institution_code(institution):
data = {}

# Barcode
if patron['patron'].get('barcode'):
if patron.get('patron', {}).get('barcode'):
data['barcode'] = patron['patron']['barcode'][0]

# Full name
Expand All @@ -372,15 +374,21 @@ def get_institution_code(institution):
if 'patron_types' in token_scopes:
patron_types = []
for patron in patrons:
patron_types.append({
'patron_type':
patron['patron']['type']['code'],
'institution':
get_institution_code(patron['institution']),
'expiration_date':
datetime.datetime.strptime(patron['patron']['expiration_date'],
'%Y-%m-%d').isoformat()
})
data['patron_types'] = patron_types
info = {}
patron_type_code = patron.get(
'patron', {}).get('type', {}).get('code')
if patron_type_code:
info['patron_type'] = patron_type_code
if patron.get('institution'):
info['institution'] = get_institution_code(
patron['institution'])
if patron.get('patron', {}).get('expiration_date'):
info['expiration_date'] = datetime.datetime.strptime(
patron['patron']['expiration_date'],
'%Y-%m-%d').isoformat()
if info:
patron_types.append(info)
if patron_types:
data['patron_types'] = patron_types

return jsonify(data)
81 changes: 57 additions & 24 deletions tests/api/patrons/test_patrons_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import mock
from flask import url_for
from invenio_accounts.testutils import login_user_via_session
from invenio_db import db
from invenio_oauth2server.models import Client, Token
from utils import VerifyRecordPermissionPatch, create_patron, get_json, \
postdata, to_relative_url

Expand Down Expand Up @@ -569,37 +571,68 @@ def test_patron_messages(client, patron_martigny):
'Will be back in february.'


def test_patron_info(client, patron_martigny, monkeypatch):
def test_patron_info(app, client, patron_martigny, librarian_martigny):
"""Test patron info."""

class MockToken:
"""Mock token class."""
scopes = []

class MockOAuth:
"""Mock OAuth class."""
access_token = MockToken()
# All scopes
scopes = [
'fullname', 'birthdate', 'institution', 'expiration_date',
'patron_type', 'patron_types'
]

class MockRequest:
"""Mock request class."""
oauth = MockOAuth()
# create a oauth client liked to the librarian account
oauth_client = Client(
client_id='dev',
client_secret='dev',
name='Test name',
description='Test description',
is_confidential=False,
user=librarian_martigny.user,
website='http://foo.org',
_redirect_uris='')

# token with all scopes
token = Token(
client=oauth_client,
user=patron_martigny.user,
token_type='bearer',
access_token='test_access_1',
expires=None,
is_personal=False,
is_internal=False,
_scopes=' '.join(scopes))

# token without scope
no_scope_token = Token(
client=oauth_client,
user=patron_martigny.user,
token_type='bearer',
access_token='test_access_2',
expires=None,
is_personal=False,
is_internal=False)

db.session.add(oauth_client)
db.session.add(token)
db.session.commit()

# denied with a wrong token
res = client.get(url_for('api_patrons.info', access_token='wrong'))
assert res.status_code == 401

monkeypatch.setattr('rero_ils.modules.patrons.views.flask_request',
MockRequest)
login_user_via_session(client, patron_martigny.user)
url = url_for('api_patrons.info')
# denied without token
res = client.get(url_for('api_patrons.info'))
assert res.status_code == 401

# No scope
res = client.get(url)
# minimal information without scope
res = client.get(
url_for('api_patrons.info', access_token=no_scope_token.access_token))
assert res.status_code == 200
assert res.json == {'barcode': '4098124352'}
assert res.json == {'barcode': patron_martigny['patron']['barcode'].pop()}

# All scopes
MockToken.scopes = [
'fullname', 'birthdate', 'institution', 'expiration_date',
'patron_type', 'patron_types'
]
res = client.get(url)
# full information with all scopes
res = client.get(
url_for('api_patrons.info', access_token=token.access_token))
assert res.status_code == 200
assert res.json == {
'barcode':
Expand Down

0 comments on commit 944650a

Please sign in to comment.