Skip to content

Commit

Permalink
monitoring: access token
Browse files Browse the repository at this point in the history
* Add monitoring user and access token for setup.

Co-Authored-by: Peter Weber <[email protected]>
  • Loading branch information
rerowep committed Jan 8, 2024
1 parent bf8742c commit ddba779
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 29 deletions.
25 changes: 20 additions & 5 deletions scripts/setup
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ invenio queues declare

# Create admin role to restrict access
section "Create roles for users" "info"
invenio roles create superuser
invenio roles create admin
invenio roles create moderator
invenio roles create submitter
invenio roles create user
invenio roles create superuser -d SuperUser
invenio roles create admin -d Admin
invenio roles create moderator -d Moderator
invenio roles create submitter -d Submitter
invenio roles create user -d User

section "Initialize access for roles" "info"
invenio access allow superuser-access role superuser
Expand All @@ -111,6 +111,21 @@ invenio access allow admin-access role moderator
invenio access allow admin-access role submitter
message "Done" "success"

section "Create user for monitoring" "info"
invenio users create -a [email protected] --password monitor
invenio users confirm [email protected]
invenio roles create monitoring -d Monitoring
invenio roles add [email protected] monitoring
# create token access for monitoring
# if the environement variable INVENIO_RERO_ACCESS_TOKEN_MONITORING is not set
# a new token will be generated
if [ -z ${INVENIO_RERO_ACCESS_TOKEN_MONITORING} ]
then
eval ${PREFIX} invenio utils tokens_create -n monitoring -u [email protected]
else
eval ${PREFIX} invenio utils tokens_create -n monitoring -u [email protected] -t ${INVENIO_RERO_ACCESS_TOKEN_MONITORING}
fi

# Create a default location for depositing files
section "Create location for storing files" "info"
invenio fixtures deposits create
Expand Down
115 changes: 102 additions & 13 deletions sonar/modules/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,24 @@
import jsonref
from flask import current_app
from flask.cli import with_appcontext
from flask_security.confirmable import confirm_user
from invenio_accounts.cli import commit, users
from invenio_db import db
from invenio_files_rest.models import Location
from invenio_jsonschemas import current_jsonschemas
from invenio_oauth2server.cli import process_scopes, process_user
from invenio_oauth2server.models import Client, Token
from invenio_records_rest.utils import obj_or_import_string
from invenio_search.cli import search_version_check
from invenio_search.proxies import current_search
from jsonref import jsonloader
from werkzeug.local import LocalProxy
from werkzeug.security import gen_salt

from sonar.modules.api import SonarRecord

_datastore = LocalProxy(lambda: current_app.extensions['security'].datastore)


@click.group()
def utils():
Expand Down Expand Up @@ -71,9 +80,10 @@ def clear_files():
try:
shutil.rmtree(location.uri)
except Exception:
click.secho('Directory {directory} cannot be cleaned'.format(
directory=location.uri),
fg='yellow')
click.secho(
f'Directory {location.uri} cannot be cleaned',
fg='yellow'
)

click.secho('Finished', fg='green')

Expand Down Expand Up @@ -119,16 +129,14 @@ def export(pid_type, serializer_key, output_dir):
:param pid_type: record type
:param output_dir: Output directory
"""
click.secho('Export "{pid_type}" records in {dir}'.format(
pid_type=pid_type, dir=output_dir.name))
click.secho(f'Export "{pid_type}" records in {output_dir.name}')

try:
# Get the correct record class
record_class = SonarRecord.get_record_class_by_pid_type(pid_type)

if not record_class:
raise Exception('No record class found for type "{type}"'.format(
type=pid_type))
raise Exception(f'No record class found for type "{pid_type}"')

# Load the serializer
serializer_class = current_app.config.get(
Expand Down Expand Up @@ -164,8 +172,7 @@ def export(pid_type, serializer_key, output_dir):
exist_ok=True)
shutil.copyfile(file['uri'], target_path)
file.pop('uri')
file['path'] = './{pid}/{key}'.format(pid=pid,
key=file['key'])
file['path'] = f'./{pid}/{file["key"]}'

records.append(record)

Expand All @@ -178,7 +185,89 @@ def export(pid_type, serializer_key, output_dir):

click.secho('Finished', fg='green')

except Exception as exception:
click.secho('An error occured during export: {error}'.format(
error=str(exception)),
fg='red')
except Exception as err:
click.secho(f'An error occured during export: {err}', fg='red')


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 ""

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

if not access_token:
access_token = gen_salt(
current_app.config.get(
'OAUTH2SERVER_TOKEN_PERSONAL_SALT_LEN')
)
token = Token(
client_id=client.client_id,
user_id=user_id,
access_token=access_token,
expires=None,
_scopes=scopes,
is_personal=True,
is_internal=is_internal,
)
db.session.add(client)
db.session.add(token)

return token


@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')


@users.command()
@click.argument('user')
@with_appcontext
@commit
def confirm(user):
"""Confirm a user."""
user_obj = _datastore.get_user(user)
if user_obj is None:
raise click.UsageError('ERROR: User not found.')
if confirm_user(user_obj):
click.secho(f'User "{user}" has been confirmed.', fg='green')
else:
click.secho('User "{user}" was already confirmed.', fg='yellow')
2 changes: 2 additions & 0 deletions sonar/modules/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
moderator_access_permission = Permission(RoleNeed('moderator'),
RoleNeed('admin'),
RoleNeed('superuser'))
monitoring_access_permission = Permission(RoleNeed('superuser'),
RoleNeed('monitoring'))

# Allow access without permission check
allow_access = type('Allow', (), {'can': lambda self: True})()
Expand Down
26 changes: 15 additions & 11 deletions sonar/monitoring/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,37 @@
from redis import Redis

from sonar.modules.documents.urn import Urn
from sonar.modules.permissions import superuser_access_permission
from sonar.modules.permissions import monitoring_access_permission
from sonar.monitoring.api.data_integrity import DataIntegrityMonitoring
from sonar.monitoring.api.database import DatabaseMonitoring

api_blueprint = Blueprint('monitoring_api', __name__, url_prefix='/monitoring')


def is_superuser(func):
"""Decorator checking if a user is logged and has role `superuser`."""
def is_monitoring_user(func):
"""
Decorator checking if a user is logged and has
role `superuser`, `monitoring`.
"""

@wraps(func)
def decorated_view(*args, **kwargs):
print('--->', current_user)
if not current_user.is_authenticated:
return jsonify({'error': 'Unauthorized'}), 401

if not superuser_access_permission.can():
if not monitoring_access_permission.require().can():
return jsonify({'error': 'Forbidden'}), 403

return func(*args, **kwargs)

return decorated_view


@api_blueprint.before_request
@is_superuser
def check_for_superuser():
"""Check if user is superuser before each request, with decorator."""
@is_monitoring_user
def check_for_monitoring_user():
"""
Check if user is superuser or monitoring before each request,
with decorator.
"""


@api_blueprint.route('/db_connection_counts')
Expand All @@ -65,7 +69,7 @@ def db_connection_count():
return jsonify({'error': str(exception)}), 500


@api_blueprint.route('db_connections')
@api_blueprint.route('/db_connections')
def db_activity():
"""Current database activity."""
try:
Expand Down

0 comments on commit ddba779

Please sign in to comment.