cmem-cmemc 24.2.0rc2__py3-none-any.whl → 24.3.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cmem_cmemc/__init__.py +7 -12
- cmem_cmemc/command.py +20 -0
- cmem_cmemc/command_group.py +70 -0
- cmem_cmemc/commands/__init__.py +0 -81
- cmem_cmemc/commands/acl.py +118 -62
- cmem_cmemc/commands/admin.py +46 -35
- cmem_cmemc/commands/client.py +2 -1
- cmem_cmemc/commands/config.py +3 -1
- cmem_cmemc/commands/dataset.py +27 -24
- cmem_cmemc/commands/graph.py +100 -16
- cmem_cmemc/commands/metrics.py +195 -79
- cmem_cmemc/commands/migration.py +265 -0
- cmem_cmemc/commands/project.py +62 -17
- cmem_cmemc/commands/python.py +57 -26
- cmem_cmemc/commands/query.py +23 -14
- cmem_cmemc/commands/resource.py +10 -2
- cmem_cmemc/commands/scheduler.py +10 -2
- cmem_cmemc/commands/store.py +118 -14
- cmem_cmemc/commands/user.py +8 -2
- cmem_cmemc/commands/validation.py +165 -78
- cmem_cmemc/commands/variable.py +10 -2
- cmem_cmemc/commands/vocabulary.py +48 -29
- cmem_cmemc/commands/workflow.py +86 -59
- cmem_cmemc/commands/workspace.py +27 -8
- cmem_cmemc/completion.py +185 -141
- cmem_cmemc/constants.py +2 -0
- cmem_cmemc/context.py +88 -42
- cmem_cmemc/manual_helper/graph.py +1 -0
- cmem_cmemc/manual_helper/multi_page.py +3 -1
- cmem_cmemc/migrations/__init__.py +1 -0
- cmem_cmemc/migrations/abc.py +84 -0
- cmem_cmemc/migrations/access_conditions_243.py +118 -0
- cmem_cmemc/migrations/bootstrap_data.py +30 -0
- cmem_cmemc/migrations/shapes_widget_integrations_243.py +194 -0
- cmem_cmemc/migrations/workspace_configurations.py +28 -0
- cmem_cmemc/object_list.py +53 -22
- cmem_cmemc/parameter_types/__init__.py +1 -0
- cmem_cmemc/parameter_types/path.py +69 -0
- cmem_cmemc/smart_path/__init__.py +94 -0
- cmem_cmemc/smart_path/clients/__init__.py +63 -0
- cmem_cmemc/smart_path/clients/http.py +65 -0
- cmem_cmemc/string_processor.py +77 -0
- cmem_cmemc/title_helper.py +41 -0
- cmem_cmemc/utils.py +114 -47
- {cmem_cmemc-24.2.0rc2.dist-info → cmem_cmemc-24.3.0rc1.dist-info}/LICENSE +1 -1
- cmem_cmemc-24.3.0rc1.dist-info/METADATA +89 -0
- cmem_cmemc-24.3.0rc1.dist-info/RECORD +53 -0
- {cmem_cmemc-24.2.0rc2.dist-info → cmem_cmemc-24.3.0rc1.dist-info}/WHEEL +1 -1
- cmem_cmemc-24.2.0rc2.dist-info/METADATA +0 -69
- cmem_cmemc-24.2.0rc2.dist-info/RECORD +0 -37
- {cmem_cmemc-24.2.0rc2.dist-info → cmem_cmemc-24.3.0rc1.dist-info}/entry_points.txt +0 -0
cmem_cmemc/__init__.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""The main command line interface."""
|
|
2
|
+
|
|
2
3
|
import contextlib
|
|
3
4
|
import os
|
|
4
5
|
import sys
|
|
@@ -10,8 +11,8 @@ import click
|
|
|
10
11
|
import requests.exceptions
|
|
11
12
|
|
|
12
13
|
from cmem_cmemc import completion
|
|
14
|
+
from cmem_cmemc.command_group import CmemcGroup
|
|
13
15
|
from cmem_cmemc.commands import (
|
|
14
|
-
CmemcGroup,
|
|
15
16
|
admin,
|
|
16
17
|
config,
|
|
17
18
|
dataset,
|
|
@@ -26,7 +27,7 @@ from cmem_cmemc.exceptions import InvalidConfigurationError
|
|
|
26
27
|
from cmem_cmemc.manual_helper.graph import print_manual_graph
|
|
27
28
|
from cmem_cmemc.manual_helper.multi_page import create_multi_page_documentation
|
|
28
29
|
from cmem_cmemc.manual_helper.single_page import print_manual
|
|
29
|
-
from cmem_cmemc.utils import extract_error_message, get_version
|
|
30
|
+
from cmem_cmemc.utils import check_python_version, extract_error_message, get_version
|
|
30
31
|
|
|
31
32
|
CMEMC_VERSION = get_version()
|
|
32
33
|
|
|
@@ -38,16 +39,10 @@ if os.environ.get("_CMEMC_COMPLETE", "") == "zsh_source":
|
|
|
38
39
|
|
|
39
40
|
version = sys.version_info
|
|
40
41
|
PYTHON_VERSION = f"{version.major}.{version.minor}.{version.micro}"
|
|
41
|
-
|
|
42
|
-
PYTHON_GOT = f"{version.major}.{version.minor}"
|
|
43
|
-
if PYTHON_EXPECTED != PYTHON_GOT and not CONTEXT.is_completing():
|
|
44
|
-
CONTEXT.echo_warning(
|
|
45
|
-
"Warning: You are running cmemc under a non-tested python "
|
|
46
|
-
f"environment (expected {PYTHON_EXPECTED}, got {PYTHON_GOT})"
|
|
47
|
-
)
|
|
42
|
+
check_python_version(ctx=CONTEXT)
|
|
48
43
|
|
|
49
44
|
# set the user-agent environment for the http request headers
|
|
50
|
-
os.environ["CMEM_USER_AGENT"] = f"cmemc/{CMEMC_VERSION}
|
|
45
|
+
os.environ["CMEM_USER_AGENT"] = f"cmemc/{CMEMC_VERSION} (Python {PYTHON_VERSION})"
|
|
51
46
|
|
|
52
47
|
# https://github.com/pallets/click/blob/master/examples/complex/complex/cli.py
|
|
53
48
|
CONTEXT_SETTINGS = {"auto_envvar_prefix": "CMEMC", "help_option_names": ["-h", "--help"]}
|
|
@@ -123,11 +118,11 @@ def cli(
|
|
|
123
118
|
ctx.obj.set_config_file(config_file)
|
|
124
119
|
try:
|
|
125
120
|
ctx.obj.set_connection(connection)
|
|
126
|
-
except InvalidConfigurationError
|
|
121
|
+
except InvalidConfigurationError:
|
|
127
122
|
# if config is broken still allow for "config edit"
|
|
128
123
|
# means: do not forward this exception if "config edit"
|
|
129
124
|
if " ".join(sys.argv).find("config edit") == -1:
|
|
130
|
-
raise
|
|
125
|
+
raise
|
|
131
126
|
|
|
132
127
|
|
|
133
128
|
cli.add_command(admin.admin)
|
cmem_cmemc/command.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""cmemc Click Command"""
|
|
2
|
+
|
|
3
|
+
from click_help_colors import HelpColorsCommand
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CmemcCommand(HelpColorsCommand):
|
|
7
|
+
"""Wrapper click.Command class to have a single extension point.
|
|
8
|
+
|
|
9
|
+
Currently, wrapped click extensions and additional group features:#
|
|
10
|
+
- click-help-colors: https://github.com/click-contrib/click-help-colors
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
color_for_headers = "yellow"
|
|
14
|
+
color_for_options = "green"
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args, **kwargs): # noqa: ANN002, ANN003
|
|
17
|
+
"""Init a cmemc group command."""
|
|
18
|
+
kwargs.setdefault("help_headers_color", self.color_for_headers)
|
|
19
|
+
kwargs.setdefault("help_options_color", self.color_for_options)
|
|
20
|
+
super().__init__(*args, **kwargs)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""cmemc Click Command Group"""
|
|
2
|
+
|
|
3
|
+
from click_didyoumean import DYMGroup
|
|
4
|
+
from click_help_colors import HelpColorsGroup
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CmemcGroup(HelpColorsGroup, DYMGroup):
|
|
8
|
+
"""Wrapper click.Group class to have a single extension point.
|
|
9
|
+
|
|
10
|
+
Currently, wrapped click extensions and additional group features:#
|
|
11
|
+
- click-help-colors: https://github.com/click-contrib/click-help-colors
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
color_for_command_groups = "white"
|
|
15
|
+
color_for_writing_commands = "red"
|
|
16
|
+
color_for_headers = "yellow"
|
|
17
|
+
color_for_options = "green"
|
|
18
|
+
|
|
19
|
+
def __init__(self, *args, **kwargs): # noqa: ANN002, ANN003
|
|
20
|
+
"""Init a cmemc group command group."""
|
|
21
|
+
# set default colors
|
|
22
|
+
kwargs.setdefault("help_headers_color", self.color_for_headers)
|
|
23
|
+
kwargs.setdefault("help_options_color", self.color_for_options)
|
|
24
|
+
kwargs.setdefault(
|
|
25
|
+
"help_options_custom_colors",
|
|
26
|
+
{
|
|
27
|
+
"bootstrap": self.color_for_writing_commands,
|
|
28
|
+
"showcase": self.color_for_writing_commands,
|
|
29
|
+
"delete": self.color_for_writing_commands,
|
|
30
|
+
"password": self.color_for_writing_commands,
|
|
31
|
+
"secret": self.color_for_writing_commands,
|
|
32
|
+
"upload": self.color_for_writing_commands,
|
|
33
|
+
"import": self.color_for_writing_commands,
|
|
34
|
+
"create": self.color_for_writing_commands,
|
|
35
|
+
"enable": self.color_for_writing_commands,
|
|
36
|
+
"disable": self.color_for_writing_commands,
|
|
37
|
+
"execute": self.color_for_writing_commands,
|
|
38
|
+
"replay": self.color_for_writing_commands,
|
|
39
|
+
"io": self.color_for_writing_commands,
|
|
40
|
+
"install": self.color_for_writing_commands,
|
|
41
|
+
"uninstall": self.color_for_writing_commands,
|
|
42
|
+
"reload": self.color_for_writing_commands,
|
|
43
|
+
"update": self.color_for_writing_commands,
|
|
44
|
+
"eval": self.color_for_writing_commands,
|
|
45
|
+
"cancel": self.color_for_writing_commands,
|
|
46
|
+
"admin": self.color_for_command_groups,
|
|
47
|
+
"user": self.color_for_command_groups,
|
|
48
|
+
"store": self.color_for_command_groups,
|
|
49
|
+
"metrics": self.color_for_command_groups,
|
|
50
|
+
"config": self.color_for_command_groups,
|
|
51
|
+
"dataset": self.color_for_command_groups,
|
|
52
|
+
"graph": self.color_for_command_groups,
|
|
53
|
+
"project": self.color_for_command_groups,
|
|
54
|
+
"query": self.color_for_command_groups,
|
|
55
|
+
"scheduler": self.color_for_command_groups,
|
|
56
|
+
"vocabulary": self.color_for_command_groups,
|
|
57
|
+
"workflow": self.color_for_command_groups,
|
|
58
|
+
"workspace": self.color_for_command_groups,
|
|
59
|
+
"python": self.color_for_command_groups,
|
|
60
|
+
"cache": self.color_for_command_groups,
|
|
61
|
+
"resource": self.color_for_command_groups,
|
|
62
|
+
"acl": self.color_for_command_groups,
|
|
63
|
+
"client": self.color_for_command_groups,
|
|
64
|
+
"variable": self.color_for_command_groups,
|
|
65
|
+
"validation": self.color_for_command_groups,
|
|
66
|
+
"migrate": self.color_for_writing_commands,
|
|
67
|
+
"migrations": self.color_for_command_groups,
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
super().__init__(*args, **kwargs)
|
cmem_cmemc/commands/__init__.py
CHANGED
|
@@ -1,82 +1 @@
|
|
|
1
1
|
"""cmemc commands."""
|
|
2
|
-
from click_didyoumean import DYMGroup
|
|
3
|
-
from click_help_colors import HelpColorsCommand, HelpColorsGroup
|
|
4
|
-
|
|
5
|
-
COLOR_FOR_COMMAND_GROUPS = "white"
|
|
6
|
-
COLOR_FOR_WRITING_COMMANDS = "red"
|
|
7
|
-
COLOR_FOR_HEADERS = "yellow"
|
|
8
|
-
COLOR_FOR_OPTIONS = "green"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# pylint: disable=too-many-ancestors
|
|
12
|
-
class CmemcGroup(HelpColorsGroup, DYMGroup):
|
|
13
|
-
"""Wrapper click.Group class to have a single extension point.
|
|
14
|
-
|
|
15
|
-
Currently, wrapped click extensions and additional group features:#
|
|
16
|
-
- click-help-colors: https://github.com/click-contrib/click-help-colors
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def __init__(self, *args, **kwargs): # noqa: ANN002, ANN003
|
|
20
|
-
"""Init a cmemc group command group."""
|
|
21
|
-
# set default colors
|
|
22
|
-
kwargs.setdefault("help_headers_color", COLOR_FOR_HEADERS)
|
|
23
|
-
kwargs.setdefault("help_options_color", COLOR_FOR_OPTIONS)
|
|
24
|
-
kwargs.setdefault(
|
|
25
|
-
"help_options_custom_colors",
|
|
26
|
-
{
|
|
27
|
-
"bootstrap": COLOR_FOR_WRITING_COMMANDS,
|
|
28
|
-
"showcase": COLOR_FOR_WRITING_COMMANDS,
|
|
29
|
-
"delete": COLOR_FOR_WRITING_COMMANDS,
|
|
30
|
-
"password": COLOR_FOR_WRITING_COMMANDS,
|
|
31
|
-
"secret": COLOR_FOR_WRITING_COMMANDS,
|
|
32
|
-
"upload": COLOR_FOR_WRITING_COMMANDS,
|
|
33
|
-
"import": COLOR_FOR_WRITING_COMMANDS,
|
|
34
|
-
"create": COLOR_FOR_WRITING_COMMANDS,
|
|
35
|
-
"enable": COLOR_FOR_WRITING_COMMANDS,
|
|
36
|
-
"disable": COLOR_FOR_WRITING_COMMANDS,
|
|
37
|
-
"execute": COLOR_FOR_WRITING_COMMANDS,
|
|
38
|
-
"replay": COLOR_FOR_WRITING_COMMANDS,
|
|
39
|
-
"io": COLOR_FOR_WRITING_COMMANDS,
|
|
40
|
-
"install": COLOR_FOR_WRITING_COMMANDS,
|
|
41
|
-
"uninstall": COLOR_FOR_WRITING_COMMANDS,
|
|
42
|
-
"reload": COLOR_FOR_WRITING_COMMANDS,
|
|
43
|
-
"update": COLOR_FOR_WRITING_COMMANDS,
|
|
44
|
-
"eval": COLOR_FOR_WRITING_COMMANDS,
|
|
45
|
-
"cancel": COLOR_FOR_WRITING_COMMANDS,
|
|
46
|
-
"admin": COLOR_FOR_COMMAND_GROUPS,
|
|
47
|
-
"user": COLOR_FOR_COMMAND_GROUPS,
|
|
48
|
-
"store": COLOR_FOR_COMMAND_GROUPS,
|
|
49
|
-
"metrics": COLOR_FOR_COMMAND_GROUPS,
|
|
50
|
-
"config": COLOR_FOR_COMMAND_GROUPS,
|
|
51
|
-
"dataset": COLOR_FOR_COMMAND_GROUPS,
|
|
52
|
-
"graph": COLOR_FOR_COMMAND_GROUPS,
|
|
53
|
-
"project": COLOR_FOR_COMMAND_GROUPS,
|
|
54
|
-
"query": COLOR_FOR_COMMAND_GROUPS,
|
|
55
|
-
"scheduler": COLOR_FOR_COMMAND_GROUPS,
|
|
56
|
-
"vocabulary": COLOR_FOR_COMMAND_GROUPS,
|
|
57
|
-
"workflow": COLOR_FOR_COMMAND_GROUPS,
|
|
58
|
-
"workspace": COLOR_FOR_COMMAND_GROUPS,
|
|
59
|
-
"python": COLOR_FOR_COMMAND_GROUPS,
|
|
60
|
-
"cache": COLOR_FOR_COMMAND_GROUPS,
|
|
61
|
-
"resource": COLOR_FOR_COMMAND_GROUPS,
|
|
62
|
-
"acl": COLOR_FOR_COMMAND_GROUPS,
|
|
63
|
-
"client": COLOR_FOR_COMMAND_GROUPS,
|
|
64
|
-
"variable": COLOR_FOR_COMMAND_GROUPS,
|
|
65
|
-
"validation": COLOR_FOR_COMMAND_GROUPS,
|
|
66
|
-
},
|
|
67
|
-
)
|
|
68
|
-
super().__init__(*args, **kwargs)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class CmemcCommand(HelpColorsCommand):
|
|
72
|
-
"""Wrapper click.Command class to have a single extension point.
|
|
73
|
-
|
|
74
|
-
Currently, wrapped click extensions and additional group features:#
|
|
75
|
-
- click-help-colors: https://github.com/click-contrib/click-help-colors
|
|
76
|
-
"""
|
|
77
|
-
|
|
78
|
-
def __init__(self, *args, **kwargs): # noqa: ANN002, ANN003
|
|
79
|
-
"""Init a cmemc group command."""
|
|
80
|
-
kwargs.setdefault("help_headers_color", COLOR_FOR_HEADERS)
|
|
81
|
-
kwargs.setdefault("help_options_color", COLOR_FOR_OPTIONS)
|
|
82
|
-
super().__init__(*args, **kwargs)
|
cmem_cmemc/commands/acl.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
import requests.exceptions
|
|
5
|
-
from click import Option
|
|
5
|
+
from click import Option
|
|
6
6
|
from cmem.cmempy.dp.authorization.conditions import (
|
|
7
7
|
create_access_condition,
|
|
8
8
|
delete_access_condition,
|
|
@@ -14,25 +14,36 @@ from cmem.cmempy.dp.authorization.conditions import (
|
|
|
14
14
|
from cmem.cmempy.keycloak.user import get_user_by_username, user_groups
|
|
15
15
|
|
|
16
16
|
from cmem_cmemc import completion
|
|
17
|
-
from cmem_cmemc.
|
|
18
|
-
from cmem_cmemc.
|
|
17
|
+
from cmem_cmemc.command import CmemcCommand
|
|
18
|
+
from cmem_cmemc.command_group import CmemcGroup
|
|
19
|
+
from cmem_cmemc.constants import NS_ACL, NS_ACTION, NS_GROUP, NS_USER
|
|
19
20
|
from cmem_cmemc.context import ApplicationContext
|
|
20
|
-
from cmem_cmemc.utils import
|
|
21
|
+
from cmem_cmemc.utils import (
|
|
22
|
+
convert_iri_to_qname,
|
|
23
|
+
convert_qname_to_iri,
|
|
24
|
+
get_query_text,
|
|
25
|
+
struct_to_table,
|
|
26
|
+
)
|
|
21
27
|
|
|
22
28
|
# option descriptions
|
|
23
29
|
HELP_TEXTS = {
|
|
24
|
-
"name": "A
|
|
30
|
+
"name": "A optional name.",
|
|
25
31
|
"id": "An optional ID (will be an UUID otherwise).",
|
|
26
32
|
"description": "An optional description.",
|
|
27
33
|
"user": "A specific user account required by the access condition.",
|
|
28
|
-
"group": "A membership in a user group required by the access condition",
|
|
34
|
+
"group": "A membership in a user group required by the access condition.",
|
|
29
35
|
"read_graph": "Grants read access to a graph.",
|
|
30
36
|
"write_graph": "Grants write access to a graph (includes read access).",
|
|
31
37
|
"action": "Grants usage permissions to an action / functionality.",
|
|
38
|
+
"query": "Dynamic access condition query (file or the query catalog IRI).",
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
WARNING_UNKNOWN_USER = "Unknown User or no access to get user info."
|
|
42
|
+
WARNING_NO_GROUP_ACCESS = "You do not have the permission to retrieve user groups"
|
|
43
|
+
WARNING_USE_GROUP = "Use the --group option to assign groups manually (what-if-scenario)."
|
|
44
|
+
|
|
45
|
+
PUBLIC_USER_URI = "https://vocab.eccenca.com/auth/AnonymousUser"
|
|
46
|
+
PUBLIC_GROUP_URI = "https://vocab.eccenca.com/auth/PublicGroup"
|
|
36
47
|
|
|
37
48
|
KNOWN_ACCESS_CONDITION_URLS = [PUBLIC_USER_URI, PUBLIC_GROUP_URI]
|
|
38
49
|
|
|
@@ -55,12 +66,10 @@ def _value_to_acl_url(
|
|
|
55
66
|
|
|
56
67
|
or not, if it is already a known URI .... or None
|
|
57
68
|
"""
|
|
58
|
-
if value
|
|
69
|
+
if value == "" or value is None:
|
|
70
|
+
return value
|
|
71
|
+
if value.startswith(("http://", "https://")):
|
|
59
72
|
return value
|
|
60
|
-
if value == "":
|
|
61
|
-
return ""
|
|
62
|
-
if value is None:
|
|
63
|
-
return None
|
|
64
73
|
match param.name:
|
|
65
74
|
case "groups":
|
|
66
75
|
return f"{NS_GROUP}{value}"
|
|
@@ -69,8 +78,10 @@ def _value_to_acl_url(
|
|
|
69
78
|
return f"{NS_ACL}{value}"
|
|
70
79
|
|
|
71
80
|
|
|
72
|
-
def generate_acl_name(user: str | None, groups: list[str]) -> str:
|
|
81
|
+
def generate_acl_name(user: str | None, groups: list[str], query: str | None) -> str:
|
|
73
82
|
"""Create an access condition name based on user and group assignments."""
|
|
83
|
+
if query is not None:
|
|
84
|
+
return "Query based Dynamic Access Condition"
|
|
74
85
|
if len(groups) > 0:
|
|
75
86
|
group_term = "groups" if len(groups) > 1 else "group"
|
|
76
87
|
groups_labels = ", ".join(
|
|
@@ -113,7 +124,13 @@ def list_command(app: ApplicationContext, raw: bool, id_only: bool) -> None:
|
|
|
113
124
|
(convert_iri_to_qname(iri=_.get("iri"), default_ns=NS_ACL), _.get("name", "-"))
|
|
114
125
|
for _ in acls
|
|
115
126
|
]
|
|
116
|
-
app.echo_info_table(
|
|
127
|
+
app.echo_info_table(
|
|
128
|
+
table,
|
|
129
|
+
headers=["URI", "Name"],
|
|
130
|
+
sort_column=0,
|
|
131
|
+
empty_table_message="No access conditions found. "
|
|
132
|
+
"Use the `admin acl create` command to create a new access condition.",
|
|
133
|
+
)
|
|
117
134
|
|
|
118
135
|
|
|
119
136
|
@click.command(cls=CmemcCommand, name="inspect")
|
|
@@ -137,24 +154,6 @@ def inspect_command(app: ApplicationContext, access_condition_id: str, raw: bool
|
|
|
137
154
|
|
|
138
155
|
|
|
139
156
|
@click.command(cls=CmemcCommand, name="create")
|
|
140
|
-
@click.option(
|
|
141
|
-
"--name",
|
|
142
|
-
"name",
|
|
143
|
-
type=click.STRING,
|
|
144
|
-
help=HELP_TEXTS["name"],
|
|
145
|
-
)
|
|
146
|
-
@click.option(
|
|
147
|
-
"--id",
|
|
148
|
-
"id_",
|
|
149
|
-
type=click.STRING,
|
|
150
|
-
help=HELP_TEXTS["id"],
|
|
151
|
-
)
|
|
152
|
-
@click.option(
|
|
153
|
-
"--description",
|
|
154
|
-
"description",
|
|
155
|
-
type=click.STRING,
|
|
156
|
-
help=HELP_TEXTS["description"],
|
|
157
|
-
)
|
|
158
157
|
@click.option(
|
|
159
158
|
"--user",
|
|
160
159
|
type=click.STRING,
|
|
@@ -195,6 +194,31 @@ def inspect_command(app: ApplicationContext, access_condition_id: str, raw: bool
|
|
|
195
194
|
shell_complete=completion.acl_actions,
|
|
196
195
|
help=HELP_TEXTS["action"],
|
|
197
196
|
)
|
|
197
|
+
@click.option(
|
|
198
|
+
"--query",
|
|
199
|
+
"query",
|
|
200
|
+
type=click.STRING,
|
|
201
|
+
shell_complete=completion.remote_queries_and_sparql_files,
|
|
202
|
+
help=HELP_TEXTS["query"],
|
|
203
|
+
)
|
|
204
|
+
@click.option(
|
|
205
|
+
"--id",
|
|
206
|
+
"id_",
|
|
207
|
+
type=click.STRING,
|
|
208
|
+
help=HELP_TEXTS["id"],
|
|
209
|
+
)
|
|
210
|
+
@click.option(
|
|
211
|
+
"--name",
|
|
212
|
+
"name",
|
|
213
|
+
type=click.STRING,
|
|
214
|
+
help=HELP_TEXTS["name"],
|
|
215
|
+
)
|
|
216
|
+
@click.option(
|
|
217
|
+
"--description",
|
|
218
|
+
"description",
|
|
219
|
+
type=click.STRING,
|
|
220
|
+
help=HELP_TEXTS["description"],
|
|
221
|
+
)
|
|
198
222
|
@click.pass_obj
|
|
199
223
|
# pylint: disable-msg=too-many-arguments
|
|
200
224
|
def create_command( # noqa: PLR0913
|
|
@@ -207,20 +231,43 @@ def create_command( # noqa: PLR0913
|
|
|
207
231
|
read_graphs: tuple[str],
|
|
208
232
|
write_graphs: tuple[str],
|
|
209
233
|
actions: tuple[str],
|
|
234
|
+
query: str,
|
|
210
235
|
) -> None:
|
|
211
|
-
"""Create an access condition.
|
|
212
|
-
|
|
236
|
+
"""Create an access condition.
|
|
237
|
+
|
|
238
|
+
With this command, new access conditions can be created.
|
|
239
|
+
|
|
240
|
+
An access condition captures information about WHO gets access to WHAT.
|
|
241
|
+
In order to specify WHO gets access, use the `--user` and / or `--group` options.
|
|
242
|
+
In order to specify WHAT an account get access to, use the `--read-graph`,
|
|
243
|
+
`--write-graph` and `--action` options.`
|
|
244
|
+
|
|
245
|
+
In addition to that, you can specify a name, a description and an ID (all optional).
|
|
246
|
+
|
|
247
|
+
A special case are dynamic access conditions, based on a SPARQL query: Here you
|
|
248
|
+
have to provide a query with the projection variables `user`, `group` `readGraph`
|
|
249
|
+
and `writeGraph` to create multiple grants at once. You can either provide a query file
|
|
250
|
+
or a query URL from the query catalog.
|
|
251
|
+
|
|
252
|
+
Note: Queries for dynamic access conditions are copied into the ACL, so changing the
|
|
253
|
+
query in the query catalog does not change it in the access condition.
|
|
254
|
+
|
|
255
|
+
Example: cmemc admin acl create --group local-users --write-graph https://example.org/
|
|
256
|
+
"""
|
|
257
|
+
if not read_graphs and not write_graphs and not actions and not query:
|
|
213
258
|
raise click.UsageError(
|
|
214
259
|
"Missing access / usage grant. Use at least one of the following options: "
|
|
215
|
-
"--read-graph, --write-graph or --
|
|
216
|
-
)
|
|
217
|
-
if not user and not groups:
|
|
218
|
-
app.echo_warning(
|
|
219
|
-
"Access conditions without a user and without a group assignment " "affect ALL users."
|
|
260
|
+
"--read-graph, --write-graph, --action or --query."
|
|
220
261
|
)
|
|
262
|
+
query_str = None
|
|
263
|
+
if query:
|
|
264
|
+
query_str = get_query_text(query, {"user", "group", "readGraph", "writeGraph"})
|
|
265
|
+
|
|
266
|
+
if not user and not groups and not query:
|
|
267
|
+
app.echo_warning("Access conditions without a user or group assignment affect ALL users.")
|
|
221
268
|
|
|
222
269
|
if not name:
|
|
223
|
-
name = generate_acl_name(user=user, groups=groups)
|
|
270
|
+
name = generate_acl_name(user=user, groups=groups, query=query)
|
|
224
271
|
|
|
225
272
|
if not description:
|
|
226
273
|
description = "This access condition was created with cmemc."
|
|
@@ -229,6 +276,7 @@ def create_command( # noqa: PLR0913
|
|
|
229
276
|
f"Creating access condition '{name}' ... ",
|
|
230
277
|
nl=False,
|
|
231
278
|
)
|
|
279
|
+
|
|
232
280
|
create_access_condition(
|
|
233
281
|
name=name,
|
|
234
282
|
static_id=id_,
|
|
@@ -237,7 +285,8 @@ def create_command( # noqa: PLR0913
|
|
|
237
285
|
groups=groups,
|
|
238
286
|
read_graphs=list(read_graphs),
|
|
239
287
|
write_graphs=list(write_graphs),
|
|
240
|
-
actions=
|
|
288
|
+
actions=[convert_qname_to_iri(qname=_, default_ns=NS_ACTION) for _ in actions],
|
|
289
|
+
query=query_str,
|
|
241
290
|
)
|
|
242
291
|
app.echo_success("done")
|
|
243
292
|
|
|
@@ -302,6 +351,13 @@ def create_command( # noqa: PLR0913
|
|
|
302
351
|
shell_complete=completion.acl_actions,
|
|
303
352
|
help=HELP_TEXTS["action"],
|
|
304
353
|
)
|
|
354
|
+
@click.option(
|
|
355
|
+
"--query",
|
|
356
|
+
"query",
|
|
357
|
+
type=click.STRING,
|
|
358
|
+
shell_complete=completion.remote_queries_and_sparql_files,
|
|
359
|
+
help=HELP_TEXTS["query"],
|
|
360
|
+
)
|
|
305
361
|
@click.pass_obj
|
|
306
362
|
# pylint: disable-msg=too-many-arguments
|
|
307
363
|
def update_command( # noqa: PLR0913
|
|
@@ -314,6 +370,7 @@ def update_command( # noqa: PLR0913
|
|
|
314
370
|
read_graphs: tuple[str],
|
|
315
371
|
write_graphs: tuple[str],
|
|
316
372
|
actions: tuple[str],
|
|
373
|
+
query: str,
|
|
317
374
|
) -> None:
|
|
318
375
|
"""Update an access condition.
|
|
319
376
|
|
|
@@ -326,6 +383,9 @@ def update_command( # noqa: PLR0913
|
|
|
326
383
|
f"Updating access condition {payload['name']} ... ",
|
|
327
384
|
nl=False,
|
|
328
385
|
)
|
|
386
|
+
query_str = None
|
|
387
|
+
if query:
|
|
388
|
+
query_str = get_query_text(query, {"user", "group", "readGraph", "writeGraph"})
|
|
329
389
|
|
|
330
390
|
update_access_condition(
|
|
331
391
|
iri=iri,
|
|
@@ -335,7 +395,8 @@ def update_command( # noqa: PLR0913
|
|
|
335
395
|
groups=groups,
|
|
336
396
|
read_graphs=read_graphs,
|
|
337
397
|
write_graphs=write_graphs,
|
|
338
|
-
actions=actions,
|
|
398
|
+
actions=[convert_qname_to_iri(qname=_, default_ns=NS_ACTION) for _ in actions],
|
|
399
|
+
query=query_str,
|
|
339
400
|
)
|
|
340
401
|
app.echo_success("done")
|
|
341
402
|
|
|
@@ -394,10 +455,9 @@ def review_command(app: ApplicationContext, raw: bool, user: str, groups: list[s
|
|
|
394
455
|
"""Review grants for a given account.
|
|
395
456
|
|
|
396
457
|
This command has two working modes: (1) You can review the access conditions
|
|
397
|
-
of an actual account
|
|
458
|
+
of an actual account,
|
|
398
459
|
(2) You can review the access conditions of an imaginary account with a set of
|
|
399
|
-
freely added groups (what-if-scenario)
|
|
400
|
-
condition API.
|
|
460
|
+
freely added groups (what-if-scenario).
|
|
401
461
|
|
|
402
462
|
The output of the command is a list of grants the account has based on your input
|
|
403
463
|
and all access conditions loaded in the store. In addition to that, some metadata
|
|
@@ -407,23 +467,19 @@ def review_command(app: ApplicationContext, raw: bool, user: str, groups: list[s
|
|
|
407
467
|
app.echo_debug("Trying to fetch groups from keycloak.")
|
|
408
468
|
keycloak_user = get_user_by_username(username=user)
|
|
409
469
|
if not keycloak_user:
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
" from Keycloak.\n"
|
|
421
|
-
"Use the --group option to assign groups manually (what-if-scenario)."
|
|
422
|
-
) from error
|
|
470
|
+
if user != PUBLIC_USER_URI:
|
|
471
|
+
app.echo_warning(WARNING_UNKNOWN_USER)
|
|
472
|
+
app.echo_warning(WARNING_USE_GROUP)
|
|
473
|
+
else:
|
|
474
|
+
try:
|
|
475
|
+
keycloak_user_groups = user_groups(user_id=keycloak_user[0]["id"])
|
|
476
|
+
groups = [f"{NS_GROUP}{_['name']}" for _ in keycloak_user_groups]
|
|
477
|
+
except (requests.exceptions.HTTPError, IndexError):
|
|
478
|
+
app.echo_warning(WARNING_NO_GROUP_ACCESS)
|
|
479
|
+
app.echo_warning(WARNING_USE_GROUP)
|
|
423
480
|
app.echo_debug(f"Got groups: {groups}")
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
).json()
|
|
481
|
+
account_iri = f"{NS_USER}{user}" if user != PUBLIC_USER_URI else PUBLIC_USER_URI
|
|
482
|
+
review_info: dict = review_graph_rights(account_iri=account_iri, group_iris=groups).json()
|
|
427
483
|
review_info["groupIri"] = groups
|
|
428
484
|
if raw:
|
|
429
485
|
app.echo_info_json(review_info)
|