public Collection<Group> findAllGroups(final User user) {
return CollectionUtils.union(
CollectionUtils.collect(user.getMemberships(),new
Transformer<UMembership, Group>() {
@Override public Group transform(final UMembership input) {
return input.getRightEnd();
}
},new ArrayList<Group>()),
findDynGroupMemberships(user));
}
[1] Here's exactly the code from the SCIM Provider side (calling
Syncope REST API) :
// This is the method called on PUT for Group endpoint
public Group update(String id, Group group) {
// 1. General group update
GroupTO existingGroupTO = getGroupTOByKey(id);
List<UserTO> usersOfThisGroup = getUsersForGroup(id);
GroupTO groupTO = groupConverter.fromScim(group);
groupTO.setKey(existingGroupTO.getKey());
GroupService groupService =
syncopeClient.getService(GroupService.class);
Response response;
try {
response = groupService.update(groupTO);
} catch (SyncopeClientException ex) {
throw
syncopeExHandler.convertSyncopeGroupClientException(ex,
group.getDisplayName(), ProvisioningOperation.UPDATING);
}
if (response == null || Response.Status.OK.getStatusCode() !=
response.getStatus()) {
throw
syncopeExHandler.convertSyncopeGroupErrorResponse(response,
group.getDisplayName(),
ProvisioningOperation.UPDATING);
}
// 2. General membership update
List<String> idsOfExistingUsers =
usersOfThisGroup.stream().map(user ->
user.getKey()).collect(Collectors.toList());
List<String> idsOfNewUsers =
group.getMembers().stream().map(member ->
member.getValue()).collect(Collectors.toList());
for (MemberRef memberRef : group.getMembers()) {
if (!idsOfExistingUsers.contains(memberRef.getValue())) {
addUserToGroup(groupTO, memberRef.getValue());
}
}
for (String userId : idsOfExistingUsers) {
if (!idsOfNewUsers.contains(userId)) {
removeUserFromGroup(groupTO, userId);
}
}
return getById(id);
}
private void addUserToGroup(GroupTO groupTO, String userId) {
UserService userService =
syncopeClient.getService(UserService.class);
UserPatch userPatch = new UserPatch();
userPatch.setKey(userId);
userPatch.getMemberships().add(
new
MembershipPatch.Builder().operation(PatchOperation.ADD_REPLACE).group(groupTO.getKey()).build());
try {
userService.update(userPatch);
} catch (SyncopeClientException e) {
throw new SCIMException(String.format("User %s was not
added to the group %s", userId, groupTO.getName()), e);
}
}
private void removeUserFromGroup(GroupTO groupTO, String userId) {
UserService userService =
syncopeClient.getService(UserService.class);
UserPatch userPatch = new UserPatch();
userPatch.setKey(userId);
userPatch.getMemberships().add(
new
MembershipPatch.Builder().operation(PatchOperation.DELETE).group(groupTO.getKey()).build());
try {
userService.update(userPatch);
} catch (SyncopeClientException e) {
throw new SCIMException(String.format("User %s was not
removed from the group %s", userId, groupTO.getName()), e);
}
}
[2] Stacktrace Sorry for the super long stack :
15:05:47.623 ERROR
org.apache.syncope.core.rest.cxf.RestServiceExceptionMapper -
Exception thrown
java.lang.NullPointerException
at
org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO$2.transform(JPAUserDAO.java:500)
~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO$2.transform(JPAUserDAO.java:496)
~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.commons.collections4.CollectionUtils.collect(CollectionUtils.java:1077)
~[commons-collections4-4.1.jar:4.1]
at
org.apache.commons.collections4.CollectionUtils.collect(CollectionUtils.java:1049)
~[commons-collections4-4.1.jar:4.1]
at
org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO.findAllGroups(JPAUserDAO.java:495)
~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO.findAllResources(JPAUserDAO.java:529)
~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at sun.reflect.GeneratedMethodAccessor124.invoke(Unknown Source)
~[?:?]
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[?:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
at
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:280)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
] at
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE--More--
at
org.apache.syncope.core.persistence.jpa.spring.DomainTransactionInterceptor.invoke(DomainTransactionInterceptor.java:64)
~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at com.sun.proxy.$Proxy172.findAllResources(Unknown Source) ~[?:?]
at
org.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.createTasks(PropagationManagerImpl.java:345)
~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.getUpdateTasks(PropagationManagerImpl.java:275)
~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.getUserUpdateTasks(PropagationManagerImpl.java:204)
~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.getUserUpdateTasks(PropagationManagerImpl.java:224)
~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
_91] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
~[?:1.8.0--More--
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
~[?:1.8.0_91]
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[?:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
at
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:280)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.apache.syncope.core.persistence.jpa.spring.DomainTransactionInterceptor.invoke(DomainTransactionInterceptor.java:64)
~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at com.sun.proxy.$Proxy209.getUserUpdateTasks(Unknown Source) ~[?:?]
at
org.apache.syncope.core.provisioning.java.DefaultUserProvisioningManager.update(DefaultUserProvisioningManager.java:123)
~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.syncope.core.provisioning.java.DefaultUserProvisioningManager.update(DefaultUserProvisioningManager.java:57)
~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.syncope.core.logic.UserLogic.doUpdate(UserLogic.java:232)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.syncope.core.logic.UserLogic.update(UserLogic.java:213)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.apache.syncope.core.logic.UserLogic.update(UserLogic.java:68)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
~[spring-core-4.3.2.RELEASE.jar:4.3.2.RELEASE]0-SNAPSHOT.jar:2.0.0-SNAPSHOT]--More--
at
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.apache.syncope.core.logic.LogicInvocationHandler.around(LogicInvocationHandler.java:72)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at sun.reflect.GeneratedMethodAccessor133.invoke(Unknown Source)
~[?:?]
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[?:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
at
org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:69)
~[spring-security-core-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.apache.syncope.core.logic.UserLogic$$EnhancerBySpringCGLIB$$f6ff0886.update(<generated>)
~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
stractAnyService.java:159)
~[syncope-core-rest-cxf-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]e--
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
~[?:1.8.0_91]
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
~[?:1.8.0_91]
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[?:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
at
org.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:180)
~[cxf-core-3.1.7.jar:3.1.7]
at
org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:96)
~[cxf-core-3.1.7.jar:3.1.7]
at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:189)
~[cxf-rt-frontend-jaxrs-3.1.7.jar:3.1.7]
at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:99)
~[cxf-rt-frontend-jaxrs-3.1.7.jar:3.1.7]
at
org.apache.cxf.interceptor.ServiceInvokerInterceptor$1.run(ServiceInvokerInterceptor.java:59)
~[cxf-core-3.1.7.jar:3.1.7]
at
org.apache.cxf.interceptor.ServiceInvokerInterceptor.handleMessage(ServiceInvokerInterceptor.java:96)
~[cxf-core-3.1.7.jar:3.1.7]
at
org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
~[cxf-core-3.1.7.jar:3.1.7]
at
org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121)
~[cxf-core-3.1.7.jar:3.1.7]
at
org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:254)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
at
org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:234)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
at
org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:208)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
at
org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:160)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
at
org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:180)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
at
org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:299)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
at
org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:276)
~[cxf-rt-transports-http-3.1.7.jar:3.1.7]
at
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
~[catalina.jar:8.0.35]
at
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
~[catalina.jar:8.0.35]
at
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
~[tomcat-websocket.jar:8.0.35]
icationFilterChain.java:240)
~[catalina.jar:8.0.35]erChain.internalDoFilter(Appl--More--
at
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
~[catalina.jar:8.0.35]
at
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.apache.syncope.core.spring.security.MustChangePasswordFilter.doFilter(MustChangePasswordFilter.java:77)
~[syncope-core-spring-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]
at
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
jar:4.1.3.RELEASE]ingframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115)
~[spring-security-web-4.1.3.RELEASE.--More--
at
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
.doFilter(RequestCacheAwareFilter.java:63)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:215)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]http://localhost:9091/groups/fad94db3-9245-449e-994d-b39245449e
------------------------------------------------------------------------
*De :* Francesco Chicchiriccò <ilgro...@apache.org>
*À :* user@syncope.apache.org
*Envoyé le :* Mardi 27 septembre 2016 15h20
*Objet :* Re: SCIM & Syncope : OptimisticLockException on user groups
membership update
On 27/09/2016 15:00, Adrian Gonzalez wrote:
Hello,
We're trying to build a POC on SCIM APIs on top of Syncope.
That's very good to hear: looks it is for SCIM 2.0, correct?
Looking forward to take a look at it!
Problem is when we're using some basic SCIM APIs to update the groups
membership of a given user, we got a OptimisticLockException.
This is due to the fact that SCIM group membership can be updated
only from the Group endpoint (not from the User endpoint).
From https://tools.ietf.org/html/rfc7643#page-24
groups
A list of groups 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".
So, we have the following scenario :
* we have a user1 member of 2 groups (group1 and group2).
* we want to remove the user1 from both groups from a UI console.
* the UI then needs to send 2 HTTP PUT on SCIM /Groups endpoint (one
for each group).
/PUT Groups/group1
/PUT Groups/group2
* we get a OptimisticLockException since both calls are made for a
relation on the same user - because on the SCIM side for the Group
endpoint, we must call
userService.update(userTO) to update a user <-> group relation.
i.e.
MembershipTO membershipTO =
new MembershipTO.Builder().group(userTO.getKey(),
"USER").group(groupTO.getKey(), groupTO.getName()).build();
userTO.getMemberships().add(membershipTO);
try {
userService.update(userTO);
} catch (SyncopeClientException e) {
throw new SCIMException(String.format("User %s was not
added to the group %s", userId, groupTO.getName()), e);
}
Is there an API to update user or group membership without testing
@Version field ? (i.e like a syncope REST API on top of a jpql update ?)
Perhaps we're not using the good API here (is there an API to handle
membership from the group's side ?
Do you see another possible solution (besides updating membership
from the Group side/screen) ?
You are right about the fact that Syncope manages groups memberships
by modifying users.
I am a bit confused about your snippet above, so let me rephrase it a bit.
Assuming your scenario:
* we have a user1 member of 2 groups (group1 and group2).
* we want to remove the user1 from both groups from a UI console.
* the UI then needs to send 2 HTTP PUT on SCIM /Groups endpoint (one
for each group).
/PUT Groups/group1
/PUT Groups/group2
the Group endpoint should perform as following:
UserPatch userPatch = new UserPatch();
userPatch.setKey("the key of user1");
userPatch.getMemberships().add(new MembershipPatch.Builder().
operation(PatchOperation.DELETE).
group("the key of the group passed to the endpoint").
build());
userLogic.update(userPatch, true);
As you can see, I am rather using the logic layer (e.g. UserLogic as
done in UserServiceImpl) to interact with Syncope core, rather than
the external service layer (e.g. how REST clients do).
HTH
Regards.