From a2b7ebac6c18db61c6522f7170783f516b960f5e Mon Sep 17 00:00:00 2001 From: hevp Date: Fri, 5 Feb 2021 09:55:03 +0100 Subject: [PATCH 01/11] Add user list CLI command --- b2share/modules/users/cli.py | 50 ++++++++++++++++++++++++++++++++++++ b2share/modules/users/ext.py | 24 +++++++++++++++-- 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 b2share/modules/users/cli.py diff --git a/b2share/modules/users/cli.py b/b2share/modules/users/cli.py new file mode 100644 index 0000000000..e3561a4a32 --- /dev/null +++ b/b2share/modules/users/cli.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# +# This file is part of EUDAT B2Share. +# Copyright (C) 2017 University of Tübingen, CERN +# +# B2Share is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# B2Share is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with B2Share; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +# +# In applying this license, CERN does not +# waive the privileges and immunities granted to it by virtue of its status +# as an Intergovernmental Organization or submit itself to any jurisdiction. + +"""B2Share cli commands for records.""" + + +from __future__ import absolute_import, print_function + +import click +from flask.cli import with_appcontext +import requests + +from invenio_accounts.cli import users +from invenio_accounts.models import User + +@users.command('list') +@with_appcontext +def users_list(): + """List all known users""" + + userdata = User.query.order_by(User.id) + + click.secho("ID\tACTIVE\tEMAIL\t\t\t\t\tROLES") + for u in userdata: + click.secho("%s\t%s\t%s\t\t\t%s" % ( + u.id, + u.active, + u.email, + u.roles if len(u.roles) else 'None' + )) diff --git a/b2share/modules/users/ext.py b/b2share/modules/users/ext.py index 1512e512d2..a8ca5da8a2 100644 --- a/b2share/modules/users/ext.py +++ b/b2share/modules/users/ext.py @@ -22,8 +22,24 @@ from __future__ import absolute_import, print_function +from werkzeug.utils import cached_property + from .views import blueprint +from .cli import users as users_cmd + + +class _B2ShareUsersState(object): + """B2Share users extension state.""" + + def __init__(self, app): + """Constructor. + + Args: + app: the Flask application. + """ + self.app = app + class B2ShareUsers(object): """B2Share Users extension.""" @@ -36,9 +52,13 @@ def __init__(self, app=None): def init_app(self, app): """Flask application initialization.""" self.init_config(app) - app.extensions['b2share-users'] = self + app.cli.add_command(users_cmd) app.register_blueprint(blueprint) + app.extensions['b2share-users'] = _B2ShareUsersState(app) def init_config(self, app): """Initialize configuration.""" - pass + # for k in dir(config): + # if k.startswith('B2SHARE_USERS_'): + # app.config.setdefault(k, getattr(config, k)) + pass \ No newline at end of file From 4abaa4c2c88e028b0487f291fff8ba4558f6737f Mon Sep 17 00:00:00 2001 From: hevp Date: Fri, 5 Feb 2021 17:01:51 +0100 Subject: [PATCH 02/11] Add community policies list CLI command --- b2share/modules/communities/cli.py | 48 ++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/b2share/modules/communities/cli.py b/b2share/modules/communities/cli.py index 3ede7d2143..12aeb27c82 100644 --- a/b2share/modules/communities/cli.py +++ b/b2share/modules/communities/cli.py @@ -25,17 +25,17 @@ from __future__ import absolute_import -import os +import os, json from os.path import isfile import click from flask.cli import with_appcontext +from flask import current_app, url_for, jsonify from invenio_db import db from .api import Community, CommunityDoesNotExistError - @click.group() def communities(): """communities management commands.""" @@ -145,7 +145,43 @@ def edit(verbose, id, name, description, logo, clear_fields): @communities.command() @with_appcontext @click.argument('community') -@click.argument('json_file') -def set_schema(community, json_file): - from b2share.modules.schemas.cli import update_or_set_community_schema - update_or_set_community_schema(community, json_file) +@click.argument('json_file', required=False, default=None) +@click.option('--root-schema', required=False, default=None, type=int) +def set_schema(community, json_file, root_schema): + """Set the community block schema and/or root schema version. + If a root schema version is given, but no JSON file, the latest known block schema will be used (if present).""" + from b2share.modules.schemas.cli import update_or_set_community_schema, update_or_set_community_root_schema + if json_file: + update_or_set_community_schema(community, json_file, root_schema) + elif root_schema: + update_or_set_community_root_schema(community, root_schema) + else: + raise click.BadParameter("Need at least a JSON file or root schema version") + + +@communities.group() +@with_appcontext +def policies(): + """Manage community policies""" + +@policies.command('list') +@with_appcontext +@click.argument('community', required=False) +def community_policies_list(community=None): + """List all communities' policy values""" + + def list_item(comm): + click.secho("%s\t%s\t\t%s\t%s" % ( + comm.id, + comm.name, + comm.publication_workflow, + comm.restricted_submission + )) + + click.secho("ID\t\t\t\t\tNAME\t\tWORKFLOW\tMEMBERS-ONLY") + if not community: + for c in Community.get_all(): + list_item(c) + else: + list_item(Community.get(community)) +>>>>>>> 0265fa602 (Add community policies list CLI command) From 44067ea8d97cb7a2b83ca83f673b9ef8a0b4d65a Mon Sep 17 00:00:00 2001 From: hevp Date: Fri, 5 Feb 2021 17:58:20 +0100 Subject: [PATCH 03/11] Add community policy update CLI command --- b2share/modules/communities/api.py | 31 ++++++++++++++++- b2share/modules/communities/cli.py | 46 ++++++++++++++++++++++--- b2share/modules/communities/errors.py | 8 +++++ b2share/modules/communities/policies.py | 36 +++++++++++++++++++ 4 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 b2share/modules/communities/policies.py diff --git a/b2share/modules/communities/api.py b/b2share/modules/communities/api.py index e4d6f5e8d2..96cac517d6 100644 --- a/b2share/modules/communities/api.py +++ b/b2share/modules/communities/api.py @@ -36,12 +36,13 @@ from invenio_accounts.models import Role from .errors import CommunityDeletedError, CommunityDoesNotExistError, \ - InvalidCommunityError + InvalidCommunityError, CommunityPolicyDoesNotExistError, CommunityPolicyInvalidValueError from .signals import after_community_delete, after_community_insert, \ after_community_update, before_community_delete, before_community_insert, \ before_community_update from .models import Community as CommunityMetadata, _community_admin_role_name, \ _community_member_role_name +from .policies import PolicyValuesMapping class Community(object): @@ -225,6 +226,34 @@ def patch(self, patch): self.update(data) return self + def policy(self, name, value): + """Update the community's policy with value. + + Args: + policy (string): the name of the policy + + Returns: + :class:`Community`: self + + Raises: + jsonpatch.JsonPatchConflict: the json patch conflicts on the + community. + + jsonpatch.InvalidJsonPatch: the json patch is invalid. + + b2share.modules.communities.errors.InvalidCommunityError: The + community patch failed because the resulting community is + not valid. + """ + + if not name in PolicyValuesMapping.keys(): + raise CommunityPolicyDoesNotExistError() + if not value in PolicyValuesMapping[name]: + raise CommunityPolicyInvalidValueError() + + return self.update({name: value}) + + # TODO: add properties for getting schemas and admins def delete(self): diff --git a/b2share/modules/communities/cli.py b/b2share/modules/communities/cli.py index 12aeb27c82..05b3c157a2 100644 --- a/b2share/modules/communities/cli.py +++ b/b2share/modules/communities/cli.py @@ -25,16 +25,16 @@ from __future__ import absolute_import -import os, json +import os from os.path import isfile import click from flask.cli import with_appcontext -from flask import current_app, url_for, jsonify from invenio_db import db -from .api import Community, CommunityDoesNotExistError +from .api import Community, CommunityDoesNotExistError, \ + CommunityPolicyDoesNotExistError, CommunityPolicyInvalidValueError @click.group() def communities(): @@ -184,4 +184,42 @@ def list_item(comm): list_item(c) else: list_item(Community.get(community)) ->>>>>>> 0265fa602 (Add community policies list CLI command) + + +@policies.command('set') +@with_appcontext +@click.option('-v', '--verbose', is_flag=True, default=False) +@click.argument('community') +@click.argument('policy') +@click.argument('value', required=False) +@click.option('--enable', 'enable', flag_value=True) +@click.option('--disable', 'enable', flag_value=False, default=True) +def community_policies_set(verbose, community, policy, enable=None, value=None): + """Enable/disable/set the value for a given community and policy.""" + + from .policies import PolicyValuesMapping, PolicyToggleValues + + # if a value is omitted, use the enable flag and use its value + if value is None: + if not enable is None: + value = enable + else: + raise click.BadParameter("No value given or use --enable or --disable flag") + + try: + comm = Community.get(id=community) + comm.policy(policy, value) + except CommunityDoesNotExistError: + raise click.BadParameter("No such community with ID %s" % community) + except CommunityPolicyDoesNotExistError: + raise click.BadParameter("No such community policy '%s', choose from: '%s'" % (policy, "', '".join(PolicyValuesMapping.keys()))) + except CommunityPolicyInvalidValueError: + if PolicyValuesMapping[policy] == PolicyToggleValues: + raise click.BadParameter("Invalid value '%s' for policy '%s', use --enable or --disable flag" % (value, policy)) + else: + raise click.BadParameter("Invalid value '%s' for policy '%s', choose from: '%s'" % (value, policy, "', '".join([str(x) for x in PolicyValuesMapping[policy]]))) + + db.session.commit() + if verbose: + click.echo("Community policy '%s' updated" % policy) +>>>>>>> 19031bc32 (Add community policy update CLI command) diff --git a/b2share/modules/communities/errors.py b/b2share/modules/communities/errors.py index ea8065a955..bdcf15320c 100644 --- a/b2share/modules/communities/errors.py +++ b/b2share/modules/communities/errors.py @@ -55,3 +55,11 @@ class NotACommunityRoleError(RESTException): code = 400 description = 'This role doesn\'t belong to any community.' + +class CommunityPolicyDoesNotExistError(Exception): + """Exception raised when a requested community policy does not exist.""" + pass + +class CommunityPolicyInvalidValueError(Exception): + """Exception raised when a community policy value is invalid.""" + pass diff --git a/b2share/modules/communities/policies.py b/b2share/modules/communities/policies.py new file mode 100644 index 0000000000..f0bcafa488 --- /dev/null +++ b/b2share/modules/communities/policies.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# +# This file is part of EUDAT B2Share. +# Copyright (C) 2016 University of Tuebingen, CERN +# Copyright (C) 2015 University of Tuebingen. +# +# B2Share is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# B2Share is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with B2Share; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +# +# In applying this license, CERN does not +# waive the privileges and immunities granted to it by virtue of its status +# as an Intergovernmental Organization or submit itself to any jurisdiction. + +"""Communities interface and API.""" + +from __future__ import absolute_import + +from .workflows import publication_workflows + +PolicyToggleValues = [True, False] + +PolicyValuesMapping = { + 'restricted_submission': PolicyToggleValues, + 'publication_workflow': publication_workflows.keys() +} From 70b932441e638601ac70c10e9beedd930f4bafe1 Mon Sep 17 00:00:00 2001 From: hevp Date: Fri, 5 Feb 2021 18:06:10 +0100 Subject: [PATCH 04/11] Move b2records command to subcommand manage in records command --- b2share/modules/records/cli.py | 16 ++++++++-------- b2share/modules/records/ext.py | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/b2share/modules/records/cli.py b/b2share/modules/records/cli.py index df809cb25a..9c54444842 100644 --- a/b2share/modules/records/cli.py +++ b/b2share/modules/records/cli.py @@ -34,6 +34,7 @@ from invenio_pidstore.models import PIDStatus from invenio_pidstore.providers.datacite import DataCiteProvider from invenio_records_files.api import Record +from invenio_records.cli import records from b2share.modules.deposit.api import create_file_pids from b2share.modules.records.serializers import datacite_v31 @@ -46,12 +47,11 @@ from b2share.modules.handle.proxies import current_handle -@click.group() -def b2records(): - """B2SHARE Records commands.""" +@records.group() +def manage(): + """B2SHARE record management commands.""" - -@b2records.command() +@manage.command() @with_appcontext def update_expired_embargoes(): """Updates all records with expired embargoes to open access.""" @@ -59,7 +59,7 @@ def update_expired_embargoes(): click.secho('Expiring embargoes...', fg='green') -@b2records.command() +@manage.command() @with_appcontext @click.option('-u', '--update', is_flag=True, default=False, help='updates if necessary') @@ -104,7 +104,7 @@ def check_and_update_handle_records(update, verbose): click.secho(' file PID ok: {}'.format(pid)) -@b2records.command() +@manage.command() @with_appcontext @click.option('-u', '--update', is_flag=True, default=False) @click.argument('record_pid', required=True) @@ -147,7 +147,7 @@ def check_handles(update, record_pid): db.session.commit() -@b2records.command() +@manage.command() @with_appcontext @click.option('-r', '--record', default=None) @click.option('-a', '--allrecords', is_flag=True, default=False) diff --git a/b2share/modules/records/ext.py b/b2share/modules/records/ext.py index 91c3cee232..d10c620acb 100644 --- a/b2share/modules/records/ext.py +++ b/b2share/modules/records/ext.py @@ -34,7 +34,7 @@ from .errors import register_error_handlers from .views import create_blueprint from .indexer import indexer_receiver -from .cli import b2records +from .cli import records as records_cmd class B2ShareRecords(object): @@ -48,7 +48,7 @@ def __init__(self, app=None): def init_app(self, app): """Flask application initialization.""" self.init_config(app) - app.cli.add_command(b2records) + app.cli.add_command(records_cmd) app.extensions['b2share-records'] = self register_triggers(app) register_error_handlers(app) From 6a682d415970cba65af68375a4f38349d58b785e Mon Sep 17 00:00:00 2001 From: hevp Date: Mon, 15 Feb 2021 12:07:54 +0100 Subject: [PATCH 05/11] Fix community not rendering without schema defined --- webui/src/components/schema.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webui/src/components/schema.jsx b/webui/src/components/schema.jsx index 09cbc13446..e8186fe67d 100644 --- a/webui/src/components/schema.jsx +++ b/webui/src/components/schema.jsx @@ -94,6 +94,9 @@ export const Schema = React.createClass({ return ; } const jschema = schema.get('json_schema'); + if (jschema.code == 404) { + return
Community has no schema defined yet
+ } const [majors, minors] = getSchemaOrderedMajorAndMinorFields(jschema); return (
From b0672c09d5df94b9b3b2f1df85a88125e55f2e97 Mon Sep 17 00:00:00 2001 From: hevp Date: Mon, 15 Feb 2021 12:09:05 +0100 Subject: [PATCH 06/11] Add no_block flag for community schema version creation commands --- b2share/modules/communities/cli.py | 9 +++--- b2share/modules/schemas/cli.py | 50 ++++++++++++++++++------------ 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/b2share/modules/communities/cli.py b/b2share/modules/communities/cli.py index 05b3c157a2..189e74ce08 100644 --- a/b2share/modules/communities/cli.py +++ b/b2share/modules/communities/cli.py @@ -146,17 +146,18 @@ def edit(verbose, id, name, description, logo, clear_fields): @with_appcontext @click.argument('community') @click.argument('json_file', required=False, default=None) +@click.option('--no-block', required=False, is_flag=True) @click.option('--root-schema', required=False, default=None, type=int) -def set_schema(community, json_file, root_schema): +def set_schema(community, json_file, root_schema, no_block): """Set the community block schema and/or root schema version. If a root schema version is given, but no JSON file, the latest known block schema will be used (if present).""" from b2share.modules.schemas.cli import update_or_set_community_schema, update_or_set_community_root_schema - if json_file: - update_or_set_community_schema(community, json_file, root_schema) + if json_file or no_block: + update_or_set_community_schema(community, json_file, root_schema, no_block) elif root_schema: update_or_set_community_root_schema(community, root_schema) else: - raise click.BadParameter("Need at least a JSON file or root schema version") + raise click.BadParameter("Need at least a JSON file, block flag or root schema version") @communities.group() diff --git a/b2share/modules/schemas/cli.py b/b2share/modules/schemas/cli.py index fb72e10efc..1efdb66268 100644 --- a/b2share/modules/schemas/cli.py +++ b/b2share/modules/schemas/cli.py @@ -295,7 +295,7 @@ def community_schema_list_block_schema_versions(verbose, community, version=None # this function should be called from the communities' cli module -def update_or_set_community_schema(community, json_file): +def update_or_set_community_schema(community, json_file, root_schema_version=None, no_block=False): """Updates or sets the schema for a community. The complete schema of a community contains a copy of the root metadata @@ -306,6 +306,8 @@ def update_or_set_community_schema(community, json_file): - community is the ID or NAME of the community to be updated. - json_file is a file path to the json-schema file describing the community-specific block schema. + - root_schema_version is the index of a known root schema. + - no_block is a flag indicated the newly created community schema requires an empty block schema. See also `b2share schemas block_schema_version_generate_json`""" @@ -313,25 +315,28 @@ def update_or_set_community_schema(community, json_file): if not comm: raise click.BadParameter("There is no community by this name or ID: %s" % community) - if not os.path.isfile(json_file): - raise click.ClickException("%s does not exist on the filesystem" % - json_file) schema_dict = {} - with open(json_file, 'r') as f: - try: - schema_dict = json.load(f) - except ValueError: - raise click.ClickException("%s is not valid JSON" % json_file) + if not no_block: + if not json_file: + raise click.ClickException("No JSON file given") + if not os.path.isfile(json_file): + raise click.ClickException("%s does not exist on the filesystem" % json_file) + + with open(json_file, 'r') as f: + try: + schema_dict = json.load(f) + except ValueError: + raise click.ClickException("%s is not valid JSON" % json_file) - try: - validate_metadata_schema(schema_dict) - except Exception as e: - print("schema validation error:", e) - raise click.ClickException("""%s is not a valid metadata schema for - a B2SHARE community""" % json_file) + try: + validate_metadata_schema(schema_dict) + except Exception as e: + print("schema validation error:", e) + raise click.ClickException("""%s is not a valid metadata schema for + a B2SHARE community""" % json_file) - #create new block version schema + # create new block version schema try: community_schema = CommunitySchema.get_community_schema(comm.id) comm_schema_json = json.loads(community_schema.community_schema) @@ -341,13 +346,18 @@ def update_or_set_community_schema(community, json_file): if len(comm_schema_json['properties']) > 1: raise click.ClickException("""Multiple block schemas not supported.""") - #we can by configuration also have a community schema that does not refer to a blockschema - if len (comm_schema_json['properties']) == 0: - _create_community_schema(comm, schema_dict) + # we can by configuration also have a community schema that does not refer to a block schema + if (len(comm_schema_json['properties']) == 0) and not no_block: + _create_community_schema(comm, schema_dict, root_schema_version) + elif no_block: + _create_community_schema_no_block(comm, root_schema_version) else: _update_community_schema(comm, comm_schema_json, schema_dict) except CommunitySchemaDoesNotExistError: - _create_community_schema(comm, schema_dict) + if not no_block: + _create_community_schema(comm, schema_dict, root_schema_version) + else: + _create_community_schema_no_block(comm, root_schema_version) db.session.commit() click.secho("Succesfully processed new metadata schema", fg='green') From 723eaa8f15d1661dcc1a76a73569100ac4ee52f9 Mon Sep 17 00:00:00 2001 From: hevp Date: Mon, 15 Feb 2021 13:10:34 +0100 Subject: [PATCH 07/11] Extend roles module with listing (per user) --- b2share/config.py | 1 + b2share/modules/roles/__init__.py | 48 +++++++++++++++++++++++++ b2share/modules/roles/cli.py | 53 ++++++++++++++++++++++++++++ b2share/modules/roles/ext.py | 58 +++++++++++++++++++++++++++++++ setup.py | 1 + 5 files changed, 161 insertions(+) create mode 100644 b2share/modules/roles/__init__.py create mode 100644 b2share/modules/roles/cli.py create mode 100644 b2share/modules/roles/ext.py diff --git a/b2share/config.py b/b2share/config.py index eadc2eaa76..92408f2275 100644 --- a/b2share/config.py +++ b/b2share/config.py @@ -33,6 +33,7 @@ from flask import request from invenio_records_rest.utils import deny_all, allow_all from b2share.modules.oauthclient.b2access import make_b2access_remote_app +from b2share.modules.roles import B2ShareRoles from b2share.modules.records.search import B2ShareRecordsSearch from b2share.modules.records.permissions import ( UpdateRecordPermission, DeleteRecordPermission diff --git a/b2share/modules/roles/__init__.py b/b2share/modules/roles/__init__.py new file mode 100644 index 0000000000..ea018519b1 --- /dev/null +++ b/b2share/modules/roles/__init__.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# This file is part of EUDAT B2Share. +# Copyright (C) 2016 University of Tuebingen, CERN. +# Copyright (C) 2015 University of Tuebingen. +# +# B2Share is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# B2Share is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with B2Share; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + +"""B2SHARE module providing access to user accounts + + +B2SHARE is accessible to anonymous users (not authenticated). However some +actions can only be performed by authenticated users. + +Invenio provides the ``invenio-accounts`` module which stores in the database +user account information. + +The B2SHARE module ``b2share.modules.users`` adds some features on top of +``invenio-accounts``. + +* A REST API enabling to read user information and to create **REST API Access + Tokens**. See ``b2share.modules.users.views``. + +* Permission classes limiting the access to the REST API. See the + ``b2share.modules.users.permissions`` module. + +REST API Access Tokens enable a user to send authenticated requests via the +REST API. Example: ``GET /api/user/?access_token=``. See +``invenio-oauth2server`` for more information on Access Tokens. +""" + +from __future__ import absolute_import, print_function + +from .ext import B2ShareRoles + +__all__ = ('B2ShareRoles') diff --git a/b2share/modules/roles/cli.py b/b2share/modules/roles/cli.py new file mode 100644 index 0000000000..22aa621534 --- /dev/null +++ b/b2share/modules/roles/cli.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# +# This file is part of EUDAT B2Share. +# Copyright (C) 2017 University of Tübingen, CERN +# +# B2Share is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# B2Share is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with B2Share; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +# +# In applying this license, CERN does not +# waive the privileges and immunities granted to it by virtue of its status +# as an Intergovernmental Organization or submit itself to any jurisdiction. + +"""B2Share cli commands for roles.""" + + +from __future__ import absolute_import, print_function + +import click +from flask.cli import with_appcontext + +from invenio_accounts.cli import roles +from invenio_accounts.models import Role, User + +@roles.command('list') +@click.argument('user', required=False) +@with_appcontext +def roles_list(user): + """List all known roles (for a user)""" + + if user: + userdata = User.query.filter(User.email == user).order_by(User.id) + else: + userdata = User.query.order_by(User.id) + + click.secho("ID\tACTIVE\tEMAIL\t\t\t\t\tROLES") + for u in userdata: + click.secho("%s\t%s\t%s\t\t\t%s" % ( + u.id, + u.active, + u.email, + list(map(lambda x: x.description, u.roles if len(u.roles) else [])) + )) diff --git a/b2share/modules/roles/ext.py b/b2share/modules/roles/ext.py new file mode 100644 index 0000000000..2008a6d759 --- /dev/null +++ b/b2share/modules/roles/ext.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# +# This file is part of EUDAT B2Share. +# Copyright (C) 2016 University of Tuebingen, CERN. +# Copyright (C) 2015 University of Tuebingen. +# +# B2Share is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# B2Share is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with B2Share; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + +"""B2share users extension""" + +from __future__ import absolute_import, print_function + +from werkzeug.utils import cached_property + +from .cli import roles as roles_cmd + + +class _B2ShareRolesState(object): + """B2Share users extension state.""" + + def __init__(self, app): + """Constructor. + + Args: + app: the Flask application. + """ + self.app = app + + +class B2ShareRoles(object): + """B2Share Roles extension.""" + + def __init__(self, app=None): + """Extension initialization.""" + if app: + self.init_app(app) + + def init_app(self, app): + """Flask application initialization.""" + self.init_config(app) + app.cli.add_command(roles_cmd) + app.register_blueprint(blueprint) + app.extensions['b2share-roles'] = _B2ShareRolesState(app) + + def init_config(self, app): + pass \ No newline at end of file diff --git a/setup.py b/setup.py index c1edcca6e6..cf2744cbcd 100644 --- a/setup.py +++ b/setup.py @@ -179,6 +179,7 @@ def run_tests(self): 'b2share_communities = b2share.modules.communities:B2ShareCommunities', 'b2share_schemas = b2share.modules.schemas:B2ShareSchemas', 'b2share_users = b2share.modules.users:B2ShareUsers', + 'b2share_roles = b2share.modules.roles:B2ShareRoles', 'b2share_records = b2share.modules.records:B2ShareRecords', 'b2share_deposit = b2share.modules.deposit:B2ShareDeposit', 'b2share_handle = b2share.modules.handle:B2ShareHandle', From 2a683c46155e1aa0521b138a4ec5d6c70c6ac51a Mon Sep 17 00:00:00 2001 From: hevp Date: Mon, 15 Feb 2021 13:34:03 +0100 Subject: [PATCH 08/11] Add community roles listing --- b2share/modules/communities/cli.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/b2share/modules/communities/cli.py b/b2share/modules/communities/cli.py index 189e74ce08..a54c2ad01e 100644 --- a/b2share/modules/communities/cli.py +++ b/b2share/modules/communities/cli.py @@ -160,6 +160,33 @@ def set_schema(community, json_file, root_schema, no_block): raise click.BadParameter("Need at least a JSON file, block flag or root schema version") +@communities.group() +@with_appcontext +def roles(): + """Manage community roles""" + +@roles.command('list') +@with_appcontext +@click.argument('community', required=False) +def community_roles_list(community=None): + """List all communities' roles""" + + def list_item(comm): + click.secho("%s\t%s\t\t%s\t%s" % ( + comm.id, + comm.name, + comm.admin_role, + comm.member_role + )) + + click.secho("ID\t\t\t\t\tNAME\t\tROLES") + if not community: + for c in Community.get_all(): + list_item(c) + else: + list_item(Community.get(community)) + + @communities.group() @with_appcontext def policies(): From 9c4f155c8a68355516421e0e0474c828a13a0c40 Mon Sep 17 00:00:00 2001 From: hevp Date: Mon, 15 Feb 2021 13:53:27 +0100 Subject: [PATCH 09/11] Fix broken community admin links in user profile page --- webui/src/components/user.jsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/webui/src/components/user.jsx b/webui/src/components/user.jsx index fb8033651a..a9ee7d9802 100644 --- a/webui/src/components/user.jsx +++ b/webui/src/components/user.jsx @@ -111,15 +111,15 @@ export const UserProfile = React.createClass({ ); }, - createLink(communitiesList, name){ - return (admin page) + createLink(communitiesMapping, name) { + return (admin page) }, - listRoles(roles, communitiesList){ + listRoles(roles, communitiesMapping) { return roles.map(r =>
  • {r.get('description')} - {r.get('name').includes(':admin')? this.createLink(communitiesList, r.get('name')) : ""} + {r.get('name').includes(':admin')? this.createLink(communitiesMapping, r.get('name')) : ""}
  • ) }, @@ -129,11 +129,13 @@ export const UserProfile = React.createClass({ return this.renderNoUser(); } const roles = user.get('roles'); - const communitiesListTemp = serverCache.getCommunities(); - var communitiesList = communitiesListTemp.reduce(function(map, community){ - map[community.get('id').replace(new RegExp("-",'g'),"")] = community.get('name'); - return map; - }); + const communitiesList = serverCache.getCommunities(); + if (communitiesList.size) { + var communitiesMapping = communitiesList.reduce((map, community) => { + map[community.get('id').replace(new RegExp("-",'g'),"")] = community.get('name'); + return map; + }, {}); + } return (

    User Profile

    @@ -145,7 +147,7 @@ export const UserProfile = React.createClass({

    Roles

    - {roles && roles.count() && typeof(communitiesList) !== "undefined" ? this.listRoles(roles, communitiesList) : "You have no assigned roles" } + {(roles && roles.count() && communitiesMapping !== undefined) ? this.listRoles(roles, communitiesMapping) : "You have no assigned roles" }

    From b0232c6595ec876f843596ae590d5a43001dbd9b Mon Sep 17 00:00:00 2001 From: hevp Date: Fri, 12 Mar 2021 13:27:46 +0100 Subject: [PATCH 10/11] Remove conflict line --- b2share/modules/communities/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/b2share/modules/communities/cli.py b/b2share/modules/communities/cli.py index a54c2ad01e..2912e22dda 100644 --- a/b2share/modules/communities/cli.py +++ b/b2share/modules/communities/cli.py @@ -250,4 +250,3 @@ def community_policies_set(verbose, community, policy, enable=None, value=None): db.session.commit() if verbose: click.echo("Community policy '%s' updated" % policy) ->>>>>>> 19031bc32 (Add community policy update CLI command) From 005f4caa0cf549ab38489e41858827df36e554e9 Mon Sep 17 00:00:00 2001 From: hevp Date: Wed, 17 Mar 2021 16:01:15 +0100 Subject: [PATCH 11/11] Display error on CLI no communities defined --- b2share/modules/communities/cli.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/b2share/modules/communities/cli.py b/b2share/modules/communities/cli.py index 2912e22dda..6f57318721 100644 --- a/b2share/modules/communities/cli.py +++ b/b2share/modules/communities/cli.py @@ -95,8 +95,12 @@ def create(verbose, name, description, logo): def list(verbose): """List all communities in this instances' database""" communities = Community.get_all() - for c in communities: - click.echo("%s\t%s\t%s\t%s" % (c.name[0:15], c.id, c.description[0:31], c.logo)) + + if communities: + for c in communities: + click.echo("%s\t%s\t%s\t%s" % (c.name[0:15], c.id, c.description[0:31], c.logo)) + else: + raise click.ClickException("there are no communities defined") @communities.command() @@ -181,8 +185,12 @@ def list_item(comm): click.secho("ID\t\t\t\t\tNAME\t\tROLES") if not community: - for c in Community.get_all(): - list_item(c) + communities = Community.get_all() + if communities: + for c in communities: + list_item(c) + else: + raise click.ClickException("there are no communities defined") else: list_item(Community.get(community)) @@ -208,8 +216,12 @@ def list_item(comm): click.secho("ID\t\t\t\t\tNAME\t\tWORKFLOW\tMEMBERS-ONLY") if not community: - for c in Community.get_all(): - list_item(c) + communities = Community.get_all() + if communities: + for c in communities: + list_item(c) + else: + raise click.ClickException("there are no communities defined") else: list_item(Community.get(community))