Skip to content

Commit 88edfe1

Browse files
committed
feat: configuration files can be manually passed to the CLI
1 parent 15bde17 commit 88edfe1

File tree

2 files changed

+148
-4
lines changed

2 files changed

+148
-4
lines changed

scim2_cli/__init__.py

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
from pydantic import BaseModel
88
from scim2_client.engines.httpx import SyncSCIMClient
99
from scim2_models import Group
10+
from scim2_models import Resource
11+
from scim2_models import ResourceType
12+
from scim2_models import Schema
13+
from scim2_models import ServiceProviderConfig
1014
from scim2_models import User
1115
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
1216

@@ -41,7 +45,7 @@ def _is_pydantic_model(model: Any) -> TypeGuard[type[BaseModel]]:
4145

4246

4347
@click.group(cls=make_rst_to_ansi_formatter(DOC_URL, group=True))
44-
@click.option("--url", help="The SCIM server endpoint.", envvar="SCIM_CLI_URL")
48+
@click.option("-u", "--url", help="The SCIM server endpoint.", envvar="SCIM_CLI_URL")
4549
@click.option(
4650
"-h",
4751
"--header",
@@ -50,15 +54,70 @@ def _is_pydantic_model(model: Any) -> TypeGuard[type[BaseModel]]:
5054
help="Headers to pass in the HTTP requests. Can be passed multiple times.",
5155
envvar="SCIM_CLI_HEADERS",
5256
)
57+
@click.option(
58+
"-s",
59+
"--schemas",
60+
type=click.File(),
61+
help="Path to a JSON file containing a list of SCIM Schemas. Those schemas will be assumed to be available on the server. If unset, they will be downloaded.",
62+
envvar="SCIM_CLI_SCHEMAS",
63+
)
64+
@click.option(
65+
"-r",
66+
"--resource-types",
67+
type=click.File(),
68+
help="Path to a JSON file containing a list of SCIM ResourceType. Those resource types will be assumed to be available on the server. If unset, they will be downloaded.",
69+
envvar="SCIM_CLI_RESOURCE_TYPES",
70+
)
71+
@click.option(
72+
"-c",
73+
"--service-provider-config",
74+
type=click.File(),
75+
help="Path to a JSON file containing the ServiceProviderConfig content of the server. Will be downloaded otherwise.",
76+
envvar="SCIM_CLI_SERVICE_PROVIDER_CONFIG",
77+
)
5378
@click.pass_context
54-
def cli(ctx, url: str, header: list[str]):
79+
def cli(
80+
ctx, url: str, header: list[str], schemas, resource_types, service_provider_config
81+
):
5582
"""SCIM application development CLI."""
5683
ctx.ensure_object(dict)
5784
ctx.obj["URL"] = url
85+
5886
headers_dict = split_headers(header)
5987
client = Client(base_url=ctx.obj["URL"], headers=headers_dict)
60-
ctx.obj["client"] = SyncSCIMClient(client, resource_models=(User, Group))
61-
ctx.obj["client"].register_naive_resource_types()
88+
89+
if schemas:
90+
schemas_payload = json.load(schemas)
91+
schemas_obj = [Schema.model_validate(schema) for schema in schemas_payload]
92+
resource_models = [Resource.from_schema(schema) for schema in schemas_obj]
93+
94+
else:
95+
resource_models = [User, Group]
96+
97+
if resource_types:
98+
resource_types_payload = json.load(resource_types)
99+
resource_types_obj = [
100+
ResourceType.model_validate(item) for item in resource_types_payload
101+
]
102+
else:
103+
resource_types_obj = None
104+
105+
if service_provider_config:
106+
spc_payload = json.load(service_provider_config)
107+
spc_obj = ServiceProviderConfig.model_validate(spc_payload)
108+
else:
109+
spc_obj = None
110+
111+
scim_client = SyncSCIMClient(
112+
client,
113+
resource_models=resource_models,
114+
resource_types=resource_types_obj,
115+
service_provider_config=spc_obj,
116+
)
117+
if not resource_types:
118+
scim_client.register_naive_resource_types()
119+
120+
ctx.obj["client"] = scim_client
62121
ctx.obj["resource_models"] = {
63122
resource_model.__name__.lower(): resource_model
64123
for resource_model in ctx.obj["client"].resource_models

tests/test_cli.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
import json
12
import os
23

4+
from scim2_models import AuthenticationScheme
5+
from scim2_models import Bulk
6+
from scim2_models import ChangePassword
7+
from scim2_models import ETag
8+
from scim2_models import Filter
9+
from scim2_models import Patch
10+
from scim2_models import ResourceType
11+
from scim2_models import ServiceProviderConfig
12+
from scim2_models import Sort
13+
from scim2_models import User
14+
315
from scim2_cli import cli
416

517

@@ -84,3 +96,76 @@ def test_env_vars(runner, httpserver, simple_user_payload):
8496
finally:
8597
del os.environ["SCIM_CLI_URL"]
8698
del os.environ["SCIM_CLI_HEADERS"]
99+
100+
101+
def test_custom_configuration(runner, httpserver, simple_user_payload, tmp_path):
102+
spc = ServiceProviderConfig(
103+
documentation_uri="https://canaille.readthedocs.io",
104+
patch=Patch(supported=False),
105+
bulk=Bulk(supported=False, max_operations=0, max_payload_size=0),
106+
change_password=ChangePassword(supported=True),
107+
filter=Filter(supported=False, max_results=0),
108+
sort=Sort(supported=False),
109+
etag=ETag(supported=False),
110+
authentication_schemes=[
111+
AuthenticationScheme(
112+
name="OAuth Bearer Token",
113+
description="Authentication scheme using the OAuth Bearer Token Standard",
114+
spec_uri="http://www.rfc-editor.org/info/rfc6750",
115+
documentation_uri="https://canaille.readthedocs.io",
116+
type="oauthbearertoken",
117+
primary=True,
118+
),
119+
],
120+
).model_dump()
121+
122+
spc_path = tmp_path / "service_provider_configuration.json"
123+
with open(spc_path, "w") as fd:
124+
json.dump(spc, fd)
125+
126+
schemas = [User.to_schema().model_dump()]
127+
schemas_path = tmp_path / "schemas.json"
128+
with open(schemas_path, "w") as fd:
129+
json.dump(schemas, fd)
130+
131+
resource_types = [
132+
ResourceType(
133+
id="User",
134+
name="User",
135+
endpoint="/somewhere-different",
136+
description="User accounts",
137+
schema_="urn:ietf:params:scim:schemas:core:2.0:User",
138+
).model_dump()
139+
]
140+
resource_types_path = tmp_path / "resource_types.json"
141+
with open(resource_types_path, "w") as fd:
142+
json.dump(resource_types, fd)
143+
144+
"""Test passing custom .JSON configuration files to the command."""
145+
httpserver.expect_request(
146+
"/somewhere-different/foobar",
147+
method="GET",
148+
).respond_with_json(
149+
simple_user_payload("foobar"),
150+
status=200,
151+
content_type="application/scim+json",
152+
)
153+
154+
result = runner.invoke(
155+
cli,
156+
[
157+
"--url",
158+
httpserver.url_for("/"),
159+
"--service-provider-config",
160+
spc_path,
161+
"--schemas",
162+
schemas_path,
163+
"--resource-types",
164+
resource_types_path,
165+
"query",
166+
"user",
167+
"foobar",
168+
],
169+
catch_exceptions=False,
170+
)
171+
assert result.exit_code == 0

0 commit comments

Comments
 (0)