scim2-cli 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- scim2_cli-0.1.2/PKG-INFO +94 -0
- scim2_cli-0.1.2/README.md +66 -0
- {scim2_cli-0.1.0 → scim2_cli-0.1.2}/pyproject.toml +3 -2
- scim2_cli-0.1.2/scim2_cli/__init__.py +48 -0
- scim2_cli-0.1.2/scim2_cli/create.py +53 -0
- scim2_cli-0.1.2/scim2_cli/delete.py +65 -0
- scim2_cli-0.1.2/scim2_cli/query.py +132 -0
- scim2_cli-0.1.2/scim2_cli/replace.py +53 -0
- scim2_cli-0.1.2/scim2_cli/search.py +112 -0
- scim2_cli-0.1.2/scim2_cli/test.py +40 -0
- scim2_cli-0.1.2/scim2_cli/utils.py +36 -0
- scim2_cli-0.1.0/PKG-INFO +0 -30
- scim2_cli-0.1.0/README.md +0 -3
- scim2_cli-0.1.0/scim2_cli/__init__.py +0 -129
scim2_cli-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: scim2-cli
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: SCIM application development CLI
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: scim,scim2,provisioning
|
|
7
|
+
Author: Yaal Coop
|
|
8
|
+
Author-email: contact@yaal.coop
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
22
|
+
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
23
|
+
Requires-Dist: scim2-client (>=0.1.6,<0.2.0)
|
|
24
|
+
Requires-Dist: scim2-tester (>=0.1.2,<0.2.0)
|
|
25
|
+
Requires-Dist: sphinx-click-rst-to-ansi-formatter (>=0.1.0,<0.2.0)
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# scim2-cli
|
|
29
|
+
|
|
30
|
+
An utility command line to help you perform requests against a SCIM server, while validating input and response payloads. It also uses [scim2-tester](https://scim2-tester.readthedocs.io) to perform a [SCIM server compliance test](https://scim2-cli.readthedocs.io/en/latest/reference.html#scim-url-test).
|
|
31
|
+
|
|
32
|
+
## What's SCIM anyway?
|
|
33
|
+
|
|
34
|
+
SCIM stands for System for Cross-domain Identity Management, and it is a provisioning protocol.
|
|
35
|
+
Provisioning is the action of managing a set of resources across different services, usually users and groups.
|
|
36
|
+
SCIM is often used between Identity Providers and applications in completion of standards like OAuth2 and OpenID Connect.
|
|
37
|
+
It allows users and groups creations, modifications and deletions to be synchronized between applications.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
pip install scim2-cli
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
Check the [reference](https://scim2-cli.readthedocs.io/en/latest/reference.html) for more details.
|
|
48
|
+
|
|
49
|
+
Here is an example of resource creation:
|
|
50
|
+
|
|
51
|
+
```shell
|
|
52
|
+
$ scim2 https://auth.example --headers "Authorization: Bearer 12345" create user << EOL
|
|
53
|
+
{
|
|
54
|
+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
55
|
+
"userName": "bjensen@example.com"
|
|
56
|
+
}
|
|
57
|
+
EOL
|
|
58
|
+
{
|
|
59
|
+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
60
|
+
"id": "2819c223-7f76-453a-919d-413861904646",
|
|
61
|
+
"userName": "bjensen@example.com",
|
|
62
|
+
"meta": {
|
|
63
|
+
"resourceType": "User",
|
|
64
|
+
"created": "2010-01-23T04:56:22Z",
|
|
65
|
+
"lastModified": "2011-05-13T04:42:34Z",
|
|
66
|
+
"version": 'W\\/"3694e05e9dff590"',
|
|
67
|
+
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Here is an example of resource query:
|
|
73
|
+
|
|
74
|
+
```shell
|
|
75
|
+
$ scim2 https://auth.example --header "Authorization: Bearer 12345" query user 2819c223-7f76-453a-919d-413861904646
|
|
76
|
+
{
|
|
77
|
+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
78
|
+
"id": "2819c223-7f76-453a-919d-413861904646",
|
|
79
|
+
"userName": "bjensen@example.com",
|
|
80
|
+
"meta": {
|
|
81
|
+
"resourceType": "User",
|
|
82
|
+
"created": "2010-01-23T04:56:22Z",
|
|
83
|
+
"lastModified": "2011-05-13T04:42:34Z",
|
|
84
|
+
"version": 'W\\/"3694e05e9dff590"',
|
|
85
|
+
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
scim2-cli belongs in a collection of SCIM tools developed by [Yaal Coop](https://yaal.coop),
|
|
91
|
+
with [scim2-models](https://github.com/yaal-coop/scim2-models),
|
|
92
|
+
[scim2-client](https://github.com/yaal-coop/scim2-client) and
|
|
93
|
+
[scim2-tester](https://github.com/yaal-coop/scim2-tester)
|
|
94
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# scim2-cli
|
|
2
|
+
|
|
3
|
+
An utility command line to help you perform requests against a SCIM server, while validating input and response payloads. It also uses [scim2-tester](https://scim2-tester.readthedocs.io) to perform a [SCIM server compliance test](https://scim2-cli.readthedocs.io/en/latest/reference.html#scim-url-test).
|
|
4
|
+
|
|
5
|
+
## What's SCIM anyway?
|
|
6
|
+
|
|
7
|
+
SCIM stands for System for Cross-domain Identity Management, and it is a provisioning protocol.
|
|
8
|
+
Provisioning is the action of managing a set of resources across different services, usually users and groups.
|
|
9
|
+
SCIM is often used between Identity Providers and applications in completion of standards like OAuth2 and OpenID Connect.
|
|
10
|
+
It allows users and groups creations, modifications and deletions to be synchronized between applications.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
pip install scim2-cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Check the [reference](https://scim2-cli.readthedocs.io/en/latest/reference.html) for more details.
|
|
21
|
+
|
|
22
|
+
Here is an example of resource creation:
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
$ scim2 https://auth.example --headers "Authorization: Bearer 12345" create user << EOL
|
|
26
|
+
{
|
|
27
|
+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
28
|
+
"userName": "bjensen@example.com"
|
|
29
|
+
}
|
|
30
|
+
EOL
|
|
31
|
+
{
|
|
32
|
+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
33
|
+
"id": "2819c223-7f76-453a-919d-413861904646",
|
|
34
|
+
"userName": "bjensen@example.com",
|
|
35
|
+
"meta": {
|
|
36
|
+
"resourceType": "User",
|
|
37
|
+
"created": "2010-01-23T04:56:22Z",
|
|
38
|
+
"lastModified": "2011-05-13T04:42:34Z",
|
|
39
|
+
"version": 'W\\/"3694e05e9dff590"',
|
|
40
|
+
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Here is an example of resource query:
|
|
46
|
+
|
|
47
|
+
```shell
|
|
48
|
+
$ scim2 https://auth.example --header "Authorization: Bearer 12345" query user 2819c223-7f76-453a-919d-413861904646
|
|
49
|
+
{
|
|
50
|
+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
51
|
+
"id": "2819c223-7f76-453a-919d-413861904646",
|
|
52
|
+
"userName": "bjensen@example.com",
|
|
53
|
+
"meta": {
|
|
54
|
+
"resourceType": "User",
|
|
55
|
+
"created": "2010-01-23T04:56:22Z",
|
|
56
|
+
"lastModified": "2011-05-13T04:42:34Z",
|
|
57
|
+
"version": 'W\\/"3694e05e9dff590"',
|
|
58
|
+
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
scim2-cli belongs in a collection of SCIM tools developed by [Yaal Coop](https://yaal.coop),
|
|
64
|
+
with [scim2-models](https://github.com/yaal-coop/scim2-models),
|
|
65
|
+
[scim2-client](https://github.com/yaal-coop/scim2-client) and
|
|
66
|
+
[scim2-tester](https://github.com/yaal-coop/scim2-tester)
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "scim2-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "SCIM application development CLI"
|
|
9
9
|
authors = ["Yaal Coop <contact@yaal.coop>"]
|
|
10
10
|
license = "MIT"
|
|
@@ -27,7 +27,8 @@ classifiers = [
|
|
|
27
27
|
[tool.poetry.dependencies]
|
|
28
28
|
python = "^3.10"
|
|
29
29
|
click = "^8.1.7"
|
|
30
|
-
|
|
30
|
+
scim2-client = "^0.1.6"
|
|
31
|
+
scim2-tester = "^0.1.2"
|
|
31
32
|
sphinx-click-rst-to-ansi-formatter = "^0.1.0"
|
|
32
33
|
|
|
33
34
|
[tool.poetry.group.doc]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from httpx import Client
|
|
5
|
+
from scim2_client import SCIMClient
|
|
6
|
+
from scim2_models import Group
|
|
7
|
+
from scim2_models import User
|
|
8
|
+
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
9
|
+
|
|
10
|
+
from .create import create_cli
|
|
11
|
+
from .delete import delete_cli
|
|
12
|
+
from .query import query_cli
|
|
13
|
+
from .replace import replace_cli
|
|
14
|
+
from .search import search_cli
|
|
15
|
+
from .test import test_cli
|
|
16
|
+
from .utils import DOC_URL
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group(cls=make_rst_to_ansi_formatter(DOC_URL, group=True))
|
|
20
|
+
@click.argument("url")
|
|
21
|
+
@click.pass_context
|
|
22
|
+
def cli(ctx, url):
|
|
23
|
+
"""SCIM application development CLI."""
|
|
24
|
+
|
|
25
|
+
ctx.ensure_object(dict)
|
|
26
|
+
ctx.obj["URL"] = url
|
|
27
|
+
client = Client(base_url=ctx.obj["URL"])
|
|
28
|
+
ctx.obj["client"] = SCIMClient(client, resource_types=(User, Group))
|
|
29
|
+
ctx.obj["resource_types"] = {
|
|
30
|
+
resource_type.__name__.lower(): resource_type
|
|
31
|
+
for resource_type in ctx.obj["client"].resource_types
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if not click.get_text_stream("stdin").isatty(): # pragma: no cover
|
|
35
|
+
if stdin := click.get_text_stream("stdin").read().strip():
|
|
36
|
+
try:
|
|
37
|
+
ctx.obj["stdin"] = json.loads(stdin)
|
|
38
|
+
except json.JSONDecodeError as exc:
|
|
39
|
+
message = f"Invalid JSON input.\n{exc}"
|
|
40
|
+
raise click.ClickException(message) from exc
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
cli.add_command(create_cli)
|
|
44
|
+
cli.add_command(query_cli)
|
|
45
|
+
cli.add_command(replace_cli)
|
|
46
|
+
cli.add_command(delete_cli)
|
|
47
|
+
cli.add_command(search_cli)
|
|
48
|
+
cli.add_command(test_cli)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click import ClickException
|
|
5
|
+
from scim2_client import SCIMClientError
|
|
6
|
+
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
7
|
+
|
|
8
|
+
from .utils import DOC_URL
|
|
9
|
+
from .utils import formatted_payload
|
|
10
|
+
from .utils import split_headers
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="create")
|
|
14
|
+
@click.pass_context
|
|
15
|
+
@click.option(
|
|
16
|
+
"--indent/--no-indent",
|
|
17
|
+
is_flag=True,
|
|
18
|
+
default=True,
|
|
19
|
+
help="Indent JSON response payloads.",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"-h", "--headers", multiple=True, help="Header to pass in the HTTP requests."
|
|
23
|
+
)
|
|
24
|
+
def create_cli(ctx, indent, headers):
|
|
25
|
+
"""Perform a `SCIM POST <https://www.rfc-editor.org/rfc/rfc7644#section-3.3>`_ request
|
|
26
|
+
on resources endpoint.
|
|
27
|
+
|
|
28
|
+
Input data is expected to be passed in JSON format to stdin:
|
|
29
|
+
|
|
30
|
+
.. code-block:: bash
|
|
31
|
+
|
|
32
|
+
echo '{"userName": "bjensen@example.com", "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"]}' | scim https://scim.example create user
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
payload = ctx.obj.get("stdin")
|
|
37
|
+
if not payload:
|
|
38
|
+
raise ClickException("Input data is missing")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response = ctx.obj["client"].create(payload, headers=split_headers(headers))
|
|
42
|
+
|
|
43
|
+
except SCIMClientError as scim_exc:
|
|
44
|
+
message = str(scim_exc)
|
|
45
|
+
if sys.version_info >= (3, 11) and hasattr(
|
|
46
|
+
scim_exc, "__notes__"
|
|
47
|
+
): # pragma: no cover
|
|
48
|
+
for note in scim_exc.__notes__:
|
|
49
|
+
message = f"{message}\n{note}"
|
|
50
|
+
raise ClickException(message) from scim_exc
|
|
51
|
+
|
|
52
|
+
payload = formatted_payload(response.model_dump(), indent)
|
|
53
|
+
click.echo(payload)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click import ClickException
|
|
5
|
+
from scim2_client import SCIMClientError
|
|
6
|
+
from scim2_models import Message
|
|
7
|
+
from scim2_models import Resource
|
|
8
|
+
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
9
|
+
|
|
10
|
+
from .utils import DOC_URL
|
|
11
|
+
from .utils import formatted_payload
|
|
12
|
+
from .utils import split_headers
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="delete")
|
|
16
|
+
@click.argument("resource-type", required=True)
|
|
17
|
+
@click.argument("id", required=True)
|
|
18
|
+
@click.option(
|
|
19
|
+
"-h", "--headers", multiple=True, help="Header to pass in the HTTP requests."
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--indent/--no-indent",
|
|
23
|
+
is_flag=True,
|
|
24
|
+
default=True,
|
|
25
|
+
help="Indent JSON response payloads.",
|
|
26
|
+
)
|
|
27
|
+
@click.pass_context
|
|
28
|
+
def delete_cli(ctx, resource_type, id, headers, indent):
|
|
29
|
+
"""Perform a `SCIM DELETE query <https://www.rfc-editor.org/rfc/rfc7644#section-3.6>`_ request.
|
|
30
|
+
|
|
31
|
+
.. code-block:: bash
|
|
32
|
+
|
|
33
|
+
scim https://scim.example delete user 1234
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
resource_type = ctx.obj["resource_types"][resource_type]
|
|
38
|
+
except KeyError:
|
|
39
|
+
ok_values = ", ".join(ctx.obj["resource_types"])
|
|
40
|
+
raise ClickException(
|
|
41
|
+
f"Unknown resource type '{resource_type}'. Available values are: {ok_values}'"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
response = ctx.obj["client"].delete(
|
|
46
|
+
resource_type, id, headers=split_headers(headers)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
except SCIMClientError as scim_exc:
|
|
50
|
+
message = str(scim_exc)
|
|
51
|
+
if sys.version_info >= (3, 11) and hasattr(
|
|
52
|
+
scim_exc, "__notes__"
|
|
53
|
+
): # pragma: no cover
|
|
54
|
+
for note in scim_exc.__notes__:
|
|
55
|
+
message = f"{message}\n{note}"
|
|
56
|
+
raise ClickException(message) from scim_exc
|
|
57
|
+
|
|
58
|
+
if response:
|
|
59
|
+
payload = (
|
|
60
|
+
response.model_dump()
|
|
61
|
+
if isinstance(response, (Resource, Message))
|
|
62
|
+
else response
|
|
63
|
+
)
|
|
64
|
+
payload = formatted_payload(payload, indent)
|
|
65
|
+
click.echo(payload)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import List
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from click import ClickException
|
|
7
|
+
from scim2_client import SCIMClientError
|
|
8
|
+
from scim2_models import SearchRequest
|
|
9
|
+
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
10
|
+
|
|
11
|
+
from .utils import DOC_URL
|
|
12
|
+
from .utils import formatted_payload
|
|
13
|
+
from .utils import split_headers
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="query")
|
|
17
|
+
@click.pass_context
|
|
18
|
+
@click.argument("resource_type", required=False)
|
|
19
|
+
@click.argument("id", required=False)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--attribute",
|
|
22
|
+
multiple=True,
|
|
23
|
+
help="A multi-valued list of strings indicating the names of resource attributes to return in the response, overriding the set of attributes that would be returned by default.",
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
"--excluded-attribute",
|
|
27
|
+
multiple=True,
|
|
28
|
+
help="A multi-valued list of strings indicating the names of resource attributes to be removed from the default set of attributes to return.",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--start-index",
|
|
32
|
+
type=int,
|
|
33
|
+
help="An integer indicating the 1-based index of the first query result.",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--count",
|
|
37
|
+
type=int,
|
|
38
|
+
help="An integer indicating the desired maximum number of query results per page.",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--filter", help="The filter string used to request a subset of resources."
|
|
42
|
+
)
|
|
43
|
+
@click.option(
|
|
44
|
+
"--sort-by",
|
|
45
|
+
help="A string indicating the attribute whose value SHALL be used to order the returned responses.",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--sort-order",
|
|
49
|
+
help="A string indicating the order in which the “sortBy” parameter is applied.",
|
|
50
|
+
)
|
|
51
|
+
@click.option(
|
|
52
|
+
"-h", "--headers", multiple=True, help="Header to pass in the HTTP requests."
|
|
53
|
+
)
|
|
54
|
+
@click.option(
|
|
55
|
+
"--indent/--no-indent",
|
|
56
|
+
is_flag=True,
|
|
57
|
+
default=True,
|
|
58
|
+
help="Indent JSON response payloads.",
|
|
59
|
+
)
|
|
60
|
+
def query_cli(
|
|
61
|
+
ctx,
|
|
62
|
+
resource_type: Optional[str],
|
|
63
|
+
id: Optional[str],
|
|
64
|
+
attribute: List[str],
|
|
65
|
+
excluded_attribute: List[str],
|
|
66
|
+
start_index: int,
|
|
67
|
+
count: int,
|
|
68
|
+
filter: str,
|
|
69
|
+
sort_by: str,
|
|
70
|
+
sort_order: str,
|
|
71
|
+
headers: List[str],
|
|
72
|
+
indent: bool,
|
|
73
|
+
):
|
|
74
|
+
"""Perform a `SCIM GET <https://www.rfc-editor.org/rfc/rfc7644#section-3.4.1>`_ request
|
|
75
|
+
on the :code:`RESOURCE_TYPE` endpoint.
|
|
76
|
+
|
|
77
|
+
- If :code:`RESOURCE_TYPE` is :code:`user` and :code:`id` is `1234`, then the request will made on the :code:`/Users/1234` endpoint.
|
|
78
|
+
- If :code:`RESOURCE_TYPE` is :code:`user` and :code:`id` is not set, then the request will made on the :code:`/Users` endpoint.
|
|
79
|
+
- If :code:`RESOURCE_TYPE` is not set, then the request will made on the :code:`/` endpoint.
|
|
80
|
+
|
|
81
|
+
Data passed in JSON format to stdin is sent as request arguments and all the other query arguments are ignored:
|
|
82
|
+
|
|
83
|
+
.. code-block:: bash
|
|
84
|
+
|
|
85
|
+
echo '{"startIndex": 50, "count": 10}' | scim https://scim.example query user
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
if resource_type:
|
|
89
|
+
try:
|
|
90
|
+
resource_type = ctx.obj["resource_types"][resource_type]
|
|
91
|
+
except KeyError:
|
|
92
|
+
ok_values = ", ".join(ctx.obj["resource_types"])
|
|
93
|
+
raise ClickException(
|
|
94
|
+
f"Unknown resource type '{resource_type}. Available values are: {ok_values}'"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if ctx.obj.get("stdin"):
|
|
98
|
+
check_request_payload = False
|
|
99
|
+
payload = ctx.obj.get("stdin")
|
|
100
|
+
|
|
101
|
+
else:
|
|
102
|
+
check_request_payload = True
|
|
103
|
+
payload = SearchRequest(
|
|
104
|
+
attributes=attribute,
|
|
105
|
+
excluded_attributes=excluded_attribute,
|
|
106
|
+
start_index=start_index,
|
|
107
|
+
count=count,
|
|
108
|
+
filter=filter,
|
|
109
|
+
sort_by=sort_by,
|
|
110
|
+
sort_order=sort_order,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
response = ctx.obj["client"].query(
|
|
115
|
+
resource_type,
|
|
116
|
+
id,
|
|
117
|
+
search_request=payload,
|
|
118
|
+
check_request_payload=check_request_payload,
|
|
119
|
+
headers=split_headers(headers),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
except SCIMClientError as scim_exc:
|
|
123
|
+
message = str(scim_exc)
|
|
124
|
+
if sys.version_info >= (3, 11) and hasattr(
|
|
125
|
+
scim_exc, "__notes__"
|
|
126
|
+
): # pragma: no cover
|
|
127
|
+
for note in scim_exc.__notes__:
|
|
128
|
+
message = f"{message}\n{note}"
|
|
129
|
+
raise ClickException(message) from scim_exc
|
|
130
|
+
|
|
131
|
+
payload = formatted_payload(response.model_dump(), indent)
|
|
132
|
+
click.echo(payload)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click import ClickException
|
|
5
|
+
from scim2_client import SCIMClientError
|
|
6
|
+
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
7
|
+
|
|
8
|
+
from .utils import DOC_URL
|
|
9
|
+
from .utils import formatted_payload
|
|
10
|
+
from .utils import split_headers
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="replace")
|
|
14
|
+
@click.pass_context
|
|
15
|
+
@click.option(
|
|
16
|
+
"-h", "--headers", multiple=True, help="Header to pass in the HTTP requests."
|
|
17
|
+
)
|
|
18
|
+
@click.option(
|
|
19
|
+
"--indent/--no-indent",
|
|
20
|
+
is_flag=True,
|
|
21
|
+
default=True,
|
|
22
|
+
help="Indent JSON response payloads.",
|
|
23
|
+
)
|
|
24
|
+
def replace_cli(ctx, headers, indent):
|
|
25
|
+
"""Perform a `SCIM PUT <https://www.rfc-editor.org/rfc/rfc7644#section-3.5.1>`_ request
|
|
26
|
+
on the resources endpoint.
|
|
27
|
+
|
|
28
|
+
Input data is expected to be passed in JSON format to stdin:
|
|
29
|
+
|
|
30
|
+
.. code-block:: bash
|
|
31
|
+
|
|
32
|
+
echo '{"userName": "bjensen@example.com", "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "1234"}' | scim https://scim.example replace user
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
payload = ctx.obj.get("stdin")
|
|
37
|
+
if not payload:
|
|
38
|
+
raise ClickException("Input data is missing")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response = ctx.obj["client"].replace(payload, headers=split_headers(headers))
|
|
42
|
+
|
|
43
|
+
except SCIMClientError as scim_exc:
|
|
44
|
+
message = str(scim_exc)
|
|
45
|
+
if sys.version_info >= (3, 11) and hasattr(
|
|
46
|
+
scim_exc, "__notes__"
|
|
47
|
+
): # pragma: no cover
|
|
48
|
+
for note in scim_exc.__notes__:
|
|
49
|
+
message = f"{message}\n{note}"
|
|
50
|
+
raise ClickException(message) from scim_exc
|
|
51
|
+
|
|
52
|
+
payload = formatted_payload(response.model_dump(), indent)
|
|
53
|
+
click.echo(payload)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from click import ClickException
|
|
6
|
+
from scim2_client import SCIMClientError
|
|
7
|
+
from scim2_models import SearchRequest
|
|
8
|
+
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
9
|
+
|
|
10
|
+
from .utils import DOC_URL
|
|
11
|
+
from .utils import formatted_payload
|
|
12
|
+
from .utils import split_headers
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="search")
|
|
16
|
+
@click.pass_context
|
|
17
|
+
@click.option(
|
|
18
|
+
"--attribute",
|
|
19
|
+
multiple=True,
|
|
20
|
+
help="A multi-valued list of strings indicating the names of resource attributes to return in the response, overriding the set of attributes that would be returned by default.",
|
|
21
|
+
)
|
|
22
|
+
@click.option(
|
|
23
|
+
"--excluded-attribute",
|
|
24
|
+
multiple=True,
|
|
25
|
+
help="A multi-valued list of strings indicating the names of resource attributes to be removed from the default set of attributes to return.",
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
"--start-index",
|
|
29
|
+
type=int,
|
|
30
|
+
help="An integer indicating the 1-based index of the first query result.",
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"--count",
|
|
34
|
+
type=int,
|
|
35
|
+
help="An integer indicating the desired maximum number of query results per page.",
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
"--filter", help="The filter string used to request a subset of resources."
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--sort-by",
|
|
42
|
+
help="A string indicating the attribute whose value SHALL be used to order the returned responses.",
|
|
43
|
+
)
|
|
44
|
+
@click.option(
|
|
45
|
+
"--sort-order",
|
|
46
|
+
help="A string indicating the order in which the “sortBy” parameter is applied.",
|
|
47
|
+
)
|
|
48
|
+
@click.option(
|
|
49
|
+
"-h", "--headers", multiple=True, help="Header to pass in the HTTP requests."
|
|
50
|
+
)
|
|
51
|
+
@click.option(
|
|
52
|
+
"--indent/--no-indent",
|
|
53
|
+
is_flag=True,
|
|
54
|
+
default=True,
|
|
55
|
+
help="Indent JSON response payloads.",
|
|
56
|
+
)
|
|
57
|
+
def search_cli(
|
|
58
|
+
ctx,
|
|
59
|
+
attribute: List[str],
|
|
60
|
+
excluded_attribute: List[str],
|
|
61
|
+
start_index: int,
|
|
62
|
+
count: int,
|
|
63
|
+
filter: str,
|
|
64
|
+
sort_by: str,
|
|
65
|
+
sort_order: str,
|
|
66
|
+
headers: List[str],
|
|
67
|
+
indent: bool,
|
|
68
|
+
):
|
|
69
|
+
"""Perform a `SCIM GET <https://www.rfc-editor.org/rfc/rfc7644#section-3.4.1>`_ request
|
|
70
|
+
on the :code:`/.search` endpoint.
|
|
71
|
+
|
|
72
|
+
Data passed in JSON format to stdin is sent as request arguments and all the other query arguments are ignored:
|
|
73
|
+
|
|
74
|
+
.. code-block:: bash
|
|
75
|
+
|
|
76
|
+
echo '{"startIndex": 50, "count": 10}' | scim https://scim.example search user
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
if ctx.obj.get("stdin"):
|
|
80
|
+
check_request_payload = False
|
|
81
|
+
payload = ctx.obj.get("stdin")
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
check_request_payload = True
|
|
85
|
+
payload = SearchRequest(
|
|
86
|
+
attributes=attribute,
|
|
87
|
+
excluded_attributes=excluded_attribute,
|
|
88
|
+
start_index=start_index,
|
|
89
|
+
count=count,
|
|
90
|
+
filter=filter,
|
|
91
|
+
sort_by=sort_by,
|
|
92
|
+
sort_order=sort_order,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
response = ctx.obj["client"].search(
|
|
97
|
+
search_request=payload,
|
|
98
|
+
check_request_payload=check_request_payload,
|
|
99
|
+
headers=split_headers(headers),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
except SCIMClientError as scim_exc:
|
|
103
|
+
message = str(scim_exc)
|
|
104
|
+
if sys.version_info >= (3, 11) and hasattr(
|
|
105
|
+
scim_exc, "__notes__"
|
|
106
|
+
): # pragma: no cover
|
|
107
|
+
for note in scim_exc.__notes__:
|
|
108
|
+
message = f"{message}\n{note}"
|
|
109
|
+
raise ClickException(message) from scim_exc
|
|
110
|
+
|
|
111
|
+
payload = formatted_payload(response.model_dump(), indent)
|
|
112
|
+
click.echo(payload)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from scim2_tester import Status
|
|
3
|
+
from scim2_tester import check_server
|
|
4
|
+
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
5
|
+
|
|
6
|
+
from .utils import DOC_URL
|
|
7
|
+
from .utils import Color
|
|
8
|
+
from .utils import split_headers
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="test")
|
|
12
|
+
@click.pass_context
|
|
13
|
+
@click.option(
|
|
14
|
+
"-h", "--headers", multiple=True, help="Header to pass in the HTTP requests."
|
|
15
|
+
)
|
|
16
|
+
@click.option("-v", "--verbose", is_flag=True, help="Enables verbose mode")
|
|
17
|
+
def test_cli(ctx, headers, verbose):
|
|
18
|
+
"""Perform a server SCIM compliance check using :doc:`scim2-tester
|
|
19
|
+
<scim2-tester:index>`.
|
|
20
|
+
|
|
21
|
+
.. code-block:: bash
|
|
22
|
+
|
|
23
|
+
scim https://scim.example test
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
client = ctx.obj["client"]
|
|
27
|
+
client.client.headers.update(split_headers(headers))
|
|
28
|
+
results = check_server(client)
|
|
29
|
+
click.echo(f"Performing a SCIM compliance check on {client.client.base_url} ...")
|
|
30
|
+
for result in results:
|
|
31
|
+
if result.status == Status.SUCCESS:
|
|
32
|
+
status = click.style(result.status.name, fg=Color.green)
|
|
33
|
+
else:
|
|
34
|
+
status = click.style(result.status.name, fg=Color.red)
|
|
35
|
+
click.echo(f"{status} {result.title}")
|
|
36
|
+
|
|
37
|
+
if result.reason:
|
|
38
|
+
click.echo(f" {result.reason}")
|
|
39
|
+
if verbose and result.data:
|
|
40
|
+
click.echo(f" {result.data}")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
DOC_URL = "https://scim2-cli.readthedocs.io/"
|
|
5
|
+
INDENTATION_SIZE = 4
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Color(str, Enum):
|
|
9
|
+
black = "black"
|
|
10
|
+
red = "red"
|
|
11
|
+
green = "green"
|
|
12
|
+
yellow = "yellow"
|
|
13
|
+
blue = "blue"
|
|
14
|
+
magenta = "magenta"
|
|
15
|
+
cyan = "cyan"
|
|
16
|
+
white = "white"
|
|
17
|
+
bright_black = "bright_black"
|
|
18
|
+
bright_red = "bright_red"
|
|
19
|
+
bright_green = "bright_green"
|
|
20
|
+
bright_yellow = "bright_yellow"
|
|
21
|
+
bright_blue = "bright_blue"
|
|
22
|
+
bright_magenta = "bright_magenta"
|
|
23
|
+
bright_cyan = "bright_cyan"
|
|
24
|
+
bright_white = "bright_white"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def formatted_payload(obj, indent):
|
|
28
|
+
indent = INDENTATION_SIZE if indent else False
|
|
29
|
+
return json.dumps(obj, indent=indent)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def split_headers(headers):
|
|
33
|
+
return {
|
|
34
|
+
header[: header.index(":")].strip(): header[header.index(":") + 1 :].strip()
|
|
35
|
+
for header in headers
|
|
36
|
+
}
|
scim2_cli-0.1.0/PKG-INFO
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: scim2-cli
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: SCIM application development CLI
|
|
5
|
-
License: MIT
|
|
6
|
-
Keywords: scim,scim2,provisioning
|
|
7
|
-
Author: Yaal Coop
|
|
8
|
-
Author-email: contact@yaal.coop
|
|
9
|
-
Requires-Python: >=3.10,<4.0
|
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
|
11
|
-
Classifier: Environment :: Web Environment
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: Programming Language :: Python
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
22
|
-
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
23
|
-
Requires-Dist: requests (>=2.32.2,<3.0.0)
|
|
24
|
-
Requires-Dist: sphinx-click-rst-to-ansi-formatter (>=0.1.0,<0.2.0)
|
|
25
|
-
Description-Content-Type: text/markdown
|
|
26
|
-
|
|
27
|
-
# scim2-cli
|
|
28
|
-
|
|
29
|
-
A scim utility command line.
|
|
30
|
-
|
scim2_cli-0.1.0/README.md
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
import requests
|
|
5
|
-
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
6
|
-
|
|
7
|
-
DOC_URL = "https://scim2-cli.readthedocs.io/"
|
|
8
|
-
BASE_HEADERS = {
|
|
9
|
-
"Accept": "application/scim+json",
|
|
10
|
-
"Content-Type": "application/scim+json",
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@click.group(cls=make_rst_to_ansi_formatter(DOC_URL, group=True))
|
|
15
|
-
@click.argument("url")
|
|
16
|
-
@click.pass_context
|
|
17
|
-
def cli(ctx, url):
|
|
18
|
-
"""SCIM application development CLI."""
|
|
19
|
-
ctx.ensure_object(dict)
|
|
20
|
-
ctx.obj["URL"] = url
|
|
21
|
-
|
|
22
|
-
if not click.get_text_stream("stdin").isatty(): # pragma: no cover
|
|
23
|
-
stdin = click.get_text_stream("stdin").read().strip()
|
|
24
|
-
ctx.obj["STDIN"] = json.loads(stdin)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@cli.command(cls=make_rst_to_ansi_formatter(DOC_URL))
|
|
28
|
-
@click.pass_context
|
|
29
|
-
def get(ctx):
|
|
30
|
-
"""Perform a `SCIM GET <https://www.rfc-editor.org/rfc/rfc7644#section-3.4.1>`_ request.
|
|
31
|
-
|
|
32
|
-
Data passed in JSON format to stdin is sent as request arguments:
|
|
33
|
-
|
|
34
|
-
.. code-block:: bash
|
|
35
|
-
|
|
36
|
-
echo '{"foo": "bar"}' | scim https://scim.example get
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
response = requests.get(
|
|
40
|
-
ctx.obj["URL"],
|
|
41
|
-
params=ctx.obj.get("STDIN"),
|
|
42
|
-
headers=BASE_HEADERS,
|
|
43
|
-
allow_redirects=True,
|
|
44
|
-
)
|
|
45
|
-
click.echo(response.text)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@cli.command(cls=make_rst_to_ansi_formatter(DOC_URL))
|
|
49
|
-
@click.pass_context
|
|
50
|
-
def post(ctx):
|
|
51
|
-
"""Perform a `SCIM POST <https://www.rfc-editor.org/rfc/rfc7644#section-3.3>`_ request.
|
|
52
|
-
|
|
53
|
-
Data passed in JSON format to stdin is sent as request payload:
|
|
54
|
-
|
|
55
|
-
.. code-block:: bash
|
|
56
|
-
|
|
57
|
-
echo '{"foo": "bar"}' | scim https://scim.example post
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
response = requests.post(
|
|
61
|
-
ctx.obj["URL"],
|
|
62
|
-
json=ctx.obj.get("STDIN"),
|
|
63
|
-
headers=BASE_HEADERS,
|
|
64
|
-
allow_redirects=True,
|
|
65
|
-
)
|
|
66
|
-
click.echo(response.text)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@cli.command(cls=make_rst_to_ansi_formatter(DOC_URL))
|
|
70
|
-
@click.pass_context
|
|
71
|
-
def put(ctx):
|
|
72
|
-
"""Perform a `SCIM PUT <https://www.rfc-editor.org/rfc/rfc7644#section-3.5.1>`_ request.
|
|
73
|
-
|
|
74
|
-
Data passed in JSON format to stdin is sent as request payload:
|
|
75
|
-
|
|
76
|
-
.. code-block:: bash
|
|
77
|
-
|
|
78
|
-
echo '{"foo": "bar"}' | scim https://scim.example put
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
response = requests.put(
|
|
82
|
-
ctx.obj["URL"],
|
|
83
|
-
json=ctx.obj.get("STDIN"),
|
|
84
|
-
headers=BASE_HEADERS,
|
|
85
|
-
allow_redirects=True,
|
|
86
|
-
)
|
|
87
|
-
click.echo(response.text)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@cli.command(cls=make_rst_to_ansi_formatter(DOC_URL))
|
|
91
|
-
@click.pass_context
|
|
92
|
-
def patch(ctx):
|
|
93
|
-
"""Perform a `SCIM PATCH <https://www.rfc-editor.org/rfc/rfc7644#section-3.5.2>`_ request.
|
|
94
|
-
|
|
95
|
-
Data passed in JSON format to stdin is sent as request payload:
|
|
96
|
-
|
|
97
|
-
.. code-block:: bash
|
|
98
|
-
|
|
99
|
-
echo '{"foo": "bar"}' | scim https://scim.example patch
|
|
100
|
-
"""
|
|
101
|
-
|
|
102
|
-
response = requests.patch(
|
|
103
|
-
ctx.obj["URL"],
|
|
104
|
-
json=ctx.obj.get("STDIN"),
|
|
105
|
-
headers=BASE_HEADERS,
|
|
106
|
-
allow_redirects=True,
|
|
107
|
-
)
|
|
108
|
-
click.echo(response.text)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
@cli.command(cls=make_rst_to_ansi_formatter(DOC_URL))
|
|
112
|
-
@click.pass_context
|
|
113
|
-
def delete(ctx):
|
|
114
|
-
"""Perform a `SCIM DELETE <https://www.rfc-editor.org/rfc/rfc7644#section-3.6>`_ request.
|
|
115
|
-
|
|
116
|
-
Data passed in JSON format to stdin is sent as request payload:
|
|
117
|
-
|
|
118
|
-
.. code-block:: bash
|
|
119
|
-
|
|
120
|
-
echo '{"foo": "bar"}' | scim https://scim.example delete
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
response = requests.delete(
|
|
124
|
-
ctx.obj["URL"],
|
|
125
|
-
json=ctx.obj.get("STDIN"),
|
|
126
|
-
headers=BASE_HEADERS,
|
|
127
|
-
allow_redirects=True,
|
|
128
|
-
)
|
|
129
|
-
click.echo(response.text)
|