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.
@@ -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.0"
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
- requests = "^2.32.2"
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,3 +0,0 @@
1
- # scim2-cli
2
-
3
- A scim utility command line.
@@ -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)