Commit 1be5dde7 authored by Pablo Panero's avatar Pablo Panero
Browse files

Merge branch 'admin_permission' into 'master'

Admin permission

See merge request ppanero/cern_search_rest!18
parents a35dfa4a 45c46fbd
......@@ -24,3 +24,7 @@ secrets/
# Local env source
env.sh
# Debug and other logs
*.log
......@@ -30,10 +30,23 @@ CERN_REMOTE_APP["params"].update(dict(request_token_params={
"scope": "Name Email Bio Groups",
}))
CERN_REMOTE_APP["authorized_handler"] = 'cern_search_rest.modules.cernsearch.handlers:cern_authorized_signup_handler'
OAUTHCLIENT_REMOTE_APPS = dict(
cern=CERN_REMOTE_APP,
)
# Accounts
# ========
# FIXME: Needs to be disable for role base auth in SSO. If not invenio_account/sessions:login_listener will crash
ACCOUNTS_SESSION_ACTIVITY_ENABLED = False
# Admin
# =====
ADMIN_PERMISSION_FACTORY = 'cern_search_rest.modules.cernsearch.permissions:admin_permission_factory'
# JSON Schemas configuration
# ==========================
......@@ -98,7 +111,6 @@ RECORDS_REST_ENDPOINTS = dict(
)
)
# Flask Security
# ==============
# Avoid error upon registration with email sending
......@@ -106,3 +118,5 @@ RECORDS_REST_ENDPOINTS = dict(
SECURITY_SEND_REGISTER_EMAIL = False
SECURITY_CONFIRM_REGISTRATION = False
SECURITY_CONFIRMABLE = False
SECURITY_REGISTERABLE = False # Avoid user registration outside of CERN SSO
SECURITY_RECOVERABLE = False # Avoid user password recovery
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Handlers for customizing oauthclient endpoints."""
from __future__ import absolute_import, print_function
from flask import current_app, redirect, session, url_for, g, after_this_request
from flask_login import current_user, user_logged_in
from flask_security import logout_user
from flask_security.utils import get_post_logout_redirect
from invenio_db import db
from invenio_oauthclient.proxies import current_oauthclient
from invenio_oauthclient.signals import account_info_received, account_setup_committed, \
account_setup_received
from invenio_oauthclient.utils import oauth_authenticate, oauth_get_user, create_csrf_disabled_registrationform, \
fill_form, oauth_register
from invenio_oauthclient.handlers import oauth_error_handler, token_session_key, response_token_setter, \
token_getter, get_session_next_url
from cern_search_rest.modules.cernsearch.utils import get_user_provides
@oauth_error_handler
def cern_authorized_signup_handler(resp, remote, *args, **kwargs):
"""Handle sign-in/up functionality.
:param remote: The remote application.
:param resp: The response.
:returns: Redirect response.
"""
# Remove any previously stored auto register session key
session.pop(token_session_key(remote.name) + '_autoregister', None)
# Store token in session
# ----------------------
# Set token in session - token object only returned if
# current_user.is_autenticated().
token = response_token_setter(remote, resp)
handlers = current_oauthclient.signup_handlers[remote.name]
# Sign-in/up user
# ---------------
if not current_user.is_authenticated:
account_info = handlers['info'](resp)
account_info_received.send(
remote, token=token, response=resp, account_info=account_info
)
user = oauth_get_user(
remote.consumer_key,
account_info=account_info,
access_token=token_getter(remote)[0],
)
if user is None:
# Auto sign-up if user not found
form = create_csrf_disabled_registrationform()
form = fill_form(
form,
account_info['user']
)
user = oauth_register(form)
# if registration fails ...
if user is None:
# requires extra information
session[
token_session_key(remote.name) + '_autoregister'] = True
session[token_session_key(remote.name) +
'_account_info'] = account_info
session[token_session_key(remote.name) +
'_response'] = resp
db.session.commit()
return redirect(url_for(
'.signup',
remote_app=remote.name,
))
# Authenticate user
if not oauth_authenticate(remote.consumer_key, user,
require_existing_link=False):
return current_app.login_manager.unauthorized()
# Link account
# ------------
# Need to store token in database instead of only the session when
# called first time.
token = response_token_setter(remote, resp)
# Setup account
# -------------
if not token.remote_account.extra_data:
account_setup = handlers['setup'](token, resp)
account_setup_received.send(
remote, token=token, response=resp, account_setup=account_setup
)
db.session.commit()
account_setup_committed.send(remote, token=token)
else:
db.session.commit()
# Redirect to next
if current_user.is_authenticated and not egroup_admin():
logout_user()
return redirect(get_post_logout_redirect())
next_url = get_session_next_url(remote.name)
if next_url:
return redirect(next_url)
return redirect(url_for('invenio_oauthclient_settings.index'))
def egroup_admin():
admin_access_groups = current_app.config['ADMIN_VIEW_ACCESS_GROUPS']
# Allow based in the '_access' key
user_provides = get_user_provides()
# set.isdisjoint() is faster than set.intersection()
admin_access_groups = admin_access_groups.split(',')
if user_provides and not set(user_provides).isdisjoint(set(admin_access_groups)):
return True
return False
......@@ -2,9 +2,11 @@
# -*- coding: utf-8 -*-
from flask_security import current_user
from flask import request, g
from flask import request, g, current_app
from invenio_search import current_search_client
from cern_search_rest.modules.cernsearch.utils import get_user_provides
"""Access control for CERN Search."""
......@@ -136,6 +138,45 @@ def has_delete_permission(user, record):
return False
"""Access control for CERN Search Admin Web UI."""
def admin_permission_factory(view):
"""Record permission factory."""
return AdminPermission.create(view=view)
class AdminPermission(object):
def __init__(self, func, user, view):
"""Initialize a file permission object."""
self.user = user or current_user
self.func = func
self.view = view
def can(self):
"""Determine access."""
return self.func(self.user)
@classmethod
def create(cls, user=None, view=None):
"""Create a record permission."""
# Allow everything for testing
return cls(has_admin_view_permission, user, view)
def has_admin_view_permission(user):
admin_access_groups = current_app.config['ADMIN_VIEW_ACCESS_GROUPS']
if user.is_authenticated and admin_access_groups:
# Allow based in the '_access' key
user_provides = get_user_provides()
# set.isdisjoint() is faster than set.intersection()
admin_access_groups = admin_access_groups.split(',')
if user_provides and not set(user_provides).isdisjoint(set(admin_access_groups)):
return True
return False
# Utility functions
......@@ -155,8 +196,3 @@ def is_public(data, action):
the action is not inside access or is empty.
"""
return '_access' not in data or not data.get('_access', {}).get(action)
def get_user_provides():
"""Extract the user's provides from g."""
return [need.value for need in g.identity.provides]
......@@ -337,18 +337,22 @@ objects:
INVENIO_THEME_SITENAME: ${SITE_NAME}
INVENIO_THEME_LOGO: ${LOGO_PATH}
INVENIO_THEME_LOGO_ADMIN: ${LOGO_PATH}
INVENIO_ADMIN_VIEW_ACCESS_GROUPS: ${ADMIN_UI_ACCESS_LIST}
parameters:
- name: APP_INSTANCE_PATH
description: "Invenio instance path for CERN Search application."
required: true
value: /usr/local/var/cernsearch/var/cernsearch-instance
- name: APP_ALLOWED_HOSTS
description: "Invenio App allowed hosts. Without protocol (e.g. http) nor salsh ('/') at the end"
required: true
value: ['test-cern-search.web.cern.ch']
- name: SITE_NAME
description: "CERN Search site name (E.g. EDMS, Indico, etc.)"
required: true
description: "CERN Search site name (E.g. EDMS, Indico, etc.)"
value: CERN Search
- name: LOGO_PATH
description: "CERN Search site logo (E.g. /images/cernsearchicon.png)"
required: true
\ No newline at end of file
description: "CERN Search site logo (E.g. /images/cernsearchicon.png)"
value: /images/cernsearchicon.png
- name: ADMIN_UI_ACCESS_LIST
description: "List of comma separated egroups that have access to the ADMIN UI (e.g. 'egroup_one,egroup_two')"
value: "CernSearch-Administrators"
Supports Markdown
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