scim2-cli 0.2.0__tar.gz → 0.2.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.2.0 → scim2_cli-0.2.2}/.pre-commit-config.yaml +1 -1
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/PKG-INFO +2 -2
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/changelog.rst +16 -0
- scim2_cli-0.2.2/doc/tutorial.rst +250 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/pyproject.toml +2 -2
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/__init__.py +25 -9
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/create.py +6 -13
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/delete.py +3 -9
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/query.py +3 -9
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/replace.py +3 -9
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/search.py +3 -10
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/test.py +21 -1
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/utils.py +10 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_cli.py +163 -14
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_test.py +9 -2
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/uv.lock +33 -33
- scim2_cli-0.2.0/doc/tutorial.rst +0 -47
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.github/FUNDING.yml +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.github/workflows/release.yml +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.github/workflows/tests.yaml +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.gitignore +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.readthedocs.yml +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/LICENSE.md +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/README.md +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/__init__.py +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/conf.py +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/contributing.rst +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/index.rst +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/reference.rst +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/py.typed +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/__init__.py +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/conftest.py +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_create.py +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_delete.py +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_query.py +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_replace.py +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_search.py +0 -0
- {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: scim2-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: SCIM application development CLI
|
|
5
5
|
Project-URL: documentation, https://scim2-cli.readthedocs.io
|
|
6
6
|
Project-URL: repository, https://github.com/python-scim/scim2-cli
|
|
@@ -224,7 +224,7 @@ Requires-Python: >=3.10
|
|
|
224
224
|
Requires-Dist: click>=8.1.7
|
|
225
225
|
Requires-Dist: pydanclick>=0.3.0
|
|
226
226
|
Requires-Dist: pygments>=2.18.0
|
|
227
|
-
Requires-Dist: scim2-client>=0.4.
|
|
227
|
+
Requires-Dist: scim2-client>=0.4.3
|
|
228
228
|
Requires-Dist: scim2-tester[httpx]>=0.1.10
|
|
229
229
|
Requires-Dist: sphinx-click-rst-to-ansi-formatter>=0.1.0
|
|
230
230
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
[0.2.2] - 2024-12-06
|
|
5
|
+
--------------------
|
|
6
|
+
|
|
7
|
+
Added
|
|
8
|
+
^^^^^
|
|
9
|
+
- The :ref:`test` command returns 1 in case of errors.
|
|
10
|
+
- Added :ref:`test` ``--dont-check-content-type`` and ``--dont-check-status-code`` options.
|
|
11
|
+
|
|
12
|
+
[0.2.1] - 2024-12-04
|
|
13
|
+
--------------------
|
|
14
|
+
|
|
15
|
+
Added
|
|
16
|
+
^^^^^
|
|
17
|
+
- Display server discovery step network connections.
|
|
18
|
+
- Support loading server configuration objects in :class:`~scim2_models.ListResponses`.
|
|
19
|
+
|
|
4
20
|
[0.2.0] - 2024-12-03
|
|
5
21
|
--------------------
|
|
6
22
|
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
Tutorial
|
|
2
|
+
========
|
|
3
|
+
|
|
4
|
+
The :ref:`cli` command allows you to interact with a SCIM server.
|
|
5
|
+
|
|
6
|
+
.. tip::
|
|
7
|
+
|
|
8
|
+
If you want to test scim2-cli with a real SCIM server, you can install and run `scim2-server <https://github.com/python-scim/scim2-server>`__:
|
|
9
|
+
|
|
10
|
+
.. code-block:: shell
|
|
11
|
+
|
|
12
|
+
$ pip install scim2-server
|
|
13
|
+
$ scim2-server
|
|
14
|
+
|
|
15
|
+
Then you have a functional SCIM server available at http://127.0.0.1:8080 that you can use to test the different commands.
|
|
16
|
+
|
|
17
|
+
Basic parameters
|
|
18
|
+
----------------
|
|
19
|
+
|
|
20
|
+
In order to connect to a SCIM server you will need to pass the :option:`--url <scim --url>` parameter.
|
|
21
|
+
You can also pass additional headers, such as authentication ones, with :option:`--header <scim --header>`.
|
|
22
|
+
|
|
23
|
+
.. code-block:: shell
|
|
24
|
+
|
|
25
|
+
$ scim2 --url https://auth.example --header "Authorization: Bearer 12345" create user --user-name "bjensen@example.com"
|
|
26
|
+
{
|
|
27
|
+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
28
|
+
"id": "2819c223-7f76-453a-919d-413861904646",
|
|
29
|
+
"userName": "bjensen@example.com",
|
|
30
|
+
"meta": {
|
|
31
|
+
"resourceType": "User",
|
|
32
|
+
"created": "2010-01-23T04:56:22Z",
|
|
33
|
+
"lastModified": "2011-05-13T04:42:34Z",
|
|
34
|
+
"version": 'W\\/"3694e05e9dff590"',
|
|
35
|
+
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
However passing those parameters each time you use the command can be annoying.
|
|
40
|
+
To make commands shorter, you can set those parameters once for all by using the :ref:`SCIM_CLI_URL <scim-url-scim_cli_url>` and :ref:`SCIM_CLI_HEADERS <scim-header-scim_cli_headers>` environment vars.
|
|
41
|
+
|
|
42
|
+
.. code-block:: shell
|
|
43
|
+
|
|
44
|
+
$ export SCIM_CLI_URL="https://auth.example"
|
|
45
|
+
$ export SCIM_CLI_HEADERS="Authorization: Bearer 12345"
|
|
46
|
+
$ scim2 create user --user-name "bjensen@example.com"
|
|
47
|
+
{
|
|
48
|
+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
49
|
+
"id": "2819c223-7f76-453a-919d-413861904646",
|
|
50
|
+
"userName": "bjensen@example.com",
|
|
51
|
+
"meta": {
|
|
52
|
+
"resourceType": "User",
|
|
53
|
+
"created": "2010-01-23T04:56:22Z",
|
|
54
|
+
"lastModified": "2011-05-13T04:42:34Z",
|
|
55
|
+
"version": 'W\\/"3694e05e9dff590"',
|
|
56
|
+
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
Server configuration
|
|
62
|
+
--------------------
|
|
63
|
+
|
|
64
|
+
Before doing anything, the CLI needs to reach the server configuration to find out which features are available,
|
|
65
|
+
which resource schemas are available, and where they are located.
|
|
66
|
+
|
|
67
|
+
By default the CLI will automatically discover those resources on the server, before each command is run.
|
|
68
|
+
|
|
69
|
+
However you might find too time consuming to achieve all those network requests.
|
|
70
|
+
You can store those data locally and reuse them for future command runs thanks to the
|
|
71
|
+
:option:`--schemas <scim --schemas>`, :option:`--resource-types <scim --resource-types>` and :option:`--service-provider-config <scim --service-provider-config>` (and their corresponding environment vars :ref:`SCIM_CLI_SCHEMAS <scim-schemas-scim_cli_schemas>`, :ref:`SCIM_CLI_RESOURCE_TYPES <scim-resource_types-scim_cli_resource_types>` and :ref:`SCIM_CLI_SERVICE_PROVIDER_CONFIG <scim-service_provider_config-scim_cli_service_provider_config>`)
|
|
72
|
+
|
|
73
|
+
.. code-block:: shell
|
|
74
|
+
:caption: Save the configuration resources
|
|
75
|
+
|
|
76
|
+
$ scim query schema > /tmp/schemas.json
|
|
77
|
+
$ scim query resourcetype > /tmp/resource-types.json
|
|
78
|
+
$ scim query serviceproviderconfig > /tmp/service-provider-config.json
|
|
79
|
+
|
|
80
|
+
.. code-block:: shell
|
|
81
|
+
:caption: Load the cached resources
|
|
82
|
+
|
|
83
|
+
$ export SCIM_SCHEMAS=/tmp/schemas.json
|
|
84
|
+
$ export SCIM_RESOURCE_TYPES=/tmp/resource-types.json
|
|
85
|
+
$ export SCIM_SERVICE_PROVIDER_CONFIG=/tmp/service-provider-config.json
|
|
86
|
+
$ scim2 query ...
|
|
87
|
+
|
|
88
|
+
Query and search resources
|
|
89
|
+
--------------------------
|
|
90
|
+
|
|
91
|
+
The :ref:`query` and :ref:`search` commands can be used to look for resources.
|
|
92
|
+
:ref:`query` performs ag `GET` request on the resources endpoint, while :ref:`search` performs a `POST` request on the ``/.search`` endpoint.
|
|
93
|
+
Both commands take similar options such as :option:`--count <scim-query.--count>` or :option:`--attributes <scim-query.--attributes>`.
|
|
94
|
+
An exhaustive list of options can be found on the :doc:`reference`.
|
|
95
|
+
:ref:`query` can also take a :option:`RESOURCE_TYPE <scim-query.RESOURCE_TYPE>` and a :option:`ID <scim-query.ID>` parameters.
|
|
96
|
+
|
|
97
|
+
- If none are set, all the resources of the server are queried.
|
|
98
|
+
|
|
99
|
+
.. code-block:: console
|
|
100
|
+
:caption: Querying all the resources from the server.
|
|
101
|
+
|
|
102
|
+
$ scim query
|
|
103
|
+
{
|
|
104
|
+
"schemas": [
|
|
105
|
+
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
|
|
106
|
+
],
|
|
107
|
+
"totalResults": xx,
|
|
108
|
+
"startIndex": 1,
|
|
109
|
+
"itemsPerPage": 50,
|
|
110
|
+
"Resources": [...]
|
|
111
|
+
}
|
|
112
|
+
- If :option:`RESOURCE_TYPE <scim-query.RESOURCE_TYPE>` is set and :option:`ID <scim-query.ID>` is unset, all the resource of the kind passed in parameter are returned.
|
|
113
|
+
|
|
114
|
+
.. code-block:: console
|
|
115
|
+
:caption: Querying all the users from the server.
|
|
116
|
+
|
|
117
|
+
$ scim query user
|
|
118
|
+
{
|
|
119
|
+
"schemas": [
|
|
120
|
+
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
|
|
121
|
+
],
|
|
122
|
+
"totalResults": xx,
|
|
123
|
+
"startIndex": 1,
|
|
124
|
+
"itemsPerPage": 50,
|
|
125
|
+
"Resources": [...]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
- If both options are set, the very resource with the ID are returned.
|
|
129
|
+
|
|
130
|
+
.. code-block:: console
|
|
131
|
+
:caption: Querying the user with the ID `38b044dd95624c4186f5614fca30305d`
|
|
132
|
+
|
|
133
|
+
$ scim query user 38b044dd95624c4186f5614fca30305d
|
|
134
|
+
{
|
|
135
|
+
"schemas": [
|
|
136
|
+
"urn:ietf:params:scim:schemas:core:2.0:User",
|
|
137
|
+
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
|
|
138
|
+
],
|
|
139
|
+
"id": "38b044dd95624c4186f5614fca30305d",
|
|
140
|
+
"meta": {
|
|
141
|
+
"resourceType": "User",
|
|
142
|
+
"created": "2024-12-05T16:08:51.896646Z",
|
|
143
|
+
"lastModified": "2024-12-05T16:08:51.896646Z",
|
|
144
|
+
"location": "http://scim.example/v2/Users/38b044dd95624c4186f5614fca30305d",
|
|
145
|
+
"version": "W/\"637b1ce03c010cd55fe45b6f7be2247b5159b135\""
|
|
146
|
+
},
|
|
147
|
+
"userName": "bjensen@example.com"
|
|
148
|
+
}
|
|
149
|
+
Create and replace resources
|
|
150
|
+
----------------------------
|
|
151
|
+
|
|
152
|
+
The :ref:`create` and :ref:`replace` commands can be used to edit resources.
|
|
153
|
+
|
|
154
|
+
Options for those commands are dynamically generated, depending on the resource attributes available on the server.
|
|
155
|
+
For instance, for the :class:`~scim2_models.User` resource, you have a ``--user-name`` option.
|
|
156
|
+
You can have a look at the exhaustive list of options by running ``scim create user --help``.
|
|
157
|
+
|
|
158
|
+
.. code-block:: console
|
|
159
|
+
:caption: Creation of an user.
|
|
160
|
+
|
|
161
|
+
$ scim create user --user-name bjensen@example.com
|
|
162
|
+
{
|
|
163
|
+
"schemas": [
|
|
164
|
+
"urn:ietf:params:scim:schemas:core:2.0:User",
|
|
165
|
+
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
|
|
166
|
+
],
|
|
167
|
+
"id": "38b044dd95624c4186f5614fca30305d",
|
|
168
|
+
"meta": {
|
|
169
|
+
"resourceType": "User",
|
|
170
|
+
"created": "2024-12-05T16:08:51.896646Z",
|
|
171
|
+
"lastModified": "2024-12-05T16:08:51.896646Z",
|
|
172
|
+
"location": "http://scim.example/v2/Users/38b044dd95624c4186f5614fca30305d",
|
|
173
|
+
"version": "W/\"637b1ce03c010cd55fe45b6f7be2247b5159b135\""
|
|
174
|
+
},
|
|
175
|
+
"userName": "bjensen@example.com"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
Delete resources
|
|
179
|
+
----------------
|
|
180
|
+
|
|
181
|
+
The :ref:`delete` command allows you to delete resources.
|
|
182
|
+
|
|
183
|
+
.. code-block:: console
|
|
184
|
+
:caption: Deletion of an user.
|
|
185
|
+
|
|
186
|
+
$ scim delete user 38b044dd95624c4186f5614fca30305d
|
|
187
|
+
|
|
188
|
+
Perform a SCIM compliance test
|
|
189
|
+
------------------------------
|
|
190
|
+
|
|
191
|
+
The :ref:`test` command runs a series of resource creation, edition and deletions to check that your server complies with the SCIM specifications.
|
|
192
|
+
See the :doc:`scim2-tester documentation <scim2_tester:index>` for more details on which tests are performed.
|
|
193
|
+
|
|
194
|
+
.. code-block:: console
|
|
195
|
+
:caption: SCIM compliance test
|
|
196
|
+
|
|
197
|
+
$ scim test
|
|
198
|
+
Performing a SCIM compliance check on http://localhost:8080 ...
|
|
199
|
+
SUCCESS check_service_provider_config_endpoint
|
|
200
|
+
SUCCESS check_query_all_resource_types
|
|
201
|
+
SUCCESS check_query_resource_type_by_id
|
|
202
|
+
Successfully accessed the /ResourceTypes/User endpoint.
|
|
203
|
+
SUCCESS check_query_resource_type_by_id
|
|
204
|
+
Successfully accessed the /ResourceTypes/Group endpoint.
|
|
205
|
+
SUCCESS check_access_invalid_resource_typ
|
|
206
|
+
...
|
|
207
|
+
|
|
208
|
+
JSON input
|
|
209
|
+
----------
|
|
210
|
+
|
|
211
|
+
scim2-cli will also read input data from the standard input.
|
|
212
|
+
This can be used to send custom payloads to the SCIM server.
|
|
213
|
+
|
|
214
|
+
When user with :ref:`query` and :ref:`search`, the input value must be a JSON representation of a :class:`~scim2_models.SearchRequest` object:
|
|
215
|
+
|
|
216
|
+
.. code-block:: console
|
|
217
|
+
:caption: Search of an user by passing a custom payload.
|
|
218
|
+
|
|
219
|
+
$ echo '{"startIndex": 50, "count": 10}' | scim query user
|
|
220
|
+
{
|
|
221
|
+
"schemas": [
|
|
222
|
+
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
|
|
223
|
+
],
|
|
224
|
+
"totalResults": xx,
|
|
225
|
+
"startIndex": 50,
|
|
226
|
+
"itemsPerPage": 10,
|
|
227
|
+
"Resources": [...]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
When used with :ref:`create` and :ref:`replace`, no subcommand is needed and the endpoint is guessed from the payload.
|
|
231
|
+
|
|
232
|
+
.. code-block:: console
|
|
233
|
+
:caption: Creation of an user by passing a custom payload.
|
|
234
|
+
|
|
235
|
+
$ echo '{"userName": "bjensen@example.com", "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"]}' | scim create
|
|
236
|
+
{
|
|
237
|
+
"schemas": [
|
|
238
|
+
"urn:ietf:params:scim:schemas:core:2.0:User",
|
|
239
|
+
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
|
|
240
|
+
],
|
|
241
|
+
"id": "38b044dd95624c4186f5614fca30305d",
|
|
242
|
+
"meta": {
|
|
243
|
+
"resourceType": "User",
|
|
244
|
+
"created": "2024-12-05T16:08:51.896646Z",
|
|
245
|
+
"lastModified": "2024-12-05T16:08:51.896646Z",
|
|
246
|
+
"location": "http://scim.example/v2/Users/38b044dd95624c4186f5614fca30305d",
|
|
247
|
+
"version": "W/\"637b1ce03c010cd55fe45b6f7be2247b5159b135\""
|
|
248
|
+
},
|
|
249
|
+
"userName": "bjensen@example.com"
|
|
250
|
+
}
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scim2-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "SCIM application development CLI"
|
|
9
9
|
authors = [{name="Yaal Coop", email="contact@yaal.coop"}]
|
|
10
10
|
license = {file = "LICENSE.md"}
|
|
@@ -27,7 +27,7 @@ classifiers = [
|
|
|
27
27
|
requires-python = ">= 3.10"
|
|
28
28
|
dependencies = [
|
|
29
29
|
"click>=8.1.7",
|
|
30
|
-
"scim2-client>=0.4.
|
|
30
|
+
"scim2-client>=0.4.3",
|
|
31
31
|
"scim2-tester[httpx]>=0.1.10",
|
|
32
32
|
"sphinx-click-rst-to-ansi-formatter>=0.1.0",
|
|
33
33
|
"pydanclick>=0.3.0",
|
|
@@ -6,8 +6,10 @@ from typing import TypeGuard
|
|
|
6
6
|
import click
|
|
7
7
|
from httpx import Client
|
|
8
8
|
from pydantic import BaseModel
|
|
9
|
+
from scim2_client import SCIMClientError
|
|
9
10
|
from scim2_client.engines.httpx import SyncSCIMClient
|
|
10
11
|
from scim2_models import Group
|
|
12
|
+
from scim2_models import ListResponse
|
|
11
13
|
from scim2_models import Resource
|
|
12
14
|
from scim2_models import ResourceType
|
|
13
15
|
from scim2_models import Schema
|
|
@@ -23,6 +25,7 @@ from scim2_cli.search import search_cli
|
|
|
23
25
|
from scim2_cli.test import test_cli
|
|
24
26
|
from scim2_cli.utils import DOC_URL
|
|
25
27
|
from scim2_cli.utils import HeaderType
|
|
28
|
+
from scim2_cli.utils import exception_to_click_error
|
|
26
29
|
from scim2_cli.utils import split_headers
|
|
27
30
|
|
|
28
31
|
|
|
@@ -51,7 +54,10 @@ def load_config_files(
|
|
|
51
54
|
]:
|
|
52
55
|
if schemas_fd:
|
|
53
56
|
schemas_payload = json.load(schemas_fd)
|
|
54
|
-
|
|
57
|
+
if isinstance(schemas_payload, dict):
|
|
58
|
+
schemas_obj = ListResponse[Schema].model_validate(schemas_payload).resources
|
|
59
|
+
else:
|
|
60
|
+
schemas_obj = [Schema.model_validate(schema) for schema in schemas_payload]
|
|
55
61
|
resource_models = [Resource.from_schema(schema) for schema in schemas_obj]
|
|
56
62
|
|
|
57
63
|
else:
|
|
@@ -59,9 +65,16 @@ def load_config_files(
|
|
|
59
65
|
|
|
60
66
|
if resource_types_fd:
|
|
61
67
|
resource_types_payload = json.load(resource_types_fd)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
if isinstance(schemas_payload, dict):
|
|
69
|
+
resource_types = (
|
|
70
|
+
ListResponse[ResourceType]
|
|
71
|
+
.model_validate(resource_types_payload)
|
|
72
|
+
.resources
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
resource_types = [
|
|
76
|
+
ResourceType.model_validate(item) for item in resource_types_payload
|
|
77
|
+
]
|
|
65
78
|
else:
|
|
66
79
|
resource_types = None
|
|
67
80
|
|
|
@@ -128,11 +141,14 @@ def cli(
|
|
|
128
141
|
resource_types=resource_types_obj,
|
|
129
142
|
service_provider_config=spc_obj,
|
|
130
143
|
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
144
|
+
try:
|
|
145
|
+
scim_client.discover(
|
|
146
|
+
schemas=not bool(schemas),
|
|
147
|
+
resource_types=not bool(resource_types),
|
|
148
|
+
service_provider_config=not bool(service_provider_config),
|
|
149
|
+
)
|
|
150
|
+
except SCIMClientError as exc:
|
|
151
|
+
raise exception_to_click_error(exc) from exc
|
|
136
152
|
|
|
137
153
|
ctx.obj["client"] = scim_client
|
|
138
154
|
ctx.obj["resource_models"] = {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
1
|
import click
|
|
4
2
|
from click import ClickException
|
|
5
3
|
from pydanclick import from_pydantic
|
|
@@ -7,10 +5,11 @@ from scim2_client import SCIMClientError
|
|
|
7
5
|
from scim2_models import Context
|
|
8
6
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
9
7
|
|
|
10
|
-
from .utils import DOC_URL
|
|
11
|
-
from .utils import ModelCommand
|
|
12
|
-
from .utils import
|
|
13
|
-
from .utils import
|
|
8
|
+
from scim2_cli.utils import DOC_URL
|
|
9
|
+
from scim2_cli.utils import ModelCommand
|
|
10
|
+
from scim2_cli.utils import exception_to_click_error
|
|
11
|
+
from scim2_cli.utils import formatted_payload
|
|
12
|
+
from scim2_cli.utils import unacceptable_fields
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
def create_payload(client, payload, indent):
|
|
@@ -18,13 +17,7 @@ def create_payload(client, payload, indent):
|
|
|
18
17
|
response = client.create(payload, raise_scim_errors=False)
|
|
19
18
|
|
|
20
19
|
except SCIMClientError as scim_exc:
|
|
21
|
-
|
|
22
|
-
if sys.version_info >= (3, 11) and hasattr(
|
|
23
|
-
scim_exc, "__notes__"
|
|
24
|
-
): # pragma: no cover
|
|
25
|
-
for note in scim_exc.__notes__:
|
|
26
|
-
message = f"{message}\n{note}"
|
|
27
|
-
raise ClickException(message) from scim_exc
|
|
20
|
+
raise exception_to_click_error(scim_exc) from scim_exc
|
|
28
21
|
|
|
29
22
|
payload = formatted_payload(response.model_dump(), indent)
|
|
30
23
|
click.echo(payload)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
1
|
import click
|
|
4
2
|
from click import ClickException
|
|
5
3
|
from scim2_client import SCIMClientError
|
|
@@ -7,6 +5,8 @@ from scim2_models import Message
|
|
|
7
5
|
from scim2_models import Resource
|
|
8
6
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
9
7
|
|
|
8
|
+
from scim2_cli.utils import exception_to_click_error
|
|
9
|
+
|
|
10
10
|
from .utils import DOC_URL
|
|
11
11
|
from .utils import formatted_payload
|
|
12
12
|
|
|
@@ -40,13 +40,7 @@ def delete_cli(ctx, resource_type, id, indent):
|
|
|
40
40
|
response = ctx.obj["client"].delete(resource_model, id, raise_scim_errors=False)
|
|
41
41
|
|
|
42
42
|
except SCIMClientError as scim_exc:
|
|
43
|
-
|
|
44
|
-
if sys.version_info >= (3, 11) and hasattr(
|
|
45
|
-
scim_exc, "__notes__"
|
|
46
|
-
): # pragma: no cover
|
|
47
|
-
for note in scim_exc.__notes__:
|
|
48
|
-
message = f"{message}\n{note}"
|
|
49
|
-
raise ClickException(message) from scim_exc
|
|
43
|
+
raise exception_to_click_error(scim_exc) from scim_exc
|
|
50
44
|
|
|
51
45
|
if response:
|
|
52
46
|
payload = (
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
1
|
import click
|
|
4
2
|
from click import ClickException
|
|
5
3
|
from scim2_client import SCIMClientError
|
|
@@ -9,6 +7,8 @@ from scim2_models import SearchRequest
|
|
|
9
7
|
from scim2_models import ServiceProviderConfig
|
|
10
8
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
11
9
|
|
|
10
|
+
from scim2_cli.utils import exception_to_click_error
|
|
11
|
+
|
|
12
12
|
from .utils import DOC_URL
|
|
13
13
|
from .utils import formatted_payload
|
|
14
14
|
|
|
@@ -118,13 +118,7 @@ def query_cli(
|
|
|
118
118
|
)
|
|
119
119
|
|
|
120
120
|
except SCIMClientError as scim_exc:
|
|
121
|
-
|
|
122
|
-
if sys.version_info >= (3, 11) and hasattr(
|
|
123
|
-
scim_exc, "__notes__"
|
|
124
|
-
): # pragma: no cover
|
|
125
|
-
for note in scim_exc.__notes__:
|
|
126
|
-
message = f"{message}\n{note}"
|
|
127
|
-
raise ClickException(message) from scim_exc
|
|
121
|
+
raise exception_to_click_error(scim_exc) from scim_exc
|
|
128
122
|
|
|
129
123
|
payload = formatted_payload(response.model_dump(), indent)
|
|
130
124
|
click.echo(payload)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
1
|
import click
|
|
4
2
|
from click import ClickException
|
|
5
3
|
from pydanclick import from_pydantic
|
|
@@ -7,6 +5,8 @@ from scim2_client import SCIMClientError
|
|
|
7
5
|
from scim2_models import Context
|
|
8
6
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
9
7
|
|
|
8
|
+
from scim2_cli.utils import exception_to_click_error
|
|
9
|
+
|
|
10
10
|
from .utils import DOC_URL
|
|
11
11
|
from .utils import ModelCommand
|
|
12
12
|
from .utils import formatted_payload
|
|
@@ -18,13 +18,7 @@ def replace_payload(client, payload, indent):
|
|
|
18
18
|
response = client.replace(payload, raise_scim_errors=False)
|
|
19
19
|
|
|
20
20
|
except SCIMClientError as scim_exc:
|
|
21
|
-
|
|
22
|
-
if sys.version_info >= (3, 11) and hasattr(
|
|
23
|
-
scim_exc, "__notes__"
|
|
24
|
-
): # pragma: no cover
|
|
25
|
-
for note in scim_exc.__notes__:
|
|
26
|
-
message = f"{message}\n{note}"
|
|
27
|
-
raise ClickException(message) from scim_exc
|
|
21
|
+
raise exception_to_click_error(scim_exc) from scim_exc
|
|
28
22
|
|
|
29
23
|
payload = formatted_payload(response.model_dump(), indent)
|
|
30
24
|
click.echo(payload)
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
1
|
import click
|
|
4
|
-
from click import ClickException
|
|
5
2
|
from scim2_client import SCIMClientError
|
|
6
3
|
from scim2_models import SearchRequest
|
|
7
4
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
8
5
|
|
|
6
|
+
from scim2_cli.utils import exception_to_click_error
|
|
7
|
+
|
|
9
8
|
from .utils import DOC_URL
|
|
10
9
|
from .utils import formatted_payload
|
|
11
10
|
|
|
@@ -93,13 +92,7 @@ def search_cli(
|
|
|
93
92
|
)
|
|
94
93
|
|
|
95
94
|
except SCIMClientError as scim_exc:
|
|
96
|
-
|
|
97
|
-
if sys.version_info >= (3, 11) and hasattr(
|
|
98
|
-
scim_exc, "__notes__"
|
|
99
|
-
): # pragma: no cover
|
|
100
|
-
for note in scim_exc.__notes__:
|
|
101
|
-
message = f"{message}\n{note}"
|
|
102
|
-
raise ClickException(message) from scim_exc
|
|
95
|
+
raise exception_to_click_error(scim_exc) from scim_exc
|
|
103
96
|
|
|
104
97
|
payload = formatted_payload(response.model_dump(), indent)
|
|
105
98
|
click.echo(payload)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
1
3
|
import click
|
|
2
4
|
from scim2_tester import Status
|
|
3
5
|
from scim2_tester import check_server
|
|
@@ -10,7 +12,19 @@ from .utils import Color
|
|
|
10
12
|
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="test")
|
|
11
13
|
@click.pass_context
|
|
12
14
|
@click.option("-v", "--verbose", is_flag=True, help="Enables verbose mode")
|
|
13
|
-
|
|
15
|
+
@click.option(
|
|
16
|
+
"--check-status-code/--dont-check-status-code",
|
|
17
|
+
is_flag=True,
|
|
18
|
+
default=True,
|
|
19
|
+
help="Fail on unexpected return codes.",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--check-content-type/--dont-check-content-type",
|
|
23
|
+
is_flag=True,
|
|
24
|
+
default=True,
|
|
25
|
+
help="Fail on unexpected content types.",
|
|
26
|
+
)
|
|
27
|
+
def test_cli(ctx, verbose, check_status_code, check_content_type):
|
|
14
28
|
"""Perform a server SCIM compliance check using :doc:`scim2-tester <scim2_tester:index>`.
|
|
15
29
|
|
|
16
30
|
.. code-block:: bash
|
|
@@ -18,16 +32,22 @@ def test_cli(ctx, verbose):
|
|
|
18
32
|
test
|
|
19
33
|
"""
|
|
20
34
|
client = ctx.obj["client"]
|
|
35
|
+
client.check_status_code = check_status_code
|
|
36
|
+
client.check_content_type = check_content_type
|
|
21
37
|
results = check_server(client)
|
|
22
38
|
click.echo(f"Performing a SCIM compliance check on {client.client.base_url} ...")
|
|
39
|
+
success = True
|
|
23
40
|
for result in results:
|
|
24
41
|
if result.status == Status.SUCCESS:
|
|
25
42
|
status = click.style(result.status.name, fg=Color.green)
|
|
26
43
|
else:
|
|
27
44
|
status = click.style(result.status.name, fg=Color.red)
|
|
45
|
+
success = False
|
|
28
46
|
click.echo(f"{status} {result.title}")
|
|
29
47
|
|
|
30
48
|
if result.reason:
|
|
31
49
|
click.echo(f" {result.reason}")
|
|
32
50
|
if verbose and result.data:
|
|
33
51
|
click.echo(f" {result.data}")
|
|
52
|
+
|
|
53
|
+
sys.exit(0 if success else 1)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import sys
|
|
2
3
|
from enum import Enum
|
|
3
4
|
|
|
4
5
|
import click
|
|
@@ -104,3 +105,12 @@ def unacceptable_fields(context, model):
|
|
|
104
105
|
for field_name in model.model_fields
|
|
105
106
|
if not is_field_acceptable(context, model, field_name)
|
|
106
107
|
]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def exception_to_click_error(exception):
|
|
111
|
+
message = str(exception)
|
|
112
|
+
if sys.version_info >= (3, 11) and hasattr(
|
|
113
|
+
exception, "__notes__"
|
|
114
|
+
): # pragma: no cover
|
|
115
|
+
message += "\n" + "\n".join(exception.__notes__)
|
|
116
|
+
return click.ClickException(message)
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
|
+
import pytest
|
|
4
5
|
from scim2_models import AuthenticationScheme
|
|
5
6
|
from scim2_models import Bulk
|
|
6
7
|
from scim2_models import ChangePassword
|
|
7
8
|
from scim2_models import ETag
|
|
8
9
|
from scim2_models import Filter
|
|
10
|
+
from scim2_models import ListResponse
|
|
9
11
|
from scim2_models import Patch
|
|
10
12
|
from scim2_models import ResourceType
|
|
13
|
+
from scim2_models import Schema
|
|
11
14
|
from scim2_models import ServiceProviderConfig
|
|
12
15
|
from scim2_models import Sort
|
|
13
16
|
from scim2_models import User
|
|
@@ -27,6 +30,13 @@ def test_help(runner):
|
|
|
27
30
|
assert "SCIM application development CLI.\n" in result.output
|
|
28
31
|
|
|
29
32
|
|
|
33
|
+
def test_bad_server(runner):
|
|
34
|
+
"""Test that the discovery step display a readable error when a bad server has been passed."""
|
|
35
|
+
result = runner.invoke(cli, ["--url", "http://scim.invalid", "query"])
|
|
36
|
+
assert result.exit_code == 1
|
|
37
|
+
assert "Error: Network error happened during request\n" in result.output
|
|
38
|
+
|
|
39
|
+
|
|
30
40
|
def test_stdin_bad_json(runner, httpserver):
|
|
31
41
|
"""Test that invalid JSON stdin raise an error."""
|
|
32
42
|
result = runner.invoke(
|
|
@@ -105,9 +115,9 @@ def test_env_vars(runner, httpserver, simple_user_payload):
|
|
|
105
115
|
del os.environ["SCIM_CLI_HEADERS"]
|
|
106
116
|
|
|
107
117
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
@pytest.fixture
|
|
119
|
+
def service_provider_configuration():
|
|
120
|
+
return ServiceProviderConfig(
|
|
111
121
|
documentation_uri="https://scim.test",
|
|
112
122
|
patch=Patch(supported=False),
|
|
113
123
|
bulk=Bulk(supported=False, max_operations=0, max_payload_size=0),
|
|
@@ -125,29 +135,48 @@ def test_custom_configuration(runner, httpserver, simple_user_payload, tmp_path)
|
|
|
125
135
|
primary=True,
|
|
126
136
|
),
|
|
127
137
|
],
|
|
128
|
-
)
|
|
138
|
+
)
|
|
129
139
|
|
|
130
|
-
spc_path = tmp_path / "service_provider_configuration.json"
|
|
131
|
-
with open(spc_path, "w") as fd:
|
|
132
|
-
json.dump(spc, fd)
|
|
133
140
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
json.dump(schemas, fd)
|
|
141
|
+
@pytest.fixture
|
|
142
|
+
def schemas():
|
|
143
|
+
return [User.to_schema()]
|
|
138
144
|
|
|
139
|
-
|
|
145
|
+
|
|
146
|
+
@pytest.fixture
|
|
147
|
+
def resource_types():
|
|
148
|
+
return [
|
|
140
149
|
ResourceType(
|
|
141
150
|
id="User",
|
|
142
151
|
name="User",
|
|
143
152
|
endpoint="/somewhere-different",
|
|
144
153
|
description="User accounts",
|
|
145
154
|
schema_="urn:ietf:params:scim:schemas:core:2.0:User",
|
|
146
|
-
)
|
|
155
|
+
)
|
|
147
156
|
]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_custom_configuration_by_parameter(
|
|
160
|
+
runner,
|
|
161
|
+
httpserver,
|
|
162
|
+
simple_user_payload,
|
|
163
|
+
tmp_path,
|
|
164
|
+
service_provider_configuration,
|
|
165
|
+
schemas,
|
|
166
|
+
resource_types,
|
|
167
|
+
):
|
|
168
|
+
"""Test passing custom .JSON configuration files to the command."""
|
|
169
|
+
spc_path = tmp_path / "service_provider_configuration.json"
|
|
170
|
+
with open(spc_path, "w") as fd:
|
|
171
|
+
json.dump(service_provider_configuration.model_dump(), fd)
|
|
172
|
+
|
|
173
|
+
schemas_path = tmp_path / "schemas.json"
|
|
174
|
+
with open(schemas_path, "w") as fd:
|
|
175
|
+
json.dump([schema.model_dump() for schema in schemas], fd)
|
|
176
|
+
|
|
148
177
|
resource_types_path = tmp_path / "resource_types.json"
|
|
149
178
|
with open(resource_types_path, "w") as fd:
|
|
150
|
-
json.dump(resource_types, fd)
|
|
179
|
+
json.dump([resource_type.model_dump() for resource_type in resource_types], fd)
|
|
151
180
|
|
|
152
181
|
httpserver.expect_request(
|
|
153
182
|
"/somewhere-different/foobar",
|
|
@@ -176,3 +205,123 @@ def test_custom_configuration(runner, httpserver, simple_user_payload, tmp_path)
|
|
|
176
205
|
catch_exceptions=False,
|
|
177
206
|
)
|
|
178
207
|
assert result.exit_code == 0
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_custom_configuration_in_list_response(
|
|
211
|
+
runner,
|
|
212
|
+
httpserver,
|
|
213
|
+
simple_user_payload,
|
|
214
|
+
tmp_path,
|
|
215
|
+
service_provider_configuration,
|
|
216
|
+
schemas,
|
|
217
|
+
resource_types,
|
|
218
|
+
):
|
|
219
|
+
"""Test that ListResponse format is supported."""
|
|
220
|
+
spc_path = tmp_path / "service_provider_configuration.json"
|
|
221
|
+
with open(spc_path, "w") as fd:
|
|
222
|
+
json.dump(service_provider_configuration.model_dump(), fd)
|
|
223
|
+
|
|
224
|
+
schemas_path = tmp_path / "schemas.json"
|
|
225
|
+
with open(schemas_path, "w") as fd:
|
|
226
|
+
json.dump(
|
|
227
|
+
ListResponse[Schema](
|
|
228
|
+
total_results=len(schemas),
|
|
229
|
+
start_index=1,
|
|
230
|
+
items_per_page=len(schemas),
|
|
231
|
+
resources=schemas,
|
|
232
|
+
).model_dump(),
|
|
233
|
+
fd,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
resource_types_path = tmp_path / "resource_types.json"
|
|
237
|
+
with open(resource_types_path, "w") as fd:
|
|
238
|
+
json.dump(
|
|
239
|
+
ListResponse[ResourceType](
|
|
240
|
+
total_results=len(resource_types),
|
|
241
|
+
start_index=1,
|
|
242
|
+
items_per_page=len(resource_types),
|
|
243
|
+
resources=resource_types,
|
|
244
|
+
).model_dump(),
|
|
245
|
+
fd,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
httpserver.expect_request(
|
|
249
|
+
"/somewhere-different/foobar",
|
|
250
|
+
method="GET",
|
|
251
|
+
).respond_with_json(
|
|
252
|
+
simple_user_payload("foobar"),
|
|
253
|
+
status=200,
|
|
254
|
+
content_type="application/scim+json",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
result = runner.invoke(
|
|
258
|
+
cli,
|
|
259
|
+
[
|
|
260
|
+
"--url",
|
|
261
|
+
httpserver.url_for("/"),
|
|
262
|
+
"--service-provider-config",
|
|
263
|
+
spc_path,
|
|
264
|
+
"--schemas",
|
|
265
|
+
schemas_path,
|
|
266
|
+
"--resource-types",
|
|
267
|
+
resource_types_path,
|
|
268
|
+
"query",
|
|
269
|
+
"user",
|
|
270
|
+
"foobar",
|
|
271
|
+
],
|
|
272
|
+
catch_exceptions=False,
|
|
273
|
+
)
|
|
274
|
+
assert result.exit_code == 0
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_custom_configuration_by_env(
|
|
278
|
+
runner,
|
|
279
|
+
httpserver,
|
|
280
|
+
simple_user_payload,
|
|
281
|
+
tmp_path,
|
|
282
|
+
service_provider_configuration,
|
|
283
|
+
schemas,
|
|
284
|
+
resource_types,
|
|
285
|
+
):
|
|
286
|
+
"""Test passing custom .JSON configuration files to the command."""
|
|
287
|
+
spc_path = tmp_path / "service_provider_configuration.json"
|
|
288
|
+
with open(spc_path, "w") as fd:
|
|
289
|
+
json.dump(service_provider_configuration.model_dump(), fd)
|
|
290
|
+
|
|
291
|
+
schemas_path = tmp_path / "schemas.json"
|
|
292
|
+
with open(schemas_path, "w") as fd:
|
|
293
|
+
json.dump([schema.model_dump() for schema in schemas], fd)
|
|
294
|
+
|
|
295
|
+
resource_types_path = tmp_path / "resource_types.json"
|
|
296
|
+
with open(resource_types_path, "w") as fd:
|
|
297
|
+
json.dump([resource_type.model_dump() for resource_type in resource_types], fd)
|
|
298
|
+
|
|
299
|
+
httpserver.expect_request(
|
|
300
|
+
"/somewhere-different/foobar",
|
|
301
|
+
method="GET",
|
|
302
|
+
).respond_with_json(
|
|
303
|
+
simple_user_payload("foobar"),
|
|
304
|
+
status=200,
|
|
305
|
+
content_type="application/scim+json",
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
os.environ["SCIM_CLI_SERVICE_PROVIDER_CONFIG"] = str(spc_path)
|
|
309
|
+
os.environ["SCIM_CLI_SCHEMAS"] = str(schemas_path)
|
|
310
|
+
os.environ["SCIM_CLI_RESOURCE_TYPES"] = str(resource_types_path)
|
|
311
|
+
try:
|
|
312
|
+
result = runner.invoke(
|
|
313
|
+
cli,
|
|
314
|
+
[
|
|
315
|
+
"--url",
|
|
316
|
+
httpserver.url_for("/"),
|
|
317
|
+
"query",
|
|
318
|
+
"user",
|
|
319
|
+
"foobar",
|
|
320
|
+
],
|
|
321
|
+
catch_exceptions=False,
|
|
322
|
+
)
|
|
323
|
+
assert result.exit_code == 0
|
|
324
|
+
finally:
|
|
325
|
+
del os.environ["SCIM_CLI_SERVICE_PROVIDER_CONFIG"]
|
|
326
|
+
del os.environ["SCIM_CLI_SCHEMAS"]
|
|
327
|
+
del os.environ["SCIM_CLI_RESOURCE_TYPES"]
|
|
@@ -32,7 +32,6 @@ def test_nominal(runner, httpserver):
|
|
|
32
32
|
["--url", httpserver.url_for("/"), "test"],
|
|
33
33
|
catch_exceptions=False,
|
|
34
34
|
)
|
|
35
|
-
assert result.exit_code == 0, result.stdout
|
|
36
35
|
|
|
37
36
|
expected_output = f"""Performing a SCIM compliance check on http://localhost:{httpserver.port}/ ...
|
|
38
37
|
SUCCESS test1
|
|
@@ -68,7 +67,6 @@ def test_verbose(runner, httpserver):
|
|
|
68
67
|
["--url", httpserver.url_for("/"), "test", "--verbose"],
|
|
69
68
|
catch_exceptions=False,
|
|
70
69
|
)
|
|
71
|
-
assert result.exit_code == 0, result.stdout
|
|
72
70
|
|
|
73
71
|
expected_output = f"""Performing a SCIM compliance check on http://localhost:{httpserver.port}/ ...
|
|
74
72
|
SUCCESS test1
|
|
@@ -79,3 +77,12 @@ ERROR test2
|
|
|
79
77
|
data2
|
|
80
78
|
"""
|
|
81
79
|
assert result.output == expected_output
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_failure(runner, httpserver):
|
|
83
|
+
"""Test SCIM compliance test failure."""
|
|
84
|
+
result = runner.invoke(
|
|
85
|
+
cli,
|
|
86
|
+
["--url", "http://scim.invalid", "test"],
|
|
87
|
+
)
|
|
88
|
+
assert result.exit_code == 1, result.stdout
|
|
@@ -30,17 +30,17 @@ wheels = [
|
|
|
30
30
|
|
|
31
31
|
[[package]]
|
|
32
32
|
name = "anyio"
|
|
33
|
-
version = "4.
|
|
33
|
+
version = "4.7.0"
|
|
34
34
|
source = { registry = "https://pypi.org/simple" }
|
|
35
35
|
dependencies = [
|
|
36
36
|
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
|
37
37
|
{ name = "idna" },
|
|
38
38
|
{ name = "sniffio" },
|
|
39
|
-
{ name = "typing-extensions", marker = "python_full_version < '3.
|
|
39
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
40
40
|
]
|
|
41
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
41
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f6/40/318e58f669b1a9e00f5c4453910682e2d9dd594334539c7b7817dabb765f/anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", size = 177076 }
|
|
42
42
|
wheels = [
|
|
43
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
43
|
+
{ url = "https://files.pythonhosted.org/packages/a0/7a/4daaf3b6c08ad7ceffea4634ec206faeff697526421c20f07628c7372156/anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352", size = 93052 },
|
|
44
44
|
]
|
|
45
45
|
|
|
46
46
|
[[package]]
|
|
@@ -931,7 +931,7 @@ wheels = [
|
|
|
931
931
|
|
|
932
932
|
[[package]]
|
|
933
933
|
name = "scim2-cli"
|
|
934
|
-
version = "0.2.
|
|
934
|
+
version = "0.2.2"
|
|
935
935
|
source = { editable = "." }
|
|
936
936
|
dependencies = [
|
|
937
937
|
{ name = "click" },
|
|
@@ -967,7 +967,7 @@ requires-dist = [
|
|
|
967
967
|
{ name = "click", specifier = ">=8.1.7" },
|
|
968
968
|
{ name = "pydanclick", specifier = ">=0.3.0" },
|
|
969
969
|
{ name = "pygments", specifier = ">=2.18.0" },
|
|
970
|
-
{ name = "scim2-client", specifier = ">=0.4.
|
|
970
|
+
{ name = "scim2-client", specifier = ">=0.4.3" },
|
|
971
971
|
{ name = "scim2-tester", extras = ["httpx"], specifier = ">=0.1.10" },
|
|
972
972
|
{ name = "sphinx-click-rst-to-ansi-formatter", specifier = ">=0.1.0" },
|
|
973
973
|
]
|
|
@@ -992,14 +992,14 @@ doc = [
|
|
|
992
992
|
|
|
993
993
|
[[package]]
|
|
994
994
|
name = "scim2-client"
|
|
995
|
-
version = "0.4.
|
|
995
|
+
version = "0.4.3"
|
|
996
996
|
source = { registry = "https://pypi.org/simple" }
|
|
997
997
|
dependencies = [
|
|
998
998
|
{ name = "scim2-models" },
|
|
999
999
|
]
|
|
1000
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1000
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c9/44/1b228a6a680ca96a1274f2ca1dd22aa3e61e656c5e829c348b27f793dc9d/scim2_client-0.4.3.tar.gz", hash = "sha256:69f55e1c296cb018cb4d71954485b6dab8153bb59935647b1e063a659c141ede", size = 85428 }
|
|
1001
1001
|
wheels = [
|
|
1002
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1002
|
+
{ url = "https://files.pythonhosted.org/packages/fa/75/d56f022664d6db564b0e85265ab5e97b49133f796ff1d502bd652e7c075f/scim2_client-0.4.3-py3-none-any.whl", hash = "sha256:051578f4e56e57149b1b6ea06c30cd4d831b8f2278c301e92421b0ebf524b813", size = 22373 },
|
|
1003
1003
|
]
|
|
1004
1004
|
|
|
1005
1005
|
[package.optional-dependencies]
|
|
@@ -1260,16 +1260,16 @@ wheels = [
|
|
|
1260
1260
|
|
|
1261
1261
|
[[package]]
|
|
1262
1262
|
name = "tox-uv"
|
|
1263
|
-
version = "1.16.
|
|
1263
|
+
version = "1.16.1"
|
|
1264
1264
|
source = { registry = "https://pypi.org/simple" }
|
|
1265
1265
|
dependencies = [
|
|
1266
1266
|
{ name = "packaging" },
|
|
1267
1267
|
{ name = "tox" },
|
|
1268
1268
|
{ name = "uv" },
|
|
1269
1269
|
]
|
|
1270
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1270
|
+
sdist = { url = "https://files.pythonhosted.org/packages/81/5a/5335fe0348d626a01e7a600e85cbea04e0f5f221e9c2f3a2b3664d774f69/tox_uv-1.16.1.tar.gz", hash = "sha256:63b0a872f1f97263f89fe41e7195b401a18a67a9a14e3803372091a364b43cd6", size = 16940 }
|
|
1271
1271
|
wheels = [
|
|
1272
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1272
|
+
{ url = "https://files.pythonhosted.org/packages/57/1d/c44b1e246f95fadf9fc83b34f70fb297466bc580b71d756e85097ef1dd6a/tox_uv-1.16.1-py3-none-any.whl", hash = "sha256:ab5dae5df02ca3b6c00f95c86810afe6c0e2b8ff8e6d76053ef677dcce38975b", size = 13681 },
|
|
1273
1273
|
]
|
|
1274
1274
|
|
|
1275
1275
|
[[package]]
|
|
@@ -1292,27 +1292,27 @@ wheels = [
|
|
|
1292
1292
|
|
|
1293
1293
|
[[package]]
|
|
1294
1294
|
name = "uv"
|
|
1295
|
-
version = "0.5.
|
|
1296
|
-
source = { registry = "https://pypi.org/simple" }
|
|
1297
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1298
|
-
wheels = [
|
|
1299
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1300
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1301
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1302
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1303
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1304
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1305
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1306
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1307
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1308
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1309
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1310
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1311
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1312
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1313
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1314
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1315
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1295
|
+
version = "0.5.6"
|
|
1296
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1297
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d7/23/2c12f9941de6fd984259e778dc8660172fc6498a3c5a77bd1df81a6daf93/uv-0.5.6.tar.gz", hash = "sha256:9297f084ca2004044a9353525777c9cb1151a0d768de87cc3920574597674f88", size = 2343719 }
|
|
1298
|
+
wheels = [
|
|
1299
|
+
{ url = "https://files.pythonhosted.org/packages/d9/e4/8a8e06c2e720ab2677d9457031e58c613005158bef61aa34d72c297c3476/uv-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:485decff72ba2ba6c8b82de0f8ecc877bd4080dc4e27604f3ebd9da98e00c53f", size = 14129515 },
|
|
1300
|
+
{ url = "https://files.pythonhosted.org/packages/0f/fc/a6cd710c5c90eb03d7b9f3de1b88a8064685a266e65f3cb21653282e59b4/uv-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6e13c1bb1f00b596a50711afffc407570edf9825fea4cfbc1d80cfb59816e501", size = 14118111 },
|
|
1301
|
+
{ url = "https://files.pythonhosted.org/packages/87/4f/5263768d16f2366e70b8b56c5066fde76f41ca66f7c088aaeb715b5b684d/uv-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4ca83b575d316030503d81d3cc85a53be45d8bab56892f845f7e545006e5df59", size = 13087256 },
|
|
1302
|
+
{ url = "https://files.pythonhosted.org/packages/1e/b3/1fae9b18fbadf8ddf4817ccf10b8f5e69b9a3312f7609ebbc6aec9ca6998/uv-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a15ac68584b346e592ca4c5167ffd4c4ba9906706b423b696e7b37d03dea753b", size = 13351635 },
|
|
1303
|
+
{ url = "https://files.pythonhosted.org/packages/3c/c5/f120169cab520992124718463bd24fbd4061380b909001fded2736376595/uv-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c7afc715f48a6ddc61257ed62c74595b5762eb67a6f23696fae6e609698a990", size = 13895585 },
|
|
1304
|
+
{ url = "https://files.pythonhosted.org/packages/5c/e1/9142086ad917d20bd3cca4421ab68a5d894ea33199cb121f86a6aeac611f/uv-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b49db3c842dcdaccdaf8ea26770c2b18632528f68e7f3211415ae25796de71a", size = 14463583 },
|
|
1305
|
+
{ url = "https://files.pythonhosted.org/packages/4f/88/4f7bb77c137f52af36ebf20d67ed629a23ab35d7f47c5162707cfa1c4cdf/uv-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9c1ebf58ef3906e05e0060cda4a801a45e93334169821a89effa6ba9607dfa07", size = 15162379 },
|
|
1306
|
+
{ url = "https://files.pythonhosted.org/packages/f4/5d/e276d8bce24823f89db9faa03f325a0f67309e3ed4eac64e193a06f8cdf1/uv-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:848b12fe47ee8dd871ae5cfb1fefc2bac7cdb90bd34ff1f08bf78dd751f6950a", size = 14962665 },
|
|
1307
|
+
{ url = "https://files.pythonhosted.org/packages/d3/48/b6cb8a56b1c0e49271d24dba7af3f6e9281617a44f7b2a900ca0f3a8bce0/uv-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e14da824a775abe4f28f03c4a672df99697fdd42c5e99df40d99a7d5c90e5c8", size = 19300290 },
|
|
1308
|
+
{ url = "https://files.pythonhosted.org/packages/59/52/d7c8baab385fcfd8da93065e42993a4d0d57a97e600ab3c7b3de83f63e57/uv-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:920dd926d235f826454e7b68cb1890ded6e67510e6195c346eef41caabc9d9b7", size = 14623299 },
|
|
1309
|
+
{ url = "https://files.pythonhosted.org/packages/f1/65/10ed431541d2bce7589715951371c2218deb3ad8fb8922d38a1283c9116b/uv-0.5.6-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:152466b8854bc30661620671cd1e5e854123e734fb4f380bc83eab5e3c4450a9", size = 13579816 },
|
|
1310
|
+
{ url = "https://files.pythonhosted.org/packages/69/b3/9a5accd6da604564c9e448c515aca749a9e1f42a756d3b6c8091c7a8021f/uv-0.5.6-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:87b0b77ebf036363b8b0ed55c0f4a4a687b20500e3f9aa45574dfe05f9761057", size = 13875018 },
|
|
1311
|
+
{ url = "https://files.pythonhosted.org/packages/9f/8d/81962b96c17c7c77bc54d5f74ecf55b4699da35988149b86dbf77f9b9795/uv-0.5.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:eae7301bd35891545a4e139b0f1bd5aa7981afba43654233297be463d6d6ba8b", size = 14197982 },
|
|
1312
|
+
{ url = "https://files.pythonhosted.org/packages/f5/99/dc2113b4fd8232aacd75aa492f61f6a15fc55191967b31c46b3184aa6a99/uv-0.5.6-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:f2e6a13da16b8a3a43d50a1be5910c4e55b04889bcacfb8c9a22dcff817fa326", size = 16042590 },
|
|
1313
|
+
{ url = "https://files.pythonhosted.org/packages/6a/08/477da5f4a6eac1da2a9a94a8a6e6e16c44d4a1d44c65cb0dfefa6165ccd4/uv-0.5.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2b1dc05b9b242989d4a6dc99916a0fd6dc98ea5146f757505da69ac20825067d", size = 14759319 },
|
|
1314
|
+
{ url = "https://files.pythonhosted.org/packages/13/db/fdfa81cbeedea64cb7a376451a93d34c996d5656650d7a4578eff6464df6/uv-0.5.6-py3-none-win32.whl", hash = "sha256:8bc30ef6996c3352a45d75ebc9aeb2e7ae5ebb7b2118b6f036ddd02d4f689daa", size = 14054507 },
|
|
1315
|
+
{ url = "https://files.pythonhosted.org/packages/83/1c/19f79f9df067694a20239c68f280efa4bfb25d4ab39442a96fae874ed839/uv-0.5.6-py3-none-win_amd64.whl", hash = "sha256:c1a3b180e90734812034cc1594256f519bff6c2715f3f1ceb9ac7bc578005f5d", size = 15915638 },
|
|
1316
1316
|
]
|
|
1317
1317
|
|
|
1318
1318
|
[[package]]
|
scim2_cli-0.2.0/doc/tutorial.rst
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
Tutorial
|
|
2
|
-
========
|
|
3
|
-
|
|
4
|
-
The :ref:`cli` command allows you to interact with a SCIM server.
|
|
5
|
-
|
|
6
|
-
Basic parameters
|
|
7
|
-
----------------
|
|
8
|
-
|
|
9
|
-
In order to connect to a SCIM server you will need to pass the :option:`scim --url` parameter.
|
|
10
|
-
You can also pass additional headers, such as authentication ones, with :option:`scim --url`.
|
|
11
|
-
|
|
12
|
-
.. code-block:: shell
|
|
13
|
-
|
|
14
|
-
$ scim2 --url https://auth.example --header "Authorization: Bearer 12345" create user --user-name "bjensen@example.com"
|
|
15
|
-
{
|
|
16
|
-
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
17
|
-
"id": "2819c223-7f76-453a-919d-413861904646",
|
|
18
|
-
"userName": "bjensen@example.com",
|
|
19
|
-
"meta": {
|
|
20
|
-
"resourceType": "User",
|
|
21
|
-
"created": "2010-01-23T04:56:22Z",
|
|
22
|
-
"lastModified": "2011-05-13T04:42:34Z",
|
|
23
|
-
"version": 'W\\/"3694e05e9dff590"',
|
|
24
|
-
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
However passing those parameters each time you use the command can be annoying.
|
|
29
|
-
To make commands shorter, you can set those parameters once for all by using the :ref:`SCIM_CLI_URL <scim-url-scim_cli_url>` and :ref:`SCIM_CLI_HEADERS <scim-header-scim_cli_headers>` environment vars.
|
|
30
|
-
|
|
31
|
-
.. code-block:: shell
|
|
32
|
-
|
|
33
|
-
$ export SCIM_CLI_URL="https://auth.example"
|
|
34
|
-
$ export SCIM_CLI_HEADERS="Authorization: Bearer 12345"
|
|
35
|
-
$ scim2 create user --user-name "bjensen@example.com"
|
|
36
|
-
{
|
|
37
|
-
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
38
|
-
"id": "2819c223-7f76-453a-919d-413861904646",
|
|
39
|
-
"userName": "bjensen@example.com",
|
|
40
|
-
"meta": {
|
|
41
|
-
"resourceType": "User",
|
|
42
|
-
"created": "2010-01-23T04:56:22Z",
|
|
43
|
-
"lastModified": "2011-05-13T04:42:34Z",
|
|
44
|
-
"version": 'W\\/"3694e05e9dff590"',
|
|
45
|
-
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
|
|
46
|
-
},
|
|
47
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|