Commit 9808df74 authored by Pablo Panero's avatar Pablo Panero
Browse files

Oauth

parent 12680129
......@@ -240,6 +240,38 @@ An example mapping containing the permission fields is:
Note that there is no _create_ permission, that is specified by the __owner_ field in the metadata.
### Importante note
There is a bug in invenio-oauthclient. Once the user SSO token expires there is no way of updating the egroups
(and other information) of the user, failing with a _401 Unauthorized_ from the SSO (due to expired token). There is an
issue opened ([link](https://github.com/inveniosoftware/invenio-oauthclient/issues/154)). In the meanwhile the solution is to edit the _invenio-oauthclient/contrib/cern.py:account_groups_
with the following lines:
```python
def account_groups(account, resource, refresh_timedelta=None):
"""Fetch account groups from resource if necessary."""
updated = datetime.utcnow()
modified_since = updated
if refresh_timedelta is not None:
modified_since += refresh_timedelta
modified_since = modified_since.isoformat()
last_update = account.extra_data.get('updated', modified_since)
#if last_update > modified_since:
groups_db = account.extra_data.get('groups', [])
if groups_db is not None and groups_db:
return account.extra_data.get('groups', [])
groups = fetch_groups(resource['Group'])
account.extra_data.update(
groups=groups,
updated=updated.isoformat(),
)
return groups
```
This means the groups will be taken upon the first login of the user and never updated. A fix will come soon.
## Setup
An instance can be deployed using the OpenShift template (can be found in _template/cern-search-api.yml_)
......
......@@ -92,3 +92,12 @@ RECORDS_REST_ENDPOINTS = dict(
# error_handlers={}, # TODO
)
)
# Flask Security
# ==============
# Avoid error upon registration with email sending
# FIXME flask_security/registrable:40 "Too many values to unpack"
SECURITY_SEND_REGISTER_EMAIL = False
SECURITY_CONFIRM_REGISTRATION = False
SECURITY_CONFIRMABLE = False
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import sys
from invenio_base.app import create_app_factory
from invenio_config import create_conf_loader
from . import config
env_prefix = 'INVENIO'
conf_loader = create_conf_loader(config=config, env_prefix=env_prefix)
instance_path = os.getenv(env_prefix + '_INSTANCE_PATH') or \
os.path.join(sys.prefix, 'var', 'instance')
"""Path to instance folder.
Defaults to ``<virtualenv>/var/instance/``. Can be overwritten using the
environment variable ``APP_INSTANCE_PATH``.
"""
create_api = create_app_factory(
'cern_search',
config_loader=conf_loader,
blueprint_entry_points=['invenio_base.api_blueprints'],
extension_entry_points=['invenio_base.api_apps'],
converter_entry_points=['invenio_base.api_converters'],
instance_path=instance_path,
)
\ No newline at end of file
......@@ -4,6 +4,20 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"_access": {
"type": "object",
"properties": {
"read":{
"type": "string"
},
"update":{
"type": "string"
},
"delete":{
"type": "string"
}
}
},
"title": {
"type": "string",
"description": "Record title."
......
......@@ -6,10 +6,27 @@
"mappings": {
"test-doc_v0.0.1": {
"numeric_detection": true,
"_meta": {
"_owner": "CernSearch-Administrators@cern.ch"
},
"_all": {
"analyzer": "english"
},
"properties": {
"_access": {
"type": "nested",
"properties": {
"read": {
"type": "string"
},
"update": {
"type": "string"
},
"delete": {
"type": "string"
}
}
},
"title": {
"type": "string",
"analyzer": "english"
......
......@@ -2,7 +2,10 @@
# -*- coding: utf-8 -*-
from flask_security import current_user
from flask import request
from flask import request, g
from invenio_search import current_search, current_search_client
from invenio_search.utils import schema_to_index
"""Access control for CERN Search."""
......@@ -60,13 +63,13 @@ class RecordPermission(object):
"""Create a record permission."""
# Allow everything for testing
if action in cls.create_actions:
return cls(record, allow, user) # return cls(record, has_admin_permission, user)
return cls(record, has_create_permission, user)
elif action in cls.read_actions:
return cls(record, allow, user) # return cls(record, has_read_record_permission, user)
return cls(record, has_read_record_permission, user)
elif action in cls.update_actions:
return cls(record, allow, user) # return cls(record, has_update_permission, user)
return cls(record, has_update_permission, user)
elif action in cls.delete_actions:
return cls(record, allow, user) # return cls(record, has_admin_permission, user)
return cls(record, has_delete_permission, user)
else:
return cls(record, deny, user)
......@@ -76,22 +79,36 @@ def has_create_permission(user, record):
if user.is_authenticated:
# Allow based in the '_access' key
user_provides = get_user_provides()
search = request._methodview.search_class()
mapping = search.get_mapping(str(record.index))
# set.isdisjoint() is faster than set.intersection()
create_access_groups = mapping['_meta']['_owner']
if user_provides and not set(user_provides).isdisjoint(set(create_access_groups)):
return True
user_index = request.args.get("index")
index_exists, es_index = parse_index(user_index)
if index_exists and current_search_client.indices.exists([es_index]):
# TODO How to query the index to get the owner?
mapping = current_search_client.indices.get_mapping([es_index])
if mapping is not None:
# set.isdisjoint() is faster than set.intersection()
create_access_groups = mapping[es_index]['mappings'][user_index]['_meta']['_owner'].split(',')
if user_provides and not set(user_provides).isdisjoint(set(create_access_groups)):
return True
return False
INDEX_PREFIX = 'cernsearch'
def parse_index(index):
if index is not None:
return True, '{0}-{1}'.format(INDEX_PREFIX, index)
else:
return False, None
def has_update_permission(user, record):
"""Check if user is authenticated and has update access"""
if user.is_authenticated:
# Allow based in the '_access' key
user_provides = get_user_provides()
# set.isdisjoint() is faster than set.intersection()
update_access_groups = record['_access']['update']
update_access_groups = record['_access']['update'].split(',')
if user_provides and not set(user_provides).isdisjoint(set(update_access_groups)):
return True
return False
......@@ -103,7 +120,7 @@ def has_read_record_permission(user, record):
# Allow based in the '_access' key
user_provides = get_user_provides()
# set.isdisjoint() is faster than set.intersection()
read_access_groups = record['_access']['read']
read_access_groups = record['_access']['read'].split(',')
if user_provides and not set(user_provides).isdisjoint(set(read_access_groups)):
return True
return False
......@@ -115,7 +132,7 @@ def has_delete_permission(user, record):
# Allow based in the '_access' key
user_provides = get_user_provides()
# set.isdisjoint() is faster than set.intersection()
delete_access_groups = record['_access']['delete']
delete_access_groups = record['_access']['delete'].split(',')
if user_provides and not set(user_provides).isdisjoint(set(delete_access_groups)):
return True
return False
......
......@@ -8,4 +8,4 @@ and parsed by ``setup.py``.
from __future__ import absolute_import, print_function
__version__ = '0.0.1a1'
\ No newline at end of file
__version__ = '0.0.1a2'
\ No newline at end of file
......@@ -3,8 +3,9 @@
"""CERN Search WSGI app instantiation."""
from __future__ import absolute_import, division, print_function
from .factory import create_api
from __future__ import absolute_import, print_function
application = create_api()
\ No newline at end of file
from invenio_app.wsgi import application
__all__ = ('application', )
\ No newline at end of file
flask
gunicorn
invenio-access>=1.0.0b1
invenio-app>=1.0.0b1,<1.1.0
invenio-base>=1.0.0a15,<1.1.0
invenio-config>=1.0.0b3,<1.1.0
invenio-jsonschemas>=1.0.0a5,<1.1.0
invenio-records-rest>=1.0.0b1,<1.1.0
invenio-records[postgresql]>=1.0.0b2
invenio-rest[cors]>=1.0.0b2
invenio-oauthclient>=1.0.0b5
invenio-search[elasticsearch5]>=1.0.0a10,<1.1.0
redis>=2.10.0
invenio-access>=1.0.0,<1.1.0
invenio-admin>=1.0.0,<1.1.0
invenio-accounts>=1.0.0,<1.1.0
invenio-app>=1.0.0,<1.1.0
invenio-base>=1.0.0,<1.1.0
invenio-config>=1.0.0,<1.1.0
invenio-indexer[elasticsearch5]>=1.0.0,<1.1.0
invenio-jsonschemas>=1.0.0,<1.1.0
invenio-records-rest[elasticsearch5]>=1.0.0,<1.1.0
invenio-records[postgresql]>=1.0.0,<1.1.0
invenio-rest[cors]>=1.0.0,<1.1.0
invenio-oauthclient>=1.0.0,<1.1.0
invenio_oauth2server>=1.0.0,<1.1.0
invenio-search[elasticsearch5]>=1.0.0,<1.1.0
invenio-theme>=1.0.0,<1.1.0
redis>=2.10.0
\ No newline at end of file
......@@ -43,6 +43,8 @@ install_requires = [
'flask',
'gunicorn',
'invenio-access>=1.0.0,<1.1.0',
'invenio-admin>=1.0.0,<1.1.0',
'invenio-accounts>=1.0.0,<1.1.0',
'invenio-app>=1.0.0,<1.1.0',
'invenio-base>=1.0.0,<1.1.0',
'invenio-config>=1.0.0,<1.1.0',
......@@ -52,7 +54,9 @@ install_requires = [
'invenio-records[postgresql]>=1.0.0,<1.1.0',
'invenio-rest[cors]>=1.0.0,<1.1.0',
'invenio-oauthclient>=1.0.0,<1.1.0',
'invenio_oauth2server>=1.0.0,<1.1.0',
'invenio-search[elasticsearch5]>=1.0.0,<1.1.0',
'invenio-theme>=1.0.0,<1.1.0',
'redis>=2.10.0',
]
......@@ -87,11 +91,9 @@ setup(
'invenio_jsonschemas.schemas': [
'cern_search_rest_schemas = cern_search_rest.modules.cernsearch.jsonschemas'
],
'invenio_base.api_blueprints': [
'invenio_oauthclient = invenio_oauthclient.views.client:blueprint'
]
},
extras_require=extras_require,
install_requires=install_requires,
setup_requires=setup_requires,
tests_require=tests_require,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment