Skip to content

Commit aab9441

Browse files
authored
cleanup: refactor all the keycloak setup to json files (containers#390)
Signed-off-by: Calum Murray <[email protected]>
1 parent 75eeaac commit aab9441

File tree

15 files changed

+173
-16
lines changed

15 files changed

+173
-16
lines changed

build/keycloak.mk

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ keycloak-setup-realm:
111111
REALM_RESPONSE=$$(curl -sk -w "%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms" \
112112
-H "Authorization: Bearer $$TOKEN" \
113113
-H "Content-Type: application/json" \
114-
-d '{"realm":"openshift","enabled":true}'); \
114+
-d @dev/config/keycloak/realm/realm-create.json); \
115115
REALM_CODE=$$(echo "$$REALM_RESPONSE" | tail -c 4); \
116116
if [ "$$REALM_CODE" = "201" ] || [ "$$REALM_CODE" = "409" ]; then \
117117
if [ "$$REALM_CODE" = "201" ]; then echo "✅ OpenShift realm created"; \
@@ -125,7 +125,7 @@ keycloak-setup-realm:
125125
EVENT_CONFIG_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X PUT "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift" \
126126
-H "Authorization: Bearer $$TOKEN" \
127127
-H "Content-Type: application/json" \
128-
-d '{"realm":"openshift","enabled":true,"eventsEnabled":true,"eventsListeners":["jboss-logging"],"adminEventsEnabled":true,"adminEventsDetailsEnabled":true}'); \
128+
-d @dev/config/keycloak/realm/realm-events-config.json); \
129129
EVENT_CONFIG_CODE=$$(echo "$$EVENT_CONFIG_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
130130
if [ "$$EVENT_CONFIG_CODE" = "204" ]; then \
131131
echo "✅ User and admin event logging enabled"; \
@@ -137,7 +137,7 @@ keycloak-setup-realm:
137137
SCOPE_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \
138138
-H "Authorization: Bearer $$TOKEN" \
139139
-H "Content-Type: application/json" \
140-
-d '{"name":"mcp:openshift","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}'); \
140+
-d @dev/config/keycloak/client-scopes/mcp-openshift.json); \
141141
SCOPE_CODE=$$(echo "$$SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
142142
if [ "$$SCOPE_CODE" = "201" ] || [ "$$SCOPE_CODE" = "409" ]; then \
143143
if [ "$$SCOPE_CODE" = "201" ]; then echo "✅ mcp:openshift client scope created"; \
@@ -159,7 +159,7 @@ keycloak-setup-realm:
159159
MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes/$$SCOPE_ID/protocol-mappers/models" \
160160
-H "Authorization: Bearer $$TOKEN" \
161161
-H "Content-Type: application/json" \
162-
-d '{"name":"openshift-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"openshift","id.token.claim":"true","access.token.claim":"true"}}'); \
162+
-d @dev/config/keycloak/mappers/openshift-audience.json); \
163163
MAPPER_CODE=$$(echo "$$MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
164164
if [ "$$MAPPER_CODE" = "201" ] || [ "$$MAPPER_CODE" = "409" ]; then \
165165
if [ "$$MAPPER_CODE" = "201" ]; then echo "✅ Audience mapper added"; \
@@ -173,7 +173,7 @@ keycloak-setup-realm:
173173
GROUPS_SCOPE_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \
174174
-H "Authorization: Bearer $$TOKEN" \
175175
-H "Content-Type: application/json" \
176-
-d '{"name":"groups","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}'); \
176+
-d @dev/config/keycloak/client-scopes/groups.json); \
177177
GROUPS_SCOPE_CODE=$$(echo "$$GROUPS_SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
178178
if [ "$$GROUPS_SCOPE_CODE" = "201" ] || [ "$$GROUPS_SCOPE_CODE" = "409" ]; then \
179179
if [ "$$GROUPS_SCOPE_CODE" = "201" ]; then echo "✅ groups client scope created"; \
@@ -195,7 +195,7 @@ keycloak-setup-realm:
195195
GROUPS_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes/$$GROUPS_SCOPE_ID/protocol-mappers/models" \
196196
-H "Authorization: Bearer $$TOKEN" \
197197
-H "Content-Type: application/json" \
198-
-d '{"name":"groups","protocol":"openid-connect","protocolMapper":"oidc-group-membership-mapper","config":{"claim.name":"groups","full.path":"false","id.token.claim":"true","access.token.claim":"true","userinfo.token.claim":"true"}}'); \
198+
-d @dev/config/keycloak/mappers/groups-membership.json); \
199199
GROUPS_MAPPER_CODE=$$(echo "$$GROUPS_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
200200
if [ "$$GROUPS_MAPPER_CODE" = "201" ] || [ "$$GROUPS_MAPPER_CODE" = "409" ]; then \
201201
if [ "$$GROUPS_MAPPER_CODE" = "201" ]; then echo "✅ Group membership mapper added"; \
@@ -209,7 +209,7 @@ keycloak-setup-realm:
209209
MCP_SERVER_SCOPE_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \
210210
-H "Authorization: Bearer $$TOKEN" \
211211
-H "Content-Type: application/json" \
212-
-d '{"name":"mcp-server","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}'); \
212+
-d @dev/config/keycloak/client-scopes/mcp-server.json); \
213213
MCP_SERVER_SCOPE_CODE=$$(echo "$$MCP_SERVER_SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
214214
if [ "$$MCP_SERVER_SCOPE_CODE" = "201" ] || [ "$$MCP_SERVER_SCOPE_CODE" = "409" ]; then \
215215
if [ "$$MCP_SERVER_SCOPE_CODE" = "201" ]; then echo "✅ mcp-server client scope created"; \
@@ -231,7 +231,7 @@ keycloak-setup-realm:
231231
MCP_SERVER_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes/$$MCP_SERVER_SCOPE_ID/protocol-mappers/models" \
232232
-H "Authorization: Bearer $$TOKEN" \
233233
-H "Content-Type: application/json" \
234-
-d '{"name":"mcp-server-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"mcp-server","id.token.claim":"true","access.token.claim":"true"}}'); \
234+
-d @dev/config/keycloak/mappers/mcp-server-audience.json); \
235235
MCP_SERVER_MAPPER_CODE=$$(echo "$$MCP_SERVER_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
236236
if [ "$$MCP_SERVER_MAPPER_CODE" = "201" ] || [ "$$MCP_SERVER_MAPPER_CODE" = "409" ]; then \
237237
if [ "$$MCP_SERVER_MAPPER_CODE" = "201" ]; then echo "✅ mcp-server audience mapper added"; \
@@ -245,7 +245,7 @@ keycloak-setup-realm:
245245
OPENSHIFT_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \
246246
-H "Authorization: Bearer $$TOKEN" \
247247
-H "Content-Type: application/json" \
248-
-d '{"clientId":"openshift","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email","groups"],"optionalClientScopes":[]}'); \
248+
-d @dev/config/keycloak/clients/openshift.json); \
249249
OPENSHIFT_CLIENT_CODE=$$(echo "$$OPENSHIFT_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
250250
if [ "$$OPENSHIFT_CLIENT_CODE" = "201" ] || [ "$$OPENSHIFT_CLIENT_CODE" = "409" ]; then \
251251
if [ "$$OPENSHIFT_CLIENT_CODE" = "201" ]; then echo "✅ openshift client created"; \
@@ -263,7 +263,7 @@ keycloak-setup-realm:
263263
OPENSHIFT_USERNAME_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$OPENSHIFT_CLIENT_ID/protocol-mappers/models" \
264264
-H "Authorization: Bearer $$TOKEN" \
265265
-H "Content-Type: application/json" \
266-
-d '{ "name":"username","protocol":"openid-connect","protocolMapper":"oidc-usermodel-property-mapper","config":{"userinfo.token.claim":"true","user.attribute":"username","id.token.claim":"true","access.token.claim":"true","claim.name":"preferred_username","jsonType.label":"String"}}'); \
266+
-d @dev/config/keycloak/mappers/username.json); \
267267
OPENSHIFT_USERNAME_MAPPER_CODE=$$(echo "$$OPENSHIFT_USERNAME_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
268268
if [ "$$OPENSHIFT_USERNAME_MAPPER_CODE" = "201" ] || [ "$$OPENSHIFT_USERNAME_MAPPER_CODE" = "409" ]; then \
269269
if [ "$$OPENSHIFT_USERNAME_MAPPER_CODE" = "201" ]; then echo "✅ Username mapper added to openshift client"; \
@@ -277,7 +277,7 @@ keycloak-setup-realm:
277277
MCP_PUBLIC_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \
278278
-H "Authorization: Bearer $$TOKEN" \
279279
-H "Content-Type: application/json" \
280-
-d '{"clientId":"mcp-client","enabled":true,"publicClient":true,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":false,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email"],"optionalClientScopes":["mcp-server"]}'); \
280+
-d @dev/config/keycloak/clients/mcp-client.json); \
281281
MCP_PUBLIC_CLIENT_CODE=$$(echo "$$MCP_PUBLIC_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
282282
if [ "$$MCP_PUBLIC_CLIENT_CODE" = "201" ] || [ "$$MCP_PUBLIC_CLIENT_CODE" = "409" ]; then \
283283
if [ "$$MCP_PUBLIC_CLIENT_CODE" = "201" ]; then echo "✅ mcp-client public client created"; \
@@ -295,7 +295,7 @@ keycloak-setup-realm:
295295
MCP_PUBLIC_USERNAME_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_PUBLIC_CLIENT_ID/protocol-mappers/models" \
296296
-H "Authorization: Bearer $$TOKEN" \
297297
-H "Content-Type: application/json" \
298-
-d '{"name":"username","protocol":"openid-connect","protocolMapper":"oidc-usermodel-property-mapper","config":{"userinfo.token.claim":"true","user.attribute":"username","id.token.claim":"true","access.token.claim":"true","claim.name":"preferred_username","jsonType.label":"String"}}'); \
298+
-d @dev/config/keycloak/mappers/username.json); \
299299
MCP_PUBLIC_USERNAME_MAPPER_CODE=$$(echo "$$MCP_PUBLIC_USERNAME_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
300300
if [ "$$MCP_PUBLIC_USERNAME_MAPPER_CODE" = "201" ] || [ "$$MCP_PUBLIC_USERNAME_MAPPER_CODE" = "409" ]; then \
301301
if [ "$$MCP_PUBLIC_USERNAME_MAPPER_CODE" = "201" ]; then echo "✅ Username mapper added to mcp-client"; \
@@ -309,7 +309,7 @@ keycloak-setup-realm:
309309
MCP_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \
310310
-H "Authorization: Bearer $$TOKEN" \
311311
-H "Content-Type: application/json" \
312-
-d '{"clientId":"mcp-server","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email","groups","mcp-server"],"optionalClientScopes":["mcp:openshift"],"attributes":{"oauth2.device.authorization.grant.enabled":"false","oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","backchannel.logout.revoke.offline.tokens":"false"}}'); \
312+
-d @dev/config/keycloak/clients/mcp-server.json); \
313313
MCP_CLIENT_CODE=$$(echo "$$MCP_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
314314
if [ "$$MCP_CLIENT_CODE" = "201" ] || [ "$$MCP_CLIENT_CODE" = "409" ]; then \
315315
if [ "$$MCP_CLIENT_CODE" = "201" ]; then echo "✅ mcp-server client created"; \
@@ -331,7 +331,7 @@ keycloak-setup-realm:
331331
UPDATE_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X PUT "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_CLIENT_ID" \
332332
-H "Authorization: Bearer $$TOKEN" \
333333
-H "Content-Type: application/json" \
334-
-d '{"clientId":"mcp-server","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email","groups","mcp-server"],"optionalClientScopes":["mcp:openshift"],"attributes":{"oauth2.device.authorization.grant.enabled":"false","oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","backchannel.logout.revoke.offline.tokens":"false","standard.token.exchange.enabled":"true"}}'); \
334+
-d @dev/config/keycloak/clients/mcp-server-update.json); \
335335
UPDATE_CLIENT_CODE=$$(echo "$$UPDATE_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
336336
if [ "$$UPDATE_CLIENT_CODE" = "204" ]; then \
337337
echo "✅ Standard token exchange enabled for mcp-server client"; \
@@ -354,7 +354,7 @@ keycloak-setup-realm:
354354
MCP_USERNAME_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_CLIENT_ID/protocol-mappers/models" \
355355
-H "Authorization: Bearer $$TOKEN" \
356356
-H "Content-Type: application/json" \
357-
-d '{"name":"username","protocol":"openid-connect","protocolMapper":"oidc-usermodel-property-mapper","config":{"userinfo.token.claim":"true","user.attribute":"username","id.token.claim":"true","access.token.claim":"true","claim.name":"preferred_username","jsonType.label":"String"}}'); \
357+
-d @dev/config/keycloak/mappers/username.json); \
358358
MCP_USERNAME_MAPPER_CODE=$$(echo "$$MCP_USERNAME_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \
359359
if [ "$$MCP_USERNAME_MAPPER_CODE" = "201" ] || [ "$$MCP_USERNAME_MAPPER_CODE" = "409" ]; then \
360360
if [ "$$MCP_USERNAME_MAPPER_CODE" = "201" ]; then echo "✅ Username mapper added to mcp-server client"; \
@@ -368,7 +368,7 @@ keycloak-setup-realm:
368368
USER_RESPONSE=$$(curl -sk -w "%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/users" \
369369
-H "Authorization: Bearer $$TOKEN" \
370370
-H "Content-Type: application/json" \
371-
-d '{"username":"mcp","email":"[email protected]","firstName":"MCP","lastName":"User","enabled":true,"emailVerified":true,"credentials":[{"type":"password","value":"mcp","temporary":false}]}'); \
371+
-d @dev/config/keycloak/users/mcp.json); \
372372
USER_CODE=$$(echo "$$USER_RESPONSE" | tail -c 4); \
373373
if [ "$$USER_CODE" = "201" ] || [ "$$USER_CODE" = "409" ]; then \
374374
if [ "$$USER_CODE" = "201" ]; then echo "✅ mcp user created"; \
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "groups",
3+
"protocol": "openid-connect",
4+
"attributes": {
5+
"display.on.consent.screen": "false",
6+
"include.in.token.scope": "true"
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "mcp:openshift",
3+
"protocol": "openid-connect",
4+
"attributes": {
5+
"display.on.consent.screen": "false",
6+
"include.in.token.scope": "true"
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "mcp-server",
3+
"protocol": "openid-connect",
4+
"attributes": {
5+
"display.on.consent.screen": "false",
6+
"include.in.token.scope": "true"
7+
}
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"clientId": "mcp-client",
3+
"enabled": true,
4+
"publicClient": true,
5+
"standardFlowEnabled": true,
6+
"directAccessGrantsEnabled": true,
7+
"serviceAccountsEnabled": false,
8+
"authorizationServicesEnabled": false,
9+
"redirectUris": ["*"],
10+
"defaultClientScopes": ["profile", "email"],
11+
"optionalClientScopes": ["mcp-server"]
12+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"clientId": "mcp-server",
3+
"enabled": true,
4+
"publicClient": false,
5+
"standardFlowEnabled": true,
6+
"directAccessGrantsEnabled": true,
7+
"serviceAccountsEnabled": true,
8+
"authorizationServicesEnabled": false,
9+
"redirectUris": ["*"],
10+
"defaultClientScopes": ["profile", "email", "groups", "mcp-server"],
11+
"optionalClientScopes": ["mcp:openshift"],
12+
"attributes": {
13+
"oauth2.device.authorization.grant.enabled": "false",
14+
"oidc.ciba.grant.enabled": "false",
15+
"backchannel.logout.session.required": "true",
16+
"backchannel.logout.revoke.offline.tokens": "false",
17+
"standard.token.exchange.enabled": "true"
18+
}
19+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"clientId": "mcp-server",
3+
"enabled": true,
4+
"publicClient": false,
5+
"standardFlowEnabled": true,
6+
"directAccessGrantsEnabled": true,
7+
"serviceAccountsEnabled": true,
8+
"authorizationServicesEnabled": false,
9+
"redirectUris": ["*"],
10+
"defaultClientScopes": ["profile", "email", "groups", "mcp-server"],
11+
"optionalClientScopes": ["mcp:openshift"],
12+
"attributes": {
13+
"oauth2.device.authorization.grant.enabled": "false",
14+
"oidc.ciba.grant.enabled": "false",
15+
"backchannel.logout.session.required": "true",
16+
"backchannel.logout.revoke.offline.tokens": "false"
17+
}
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"clientId": "openshift",
3+
"enabled": true,
4+
"publicClient": false,
5+
"standardFlowEnabled": true,
6+
"directAccessGrantsEnabled": true,
7+
"serviceAccountsEnabled": true,
8+
"authorizationServicesEnabled": false,
9+
"redirectUris": ["*"],
10+
"defaultClientScopes": ["profile", "email", "groups"],
11+
"optionalClientScopes": []
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "groups",
3+
"protocol": "openid-connect",
4+
"protocolMapper": "oidc-group-membership-mapper",
5+
"config": {
6+
"claim.name": "groups",
7+
"full.path": "false",
8+
"id.token.claim": "true",
9+
"access.token.claim": "true",
10+
"userinfo.token.claim": "true"
11+
}
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "mcp-server-audience",
3+
"protocol": "openid-connect",
4+
"protocolMapper": "oidc-audience-mapper",
5+
"config": {
6+
"included.client.audience": "mcp-server",
7+
"id.token.claim": "true",
8+
"access.token.claim": "true"
9+
}
10+
}

0 commit comments

Comments
 (0)