captain-p-goldfish / scim-sdk Goto Github PK
View Code? Open in Web Editor NEWa scim implementation as described in RFC7643 and RFC7644
Home Page: https://github.com/Captain-P-Goldfish/SCIM/wiki
License: BSD 3-Clause "New" or "Revised" License
a scim implementation as described in RFC7643 and RFC7644
Home Page: https://github.com/Captain-P-Goldfish/SCIM/wiki
License: BSD 3-Clause "New" or "Revised" License
The class ResourceEndpoint
defines methods to pass HTTP parameters as a map. As multiple HTTP header fields with the same field name appear in practice, the methods should use a multi map, e.g. javax.ws.rs.core.MultivaluedMap
Would consider pull-request exposing supplied attributes and excludedAttributes to the ResourceHandler#getResource API call?
It would allow avoiding possibly expensive joins when retrieving the data from storage (e.g. large set of group members) in a similar way how listResources currently works.
I'm thinking about the following change in the ResourceHandler API:
// class ResourceHandler<T>
public T getResource(String id, Authorization auth) {
// don't want it abstract, because then you would have to implement both getResource methods
throw NotImplementedException(...); // or InternalServerError?
}
public T getResource(String id, List<SchemaAttribute> attributes, List<SchemaAttribute> excludedAttributes, Authorization auth) {
return getResource(id, auth);
}
Call the getResource(String, List<SchemaAttribute>, List<SchemaAttribute>, Authorization)
from the ResourceEndpointHandler#getResource(String, String, String, String, Map<String,String>, Supplier<String>, Authorization)
It seems it would be helpful to move attribute parsing outside of the SchemaValidator, because in case of the list request, it's done by the endpoint list method and then again by the SchemaValidator constructor. But that's just an optimization, that's not really required for this change.
What do you think? Thanks!
Hi,
When performing a PATCH request to add or replace an attribute for a resource, I've noticed that some attributes are mirrored from the PATCH request payload into the response payload, even when the mirrored properties aren't actually being used in the SCIM 2.0 server application.
For example,
The SCIM server application uses the role
property for a user. As apart of the implementation, the application code (sitting within the ResourceHandler
implementation), only utilities the roles.display
sub-attribute, and sets only the roles.display
into response object.
Given a request that includes valid SCIM 2.0 attributes, but are attributes that are not explicitly set into the response, are appearing in the JSON response payload.
Request - A PATCH request to add a role, that includes the display
, value
, type
and primary
.
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "add",
"path": "roles",
"value": [
{
"primary": false,
"type": "WindowsAzureActiveDirectoryRole",
"display": "Role 2",
"value": "Something Irrelevant"
},
]
}
]
}
The returned User object, from the ResourceHandler
does NOT set anything except the roles.display
.
Response
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"id": "100325",
"userName": "scim.test13",
"displayName": "Dave",
"active": true,
"emails": [
{
"value": "[email protected]",
"type": "work",
"primary": true
}
],
"phoneNumbers": [
{
"value": "551 1234",
"type": "work"
}
],
"roles": [
{
"value": "Something Irrelevant",
"display": "Role 1",
"type": "WindowsAzureActiveDirectoryRole",
"primary": false
},
{
"value": "Something Irrelevant",
"display": "Role 2",
"type": "WindowsAzureActiveDirectoryRole",
"primary": false
}
],
"meta": {
"resourceType": "User",
"created": "2021-04-23T02:15:38.378Z",
"lastModified": "2021-07-21T17:45:13.223+10:00",
"location": "https://********************/Users/100325",
"version": "W/\"41BA1691F91FB9C9776DD2C18349540C051E59AD9E2339642B2CB0D32C7367D4FC7ABE6E9913B225A84B48E68AC2567BD2CF400149C6C9A93F5C84B1F5E98686\""
},
}
Additional values are present in the response.
ResourceEndpointHandler.patchResource()
method, I can see that the follow lineResourceNode patchedResourceNode = patchHandler.patchResource(resourceNode, patchOpRequest);
Is responsible for performing an in-memory update based on if the incoming node is different from the current state of the resource. The input reference of the resourceNode
(that is originally retrieved from server application) has its structure/values modified based on the incoming PATCH request.
patchedResourceNode
then becomes the result of the server applications update operation on the resource. The value that the patchedResourceNode
has after this line is the desired response object.Line 1032
patchedResourceNode = resourceHandler.updateResource(patchedResourceNode, authorization);
ResourceEndpointHandler.patchResource()
is the resourceNode
after the in-memory update, as apposed to the intended patchedResourceNode
.Line 1049
JsonNode responseResource = responseValidator.validateDocument(resourceNode);
return new UpdateResponse(responseResource, location, meta);
I am wondering if I am interpreting this behavior incorrectly, or is this in-fact unintentional? The main issue I'd like to solve is to ensure that all attributes that are in the response are attributed to a resource found in the server application.
Currently with this behavior, attributes are appearing in the response body that are not being set by the server application.
Is it possible to look into this issue, or otherwise suggest a work-around?
Thanks in advance
On singleton endpoints I missed to make the "ID"-attribute optional for patch operations. So singleton instances cannot be patched right now.
Hello, this is likely more a question than a bug, I'm using the issue tracking not having found other means.
I'm trying to use SCIM-SDK client against Keycloak by configuring it with BasicAuth
:
private ScimRequestBuilder createScimRequestBuilder(Config config)
{
ScimClientConfig scimClientConfig = ScimClientConfig.builder()
.basicAuth(BasicAuth.builder()
.username(config.clientId)
.password(config.clientSecret)
.build()
)
.build();
return new ScimRequestBuilder(config.scimBaseUrl, scimClientConfig);
}
In Keycloak the client is configured as
The SCIM extension has no explicitly authorized client, so it should allow them all.
How can I use client credentials just like I do with org.keycloak.admin.client.Keycloak
, or org.keycloak.authorization.client.AuthzClient
?
When automatic filtering is enabled and filter by meta attribute is used, for example meta.created eq "2000-10-10T00:00:00.000Z"
, NullPointerException is thrown when processing request.
It's caused by meta attribute being evaluated as extension attribute, but although it internally has it's own schema, it's not (and should not be) registered as an extension.
[...snip...]
Caused by: java.lang.NullPointerException
at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[?:?]
at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[?:?]
at jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[?:?]
at java.lang.reflect.Constructor.newInstance(Constructor.java:490) ~[?:?]
at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:603) ~[?:?]
at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:678) ~[?:?]
at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:737) ~[?:?]
at java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:919) ~[?:?]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233) ~[?:?]
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[?:?]
at de.captaingoldfish.scim.sdk.server.filter.resources.FilterResourceResolver.filterResources(FilterResourceResolver.java:50) ~[scim-sdk-server-1.10.0.jar:?]
at de.captaingoldfish.scim.sdk.server.endpoints.ResourceEndpointHandler.filterResources(ResourceEndpointHandler.java:654) ~[scim-sdk-server-1.10.0.jar:?]
at de.captaingoldfish.scim.sdk.server.endpoints.ResourceEndpointHandler.listResources(ResourceEndpointHandler.java:544) ~[scim-sdk-server-1.10.0.jar:?]
... 81 more
Caused by: java.lang.NullPointerException
at de.captaingoldfish.scim.sdk.server.filter.resources.FilterResourceResolver.retrieveComplexAttributeNode(FilterResourceResolver.java:170) ~[scim-sdk-server-1.10.0.jar:?]
at de.captaingoldfish.scim.sdk.server.filter.resources.FilterResourceResolver.visitAttributeExpressionLeaf(FilterResourceResolver.java:120) ~[scim-sdk-server-1.10.0.jar:?]
at de.captaingoldfish.scim.sdk.server.filter.resources.FilterResourceResolver.isResourceMatchingFilter(FilterResourceResolver.java:92) ~[scim-sdk-server-1.10.0.jar:?]
at de.captaingoldfish.scim.sdk.server.filter.resources.FilterResourceResolver.lambda$getResourcePredicate$0(FilterResourceResolver.java:61) ~[scim-sdk-server-1.10.0.jar:?]
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:176) ~[?:?]
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655) ~[?:?]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[?:?]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[?:?]
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:952) ~[?:?]
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:926) ~[?:?]
at java.util.stream.AbstractTask.compute(AbstractTask.java:327) ~[?:?]
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746) ~[?:?]
at java.util.concurrent.ForkJoinTask.doExec$$$capture(ForkJoinTask.java:290) ~[?:?]
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java) ~[?:?]
at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020) ~[?:?]
at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656) ~[?:?]
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594) ~[?:?]
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183) ~[?:?]
When oauthbearertoken
authentication scheme is configured on ServiceProvider and Authorization header is not provided, SCIM-SDK returns HTTP authentication challenge WWW-Authenticate: oauthbearertoken realm="SCIM"
. That seems incorrect.
According to RFC 6750 Section 3, the challenge must be Bearer
:
... the resource server MUST include the HTTP "WWW-Authenticate" response header field ...
All challenges defined by this specification MUST use the auth-scheme value "Bearer"
A similar issue is there for other schemes:
scim auth type | http auth challenge | notes |
---|---|---|
oauth | OAuth | |
oauth2 | ??? | don't know enough about oauth2, it does not seem straightforward |
oauthbearertoken | Bearer | |
httpbasic | Basic | |
httpdigest | Digest | also has a mandatory nonce parameter |
Generally, it seems that a custom AuthenticationScheme implementation is needed for proper support of some schemas.
I believe, that adding a new challenge
parameter to the AuthenticationScheme would solve the problem. I would skip WWW-Authenticate header value generation, when the challenge
is not set to avoid broken value generation (e.g. for digest).
scim-sdk-server-1.9.2
scim-sdk-common-1.9.2
I'm trying to filter Users by meta.created attr in request query and NPE is thrown in FilterResourceResolver.retrieveComplexAttributeNode(JsonNode jsonNode, AttributeExpressionLeaf attributeExpressionLeaf),
In my case jsonNode is user:
{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"id":"usr-0000000000000002","externalId":"425ffs","meta":{"created":"2021-07-15T17:07:55.955Z","lastModified":"2021-07-16T15:07:56.433Z"},"userName":"frontman","name":{"familyName":"Driveshaft","givenName":"Charlie"},"active":false,"emails":[{"type":"work","value":"[email protected]"}]}
and attributeExpressionLeaf:
meta.created EQ "2021-07-15T17:07:55.955Z"
In other cases attributeExpressionLeaf.isMainSchemaNode()
returns true and it works fine. In this case the leaf is not considered to be main schema node and in else branch extensionNode is set to null. Which makes sense since
jsonNode.get("urn:ietf:params:scim:schemas:core:2.0:Meta")
(in my case attributeExpressionLeaf.getSchemaAttribute().getSchema().getId().get() ~ urn:ietf:params:scim:schemas:core:2.0:Meta)
My List User request: /scim/v2/Users?startIndex=11&count=10&filter=externalId pr
I have more than 100 users, want to get 10 users started from index 11, I got empty response.
Please, when generating source code artifact, perform a "delombok" step, so to avoid discrepancies between bytecode and source, which makes debugging much harder.
Thanks!
Note: "migrated" from Captain-P-Goldfish/scim-for-keycloak#7
While writing the example code for #134 I noticed, that basic authentication does not work if the ScimHttpClient
is used directly.
Currently it is possible to specify a doBeforeConsumer (e.g. for authentication handling).
It would be nice to be able to specify a doAfterConsumer also to handle the SCIMResponse, e.g. for different transaction behaviour depending on the response.
I'm just having the first trial with SCIM/KC. Not sure whether a misconfiguration leads to this. I just ask ...
Requesting users using this powershell leads to a successful authentication and later a throws "{"detail":"sorry but an internal error has occurred.","schemas":["urn:ietf:params:scim:api:messages:2.0:Error"],"status":500,"scimType":"invalidValue"}":
$response = Invoke-RestMethod 'https://kc.domain.ch/auth/realms/beta/scim/v2/Users' -Method 'GET' -Headers $headers -Body $body
On Keycloak I observe this:
ERROR [de.captaingoldfish.scim.sdk.common.response.ErrorResponse] (default task-62) the attribute 'userName' must match the regular expression '[a-z-_ ]+' but value is '[email protected]': de.captaingoldfish.scim.sdk.common.exceptions.DocumentValidationException: the attribute 'userName' must match the regular expression '[a-z-_ ]+' but value is '[email protected]'
the user [email protected] is the first user in the list of my realm. it's not the user I use to login so I guess SCIM just wanted to send me a userlist and struggled with the first one. Is KC 11.0.3 already stable?
thanks!
I have noticed that the schema for User resource declares the userName attribute as immutable. Is that intentional?
Would you consider changing it to readWrite?
I know I can have a custom schema easily, but it might be better to have the default schema compliant with the RFC.
Hi, I am implementing a client and would like to first check /ResourceTypes
in order to discover the types of resources available on the SCIM service provider. I did not find any ResourceType
object in scim-sdk-common
, but found an implementation in the scim-sdk-server
. Is there a reason why this object is not in scim-sdk-common
? I can of course add the server as a dependency or just supply my own implementation of ResourceType
but I am curious about why the object only exists in the server module.
The meta-schema for schemas is not correctly returned from the endpoint. This is caused by the more or less hardcoded schema validation to handle only one level of nesting as it was described in RFC7644.
In order to fix this smoothly a reimplementation of the SchemaValidator is necessary. The implementation of this class grew historically when I did not know what was coming at me.
When path in patch-remove operation is matching multiple values in multivalue attribute, it only removes correctly the first one.
The issue is in PatchTargetHandler#handleDirectMultiValuedComplexPathReference
:
for ( int i = 0 ; i < matchingComplexNodes.size() ; i++ )
{
multiValued.remove(matchingComplexNodes.get(i).getIndex());
changeWasMade = true;
}
After the first matching node is removed, the indexes in the multiValued shifts. Thus the next removal removes an incorrect node.
It seems the easiest fix would be to just iterate the matchingComplexNodes in a reverse order. I assume that the values returned by getIndex() are always increasing with position in the matchingComplexNodes list.
It's easy to reproduce on Group resource with multiple members and a patch operation like:
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "remove",
"path": "members[value eq \"member1\" or value eq \"member2\"]"
}
]
}
Unfortunately, I won't be able to provide a patch with testcases in reasonable time.
I try to patch an existing group with the following request
PATCH ..../auth/realms/Goldfish/scim/v2/Groups/d2ba0d0c-9536-4d61-beec-2b656170b0c8
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op":"add",
"path":"members",
"value":[
{
"value": "1a463183-453d-467e-9ee5-602852754ba3",
"type": "User"
}
]
}
]
}
The user is assigned to the group, but the request returns an internal server error (500)
{
"detail": "sorry but an internal error has occurred.",
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:Error"
],
"status": 500
}
the stack-trace is:
15:00:11,077 ERROR [de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator] (default task-46) meta attribute validation failed for resource type: Group [urn:ietf:params:scim:schemas:core:2.0:Group]
15:00:11,078 ERROR [de.captaingoldfish.scim.sdk.common.response.ErrorResponse] (default task-46) the attribute 'urn:ietf:params:scim:schemas:core:2.0:Meta:meta.resourceType' is required on response.
name: 'resourceType'
type: 'STRING'
description: 'The name of the resource type of the resource. This attribute has a mutability of "readOnly" and "caseExact" as "true".'
mutability: 'READ_ONLY'
returned: 'DEFAULT'
uniqueness: 'NONE'
multivalued: 'false'
required: 'true'
caseExact: 'true': de.captaingoldfish.scim.sdk.common.exceptions.DocumentValidationException: the attribute 'urn:ietf:params:scim:schemas:core:2.0:Meta:meta.resourceType' is required on response.
name: 'resourceType'
type: 'STRING'
description: 'The name of the resource type of the resource. This attribute has a mutability of "readOnly" and "caseExact" as "true".'
mutability: 'READ_ONLY'
returned: 'DEFAULT'
uniqueness: 'NONE'
multivalued: 'false'
required: 'true'
caseExact: 'true'
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.getException(SchemaValidator.java:1287)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.validateIsRequiredForResponse(SchemaValidator.java:894)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.validateIsRequired(SchemaValidator.java:831)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.checkMetaAttributeOnDocument(SchemaValidator.java:577)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.validateAttributes(SchemaValidator.java:553)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.handleComplexNode(SchemaValidator.java:757)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.handleNode(SchemaValidator.java:736)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.checkMetaAttributeOnDocument(SchemaValidator.java:595)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.validateAttributes(SchemaValidator.java:553)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.validateDocument(SchemaValidator.java:524)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.validateExtensionForResponse(SchemaValidator.java:371)
at de.captaingoldfish.scim.sdk.server.schemas.SchemaValidator.validateDocumentForResponse(SchemaValidator.java:261)
at de.captaingoldfish.scim.sdk.server.endpoints.ResourceEndpointHandler.patchResource(ResourceEndpointHandler.java:947)
at de.captaingoldfish.scim.sdk.server.endpoints.ResourceEndpoint.resolveRequest(ResourceEndpoint.java:178)
at de.captaingoldfish.scim.sdk.server.endpoints.ResourceEndpoint.handleRequest(ResourceEndpoint.java:98)
at de.captaingoldfish.scim.sdk.keycloak.scim.ScimEndpoint.get(ScimEndpoint.java:80)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
A PATCH Request on a Group does not remove the member
Sample
**PATCH** request Url: https://keycloa/auth/realms/Demo/scim/v2/Groups/e0d1016a-26dc-446f-8dab-2fd29aa4f779
Parameters : {
"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations":[
{"op":"Remove","path":"members[value eq \"fb945029-265c-4c41-9ada-459f013ed126\"]"}
]
}
A Fix could be:
private GroupModel groupToModel(KeycloakSession keycloakSession, Group group, GroupModel groupModel)
{
RealmModel realmModel = keycloakSession.getContext().getRealm();
group.getDisplayName().ifPresent(groupModel::setName);
if (group.getExternalId().isPresent())
{
groupModel.setSingleAttribute(AttributeNames.RFC7643.EXTERNAL_ID, group.getExternalId().get());
}
List<Member> groupMembers = group.getMembers();
keycloakSession.users().getGroupMembers(realmModel, groupModel).stream().forEach(modelMember -> {
boolean found = false;
for ( Member groupMember : groupMembers )
{
if (groupMember.getType().isPresent() && groupMember.getType().get().equalsIgnoreCase("User"))
{
if (groupMember.getValue().get().equals(modelMember.getId()))
{
found = false;
}
}
}
if (!found)
{
modelMember.leaveGroup(groupModel);
}
});
Hi, as mentioned on https://issues.redhat.com/browse/KEYCLOAK-2537?_sscc=t
that this might be a good fit. Do you mind to share an example (e.g. on the Wiki) how to use it with Keycloak. That would be of great use. Thanks.
Hi Pascal,
I have identified an issue in version 1.11.1
where any extension property (for example anything in the enterprise user schema, or a custom schema) that is updated via PATCH and is supplied in the below format will not map correctly into the User
object after the incoming patch object is merged with the existing object is completed.
For example, if I perform the following PATCH request on a User
to update their employeeNumber
attribute in the enterprise extension schema:
{
"Operations": [
{
"op": "replace",
"value": {
...
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeNumber": "1111"
}
}
],
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
]
}
From what I can see, in this specific scenario, the way which the "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeNumber" is resolved in PatchResourceHandler.addResourceValues()
seems to be incorrectly mapped as a "root" level property, instead of an "extension" property.
After the incoming patch object is merged with the existing object. The User
object in ResourceHandler<User>.updateResource(User updatedUser, Authorization authorization)
looks like the following:
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "2222" // Old employee ID
},
"employeeNumber": "1111", // New Employee ID
"id": "1",
"userName": "user name",
...
"meta": {
...
}
}
I would expect the User
object would look like this instead:
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "1111" // Updated extension property
},
"id": "1",
"userName": "user name",
...
"meta": {
...
}
}
I believe a fix could be put in around the method PatchResourceHandler.addResourceValues()
specifically around line 77 to better determine whether the incoming key
is an extension property.
E.g. The key
would be "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeNumber", but the schema would be "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" which does not match.
Furthermore, there maybe some changes required to update the key
value before it is passed into the recursive call at line 87.
In my investigation this issue is not just limited to the User
resource but is also effecting Group
too. But I think a fix in this handler would resolve it for all.
Thank you very much for your time, taking a look at this issue.
Please let me know if you need any further information or clarification.
Kyle
I'm trying to filter by manager property from the enterprise user extension. But I don't get any results. Also, if I try to remove the manager property with a PATCH request, it doesn't work for me either.
The filter query:
/Users/?filter=urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager.value eq "managerid"
The PATCH request:
PATCH /Users/userid
Content-Type: application/scim+json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"Operations": [
{
"op": "remove",
"path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager"
}
]
}
Although I believe the queries are ok, I'm not totally sure it's not error on my side.
I want to implement a scim server, Endpoint for example /Tenants/{tenantId}/**
, how to support multi-tenancy?
I see that ResouceHandler
does not have a way to pass the TenantId parameter when processing
public abstract T updateResource(T resourceToUpdate, Authorization authorization);
Just had a quick question regarding the default User schema and the best practices section in the wiki.
If we want to remove attributes from the default User schema, is the proper way to create a new schema entirely? I tried modifying the schema by removing attributes at runtime (based on the demonstration of modifying schema in the wiki) but it didn't appear to work.
Thanks!
I'm not completely sure how empty/blank strings should be treated in SCIM, but the SCIM-SDK API seems to treat them a bit inconsistently.
ScimObjectNode#getStringAttribute(java.lang.String)
.ScimObjectNode#setAttribute(java.lang.String, java.lang.String)
, It will be normalized to a missing value.Example request:
POST http://localhost:8180/ssc/api/scim/v2/Groups
Content-Type: application/scim+json
{
"displayName": "",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:Group"
]
}
It seems it would be better if empty/blank strings would be treated consistently in SCIM-SDK API. Or is there some rationale I'm missing for this behavior?
I haven't found any mentions about empty/blank strings in the SCIM RFCs, so probably the setAttribute should not attempt string normalization?
When trying to deploy the .ear file into keycloak 9.0.0 server deployments, encountered the following exception - the class PasswordUserCredentialModel
comes from Keycloak version 11
2020-12-17 11:32:27,792 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-11) Uncaught server error: java.lang.NoClassDefFoundError: org/keycloak/models/credential/PasswordUserCredentialModel
at deployment.scim-for-keycloak-deployment.ear.scim-for-keycloak-server.jar//de.captaingoldfish.scim.sdk.keycloak.scim.ScimConfiguration.createNewResourceEndpoint(ScimConfiguration.java:72)
at deployment.scim-for-keycloak-deployment.ear.scim-for-keycloak-server.jar//de.captaingoldfish.scim.sdk.keycloak.scim.ScimConfiguration.getScimEndpoint(ScimConfiguration.java:55)
at deployment.scim-for-keycloak-deployment.ear.scim-for-keycloak-server.jar//de.captaingoldfish.scim.sdk.keycloak.scim.AbstractEndpoint.<init>(AbstractEndpoint.java:34)
at deployment.scim-for-keycloak-deployment.ear.scim-for-keycloak-server.jar//de.captaingoldfish.scim.sdk.keycloak.scim.ScimEndpoint.<init>(ScimEndpoint.java:60)
at deployment.scim-for-keycloak-deployment.ear.scim-for-keycloak-server.jar//de.captaingoldfish.scim.sdk.keycloak.provider.ScimEndpointProvider.getResource(ScimEndpointProvider.java:40)
at [email protected]//org.keycloak.services.resources.RealmsResource.resolveRealmExtension(RealmsResource.java:280)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at [email protected]//org.jboss.resteasy.core.ResourceLocatorInvoker.createResource(ResourceLocatorInvoker.java:69)
at [email protected]//org.jboss.resteasy.core.ResourceLocatorInvoker.createResource(ResourceLocatorInvoker.java:48)
at [email protected]//org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:99)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:440)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
at [email protected]//org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:356)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
at [email protected]//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:227)
at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
at [email protected]//javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
at [email protected]//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
at [email protected]//org.keycloak.services.filters.KeycloakSessionServletFilter.doFilter(KeycloakSessionServletFilter.java:91)
at [email protected]//io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at [email protected]//io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
at [email protected]//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at [email protected]//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
at [email protected]//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at [email protected]//org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
at [email protected]//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
at [email protected]//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at [email protected]//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at [email protected]//io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at [email protected]//io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at [email protected]//io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
at [email protected]//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//io.undertow.server.handlers.MetricsHandler.handleRequest(MetricsHandler.java:64)
at [email protected]//io.undertow.servlet.core.MetricsChainHandler.handleRequest(MetricsChainHandler.java:59)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
at [email protected]//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at [email protected]//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at [email protected]//org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
at [email protected]//io.undertow.server.Connectors.executeRootHandler(Connectors.java:376)
at [email protected]//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
at [email protected]//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at [email protected]//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982)
at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.ClassNotFoundException: org.keycloak.models.credential.PasswordUserCredentialModel from [Module "deployment.scim-for-keycloak-deployment.ear.scim-for-keycloak-server.jar" from Service Module Loader]
at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:255)
at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)```
I'm running into #131 which I see was fixed a while back. Any chance of a new release?
PS - thanks for the nice library! I quite appreciate the auto-filtering, it got me started really quickly, but I'm ready to turn it off and try out direct sql for better performance
According to the SCIM RFC, it mentions that if there are multiple operations, and one fails, all operations should be rolled back to the start state.
A PATCH request, regardless of the number of operations, SHALL be
treated as atomic. If a single operation encounters an error
condition, the original SCIM resource MUST be restored, and a failure
status SHALL be returned.
And I noticed that this SCIM-SDK doesn't do that. Is this something you are aware of?
Below you can find the JSON Payload of the Patch Operation, which is trying to update the same emails.value attribute.
The first operation is fine, but the second operation fails due to a bad path, and then the third operation is not executed.
What I would expect to happen is when the second operation fails, the first one is rolled back.
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"path": "emails[value eq \"[email protected]\"].value",
"op": "replace",
"value": "[email protected]"
},
{
"path": "email[value eq \"[email protected]\"].value",
"op": "replace",
"value": "[email protected]"
},
{
"path": "emails[value eq \"[email protected]\"].value",
"op": "replace",
"value": "[email protected]"
}
]
}
Hey! This is likely more a question than a issue..
Im using the ScimClient to retrieve users and groups from an cloud based IDP (Federated Directory) with a bearer token authentication.
However, the isSucess () check returns false, although the HTTP status is 200 and data is returned in the body.
I have the same problem with the node.js based scim gateway. Here, however, only with Basic Auth.
Information from the response is printed here:
HEADER: {X-Cloud-Trace-Context=a6129f6f41bc3cae194165aa24bdd7f5, Transfer-Encoding=chunked, Server=Google Frontend, vary=origin,accept-encoding, cache-control=no-cache, Date=Thu, 25 Mar 2021 10:07:55 GMT, Content-Type=application/json; charset=utf-8}
HTTP STATUS: 200
IS SUCCESS: false
BODY: {"totalResults":6,"itemsPerPage":50,"startIndex":1,"schemas":["urn:ietf:params:scim:api:messages:2.0:ListResponse"],"Resources":[{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"id":"a1ec9b70-8d45-11eb-8092-6b8b09f2c982","userName":"[email protected]","displayName":"Armando Pearson","photos":[{"type":"photo","primary":true,"value":"https://cdn.federated.directory/images/users/demo/m26.jpg"},{"type":"thumbnail","value":"https://cdn.federated.directory/images/users/demo/thumb/thumb_m26.jpg"}],"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{"division":"Getting Started","department":"Bulk"}},{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"id":"8eefc470-8d45-11eb-8092-6b8b09f2c982","userName":"[email protected]","displayName":"Babs Jensen","photos":[{"type":"photo","primary":true,"value":"https://cdn.federated.directory/images/users/demo/w1.jpg"},{"type":"thumbnail","value":"https://cdn.federated.directory/images/users/demo/thumb/thumb_w1.jpg"}],"title":"Tour Guide","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{"division":"Getting Started"}},{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"id":"a1ed10a0-8d45-11eb-8092-6b8b09f2c982","userName":"[email protected]","displayName":"Deborah Watson","photos":[{"type":"photo","primary":true,"value":"https://cdn.federated.directory/images/users/demo/w18.jpg"},{"type":"thumbnail","value":"https://cdn.federated.directory/images/users/demo/thumb/thumb_w18.jpg"}],"title":"Project Manager","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{"division":"Getting Started","department":"Bulk"}},,{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"id":"a1e0dba0-8d45-11eb-8092-6b8b09f2c982","userName":"[email protected]","displayName":"Mae Thomas","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{"division":"Getting Started","department":"Bulk"}},{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"id":"a1ec2640-8d45-11eb-8092-6b8b09f2c982","userName":"[email protected]","displayName":"Rói Da Rosa","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{"division":"Getting Started","department":"Bulk"}}]}```
Create a group "Group1"
When you try to create "Group" you get an error that a group with this name allready exists
That is because
searchForGroupByName
()
returns all matching groups
to go around this one can:
if (keycloakSession.realms()
.searchForGroupByName(keycloakSession.getContext().getRealm(), groupName, null, null)
.stream()
.filter(g -> ((GroupModel)g).getName().equals(groupName))
.findFirst()
.orElse(null) != null)
{
throw new ConflictException("a group with name '" + groupName + "' does already exist");
}
Running version 1.10.0.
The feature of adding a $ref
value is super handy. Unfortunately it does not seem to work with the canonical values for listing groups a user belongs to, because it uses the RFC7643.TYPE
field to try and lookup a resource type. This fails if you use the RFC7643 recommended values of "direct" or "indirect". Seems like a simple fix is possible - add a special case to check if the TYPE field is one of the canonical values of "direct" or "indirect", and map that to "Group".
For example:
"groups": [
{
"value": "9d66400a-149e-40da-b55a-96d9bb6100aa",
"display": "test group",
"type": "indirect"
}
],
Use non-canonical resource name e.g. GroupNode.builder().type("Group")
instead of GroupNode.builder().type("direct")
. This works as expected:
"groups": [
{
"value": "9d66400a-149e-40da-b55a-96d9bb6100aa",
"$ref": "http://localhost:8089/scim/v2/Groups/9d66400a-149e-40da-b55a-96d9bb6100aa",
"display": "test group",
"type": "Group"
}
],
groups
A list of groups to which the user belongs, either through direct
membership, through nested groups, or dynamically calculated. The
values are meant to enable expression of common group-based or
role-based access control models, although no explicit
authorization model is defined. It is intended that the semantics
of group membership and any behavior or authorization granted as a
result of membership are defined by the service provider. The
canonical types "direct" and "indirect" are defined to describe
how the group membership was derived. Direct group membership
indicates that the user is directly associated with the group and
SHOULD indicate that clients may modify membership through the
"Group" resource. Indirect membership indicates that user
membership is transitive or dynamic and implies that clients
cannot modify indirect group membership through the "Group"
resource but MAY modify direct group membership through the
"Group" resource, which may influence indirect memberships. If
the SCIM service provider exposes a "Group" resource, the "value"
sub-attribute MUST be the "id", and the "$ref" sub-attribute must
be the URI of the corresponding "Group" resources to which the
user belongs. Since this attribute has a mutability of
"readOnly", group membership changes MUST be applied via the
"Group" Resource (Section 4.2). This attribute has a mutability
of "readOnly".
See ResponseAttributeValidator#overrideEmptyReferenceNodeInComplex:
Hi all,
we're trying to implement a SCIM solution for our application with MS Azure as a user directory and are currently using SCIM SDK 1.9.2 (also tried 1.10.0). So far I'm really enthusiastic about the SCIM SDK (though OTOH the MS SCIM implementation often seems quite strange).
However, when I remove a user from a group on the Azure side the following SCIM message is sent:
PATCH /scim/Groups/2752513
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "Remove",
"path": "members",
"value": [
{
"value": "2392066"
}
]
}
]
}
(2752513 is my group id, 2392066 the id of the removed user). SCIM-SDK answers with
400 Bad Request
{
"detail": "values must not be set for remove operation but was: {\"value\":\"2392066\"}",
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:Error"
],
"status": 400,
"scimType": "invalidValue"
}
Is this to be considered a bug on the Azure side? If yes, how is SCIM supposed to remove a group member (without sending the whole group)? Or am I missing something (or is there some kind of workaround)?
If this is an issue in SCIM SDK, of course I'm willing to help resolving this. So if you need some more data, just let me know.
Cheers
Thomas
Sample Request
POST request Url: https://keycloak/auth/realms/REALM/scim/v2/Groups
Parameters :
{
"displayName":"NeGroup",
"schemas":["urn:ietf:params:scim:schemas:core:2.0:Group"],
"members":[
{
"type":"User",
"value":"badid-265c-4c41-9ada-459f013ed126"
}],
"active":true}
A fix may be
group.getMembers()
.stream()
.filter(groupMember -> groupMember.getType().isPresent()
&& groupMember.getType().get().equalsIgnoreCase("User"))
.forEach(groupMember -> {
UserModel userMember = keycloakSession.users().getUserById(groupMember.getValue().get(), realmModel);
// ADDED
if (userMember == null)
{
throw new ResourceNotFoundException(String.format("An user with Id %s was not found",
groupMember.getValue().get()));
}
userMember.joinGroup(groupModel);
});
This might be me misreading 7643, but....
In 7643 section 2.4 we see this:
Multi-valued attributes contain a list of elements using the JSON
array format defined in Section 5 of [RFC7159]. Elements can be
either of the following:
o primitive values, or
o objects with a set of sub-attributes and values, using the JSON
object format defined in Section 4 of [RFC7159], in which case
they SHALL be considered to be complex attributes. As with
complex attributes, the order of sub-attributes is not
significant. The predefined sub-attributes listed in this section
can be used with multi-valued attribute objects, but these
sub-attributes MUST be used with the meanings defined here.If not otherwise defined, the default set of sub-attributes for a
multi-valued attribute is as follows:
I'm focused on that last line "If not otherwise defined". However, 7643's urn:ietf:params:scim:schemas:core:2.0:Group
does define the set of sub-attributes for the members
, and it does not include primary
. So it seems like the Java class Member
should possibly not have a primary
attribute?
Noticed this while trying to use the builder for member and got confused about what was meant by primary group, that led me to looking up the schema and noticing this was (possibly) a bit off
With the below code snippet, I can go straight to the endpoints without providing any Authorization.
private void authenticateClient(UriInfos uriInfos, Authorization authorization)
{
ResourceType resourceType = uriInfos.getResourceType();
if (!resourceType.getFeatures().getAuthorization().isAuthenticated())
{
// no authentication required for this endpoint
return;
}
Optional.ofNullable(authorization).ifPresent(auth -> {
boolean isAuthenticated = auth.authenticate(uriInfos.getHttpHeaders(), uriInfos.getQueryParameters());
if (!isAuthenticated)
{
log.error("authentication has failed");
throw new UnauthenticatedException("not authenticated", getServiceProvider().getAuthenticationSchemes(),
auth.getRealm());
}
});
}
I tried the following json schema and used "addtionalProperties" keyword to control the handling of extra stuff, but it seems no support for it
Couldn't find it in SchemaValidator.java
.
.
.
,
"additionalProperties": false,
"meta": {
"resourceType": "Schema",
"created": "2019-10-18T14:51:11+02:00",
"lastModified": "2019-10-18T14:51:11+02:00",
"location": "/Schemas/User"
}
}
We want to use the keycloak example in a production environment. Therefore we want to use a released version of scim-sdk without the need of patching and building it.
Enabling it by system property should be ok.
The Microsoft Azure SCIM implementation sends patch requests for users in the following form:
{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations":[{"op":"Add","path":"name.formatted","value":"My display name"}]}
Please note the "active" flag is not sent. This is translated to a call to updateResource()
. When I'm asking for
if (user.isActive().isPresent() && !user.isActive().get()) {
deactivateUser(...);
}
the call is executed (probably because in the User constructor active
is not Optional
and so has to have some value).
When (soft-)deleting a user on the Azure side, a similar call is sent with active=false. My application needs this information to deactivate users on the app side.
Naively, I'd expect user.isActive().isPresent()
to return false for the first call. Am I getting this wrong? Or is there a better way to distinguish these cases?
Regards
Thomas
master branch package de.captaingoldfish.scim.sdk.server.filter.antlr ScimFilter* java files is missing
Hi! I would love to use this project, but noticed that the latest versions are missing from Oss Sonatype, and Maven Central.
If a string attribute is set to null
, the request fails with type mismatch error. But it seems the same issue affects other attribute types too.
For example:
POST /scim/v2/Groups
Content-Type: application/scim+json
{
"displayName": "my group",
"externalId": null,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:Group"
]
}
Fails with:
{
"detail": "value of field with name 'urn:ietf:params:scim:schemas:core:2.0:Group:externalId' is not of type 'string' but of type: null",
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:Error"
],
"status": 400
}
According to RFC 7643 it seems like an incorrect behavior and the request should be treated the same way as when the externalId
attribute is omitted:
2.5. Unassigned and Null Values
Unassigned attributes, the null value, or an empty array (in the case of a multi-valued attribute) SHALL be considered to be equivalent in "state". Assigning an attribute with the value "null" or an empty array (in the case of multi-valued attributes) has the effect of making the attribute "unassigned". When a resource is expressed in JSON format, unassigned attributes, although they are defined in schema, MAY be omitted for compactness.
I believe the safest/easiest fix would to replace NullNode
with null
at the beginning of SchemaValidator#checkMetaAttributeOnDocument
method. I tried that and it fixes the issue.
Would you be interested in a pull-request?
Do think it would be possible to expose a knob to disable using parallel stream processing using ForkJoinPool? Or maybe allow specifying a custom ForkJoinPool instead of using the default common pool? The reason is it may cause unpredictable load on system.
It seems to be only used in 3 places: parsing, filtering and sorting.
I am going to add a new feature that should be an imitation of the java enterprise bean validation. Currently there is already a validation feature present but this shall be extended to add custom validations and it should go hand in hand with the schema-validation to create unified error messages that are pretty easily parseable on client side.
I am trying to perform patch operation on a group and request is standard request as given in https://docs.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
The payload is something like.
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "Remove", "path": "members", "value": [{ "$ref": null, "value": "f648f8d5ea4e4cd38e9c" }] }] }
But the response is always some validation error
values must not be set for remove operation but was: {"$ref":null,"value":"f648f8d5ea4e4cd38e9c"}"
Please guide me here.
Given a User with an emails payload of:
"emails": [
{
"value": "[email protected]",
"type": "work",
"primary": true
},
{
"value": "[email protected]",
"type": "home"
},
{
"value": "[email protected]",
"type": "private"
},
{
"value": "[email protected]"
}
],
When patching with the following request
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"path": "emails[value eq \"[email protected]\"]",
"op": "replace",
"value": "{\n \"type\" : \"business\",\n \"primary\" : false,\n \"display\" : \"testEmail\",\n \"value\" : \"[email protected]\",\n \"$ref\" : \"ref\"\n}"
}
]
}
I expect the outcome to be
"emails": [
{
"value": "[email protected]",
"type": "business",
"display": "testEmail",
"primary": true
},
{
"value": "[email protected]",
"type": "home"
},
{
"value": "[email protected]",
"type": "private"
},
{
"value": "[email protected]"
}
],
But the actual outcome is
"emails": [
{
"value": "[email protected]",
"type": "business",
"primary": false
}
],
According to the wiki, it says that this should only update matching nodes from the filter https://github.com/Captain-P-Goldfish/SCIM-SDK/wiki/Patching-resources#add-3
When using add
with the same filter, a new item is just added to the array, they aren't "merged" together
If you try to add an user with an email that is allready used by another user, a 500 - error with message:
{
"error": "org.hibernate.exception.ConstraintViolationException: could not execute statement"
}
is returned.
It should return a meaningfull error.
If request query parameters contains just parameter name (without an =
), exception is thrown due to negative index passed to substring method, resulting in HTTP 500 error.
There might be clients requiring such query parameter flags. For example Azure AD is using value-less query parameters to manage client behavior.
When patching a boolean attribute, for example User:active
, the request fails due to PatchOp validation. For example:
PATCH /Users/id
Content-Type: application/scim+json
{
"Operations": [
{
"op": "replace",
"path": "active",
"value": true
}
],
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
]
}
Fails with:
{
"detail": "value of field with name 'urn:ietf:params:scim:api:messages:2.0:PatchOp:Operations.value' is not of type 'string' but of type: boolean",
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:Error"
],
"status": 400
}
The PathOp declares the value attribute as string/multivalue. On the other hand, a json object in the value is (incorrectly?) accepted due the this check:
// class SchemaValidator
// private JsonNode validateComplexAndArrayTypeAttribute(JsonNode document, SchemaAttribute schemaAttribute)
if (isSimpleMultiValuedExpected && !isNodeSimpleMultiValued && !isNodeMultiValuedComplex)
{
ArrayNode arrayNode = new ScimArrayNode(schemaAttribute);
arrayNode.add(document);
return arrayNode;
}
It seems for PatchOp value validation, the SCIM type system is not sufficient and some custom extension will be needed, because the value can have any type. Surprisingly, the following request succeeds, although the active has type boolean:
PATCH /Users/id
Content-Type: application/scim+json
{
"Operations": [
{
"op": "replace",
"path": "active",
"value": "true"
}
],
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
]
}
Just reporting in case it slipped notice, feel free to close if the current approach is intentional
Inside JsonHelper, almost every method creates a new ObjectMapper. As JsonHelper is used for every API call, this seems to be a fair number of allocations. OM is not just a simple DTO object it has a few internal classes with non-trivial internal structures.
I have not tested this at all, but suspect you could see better performance (both req/sec and memory thrashing) by using a pool of OMs. Ideally, callers would be able to configure the pool size to match the server worker thread pool size.
Having a OM pool would prevent the repeated allocations. While (IIUC) OM is a thread-safe API (so you could just use a single OM), having a pool also prevents slowdowns from thread synchronization
When I try to patch an user with the request
PATCH /auth/realms/REALM/scim/v2/Users/03146c7f-acab-4edd-a6b2-0648ad405eb9
{
"schemas":
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{
"op":"replace",
"path": "displayName",
"value": "Erna Elvira Ente"
}]
}
I get the error
{
"detail": "the attribute 'Operations.value' does not apply to its defined type. The received document node is of type 'STRING' but the schema defintion is as follows: \n\tmultivalued: true\n\ttype: STRING\nfor schema with id urn:ietf:params:scim:api:messages:2.0:PatchOp\n\"Erna Elvira Ente\"",
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:Error"
],
"status": 400
}
whereas
"value": ["Erna Elvira Ente"]
works, but as far as I understand from rfc7643 displayName is a Singular Attribute.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.