/*
* Copyright (c) 2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.identity.scim.common.impl;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.scim.common.utils.BasicAuthUtil;
import org.wso2.carbon.identity.scim.common.utils.IdentitySCIMException;
import org.wso2.carbon.identity.scim.common.utils.SCIMCommonConstants;
import org.wso2.charon.core.client.SCIMClient;
import org.wso2.charon.core.config.SCIMConfigConstants;
import org.wso2.charon.core.config.SCIMProvider;
import org.wso2.charon.core.exceptions.AbstractCharonException;
import org.wso2.charon.core.exceptions.BadRequestException;
import org.wso2.charon.core.exceptions.CharonException;
import org.wso2.charon.core.objects.AbstractSCIMObject;
import org.wso2.charon.core.objects.Group;
import org.wso2.charon.core.objects.ListedResource;
import org.wso2.charon.core.objects.SCIMObject;
import org.wso2.charon.core.objects.User;
import org.wso2.charon.core.schema.SCIMConstants;
import org.wso2.charon.core.util.CopyUtil;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
/**
* This class implements logic to initiate SCIM provisioning operations to other SCIM provider endpoints.
* Since SCIM provisioning operations are usually run asynchronously, this runs in a separate thread.
*/
public class ProvisioningClient implements Runnable {
private static Log logger = LogFactory.getLog(ProvisioningClient.class.getName());
private final String USER_FILTER = "filter=userName%20Eq%20";
private final String GROUP_FILTER = "filter=displayName%20Eq%20";
SCIMObject scimObject;
SCIMProvider provider;
int provisioningMethod;
private int objectType;
private Map<String, Object> additionalProvisioningInformation;
/**
* Initialize parameters to be used in the SCIM User operation which will be invoked by the run operation
* of the thread.
*
* @param scimProvider
* @param user
* @param httpMethod
*/
public ProvisioningClient(SCIMProvider scimProvider, User user, int httpMethod,
Map<String, Object> additionalInformation) {
this.objectType = SCIMConstants.USER_INT;
provider = scimProvider;
scimObject = user;
provisioningMethod = httpMethod;
additionalProvisioningInformation = additionalInformation;
}
/**
* Initialize parameters to be used in the SCIM Group operation which will be invoked by the run operation
* of the thread.
*
* @param scimProvider
* @param group
* @param httpMethod
*/
public ProvisioningClient(SCIMProvider scimProvider, Group group, int httpMethod,
Map<String, Object> additionalInformation) {
this.objectType = SCIMConstants.GROUP_INT;
provider = scimProvider;
scimObject = group;
provisioningMethod = httpMethod;
additionalProvisioningInformation = additionalInformation;
}
/**
* Provision the SCIM User Object passed to the provisioning client in the constructor, to the
* SCIM Provider whose details are also sent at the initialization.
*/
public void provisionCreateUser() throws IdentitySCIMException {
String userName = null;
try {
//encode payload using SCIMClient API.
SCIMClient scimClient = new SCIMClient();
//get provider details
String userEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USER_ENDPOINT);
userName = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USERNAME);
String password = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_PASSWORD);
String contentType = provider.getProperty(SCIMConstants.CONTENT_TYPE_HEADER);
if (contentType == null) {
contentType = SCIMConstants.APPLICATION_JSON;
}
String encodedUser = scimClient.encodeSCIMObject((AbstractSCIMObject) scimObject,
SCIMConstants.identifyFormat(contentType));
if (logger.isDebugEnabled()) {
logger.debug("User to provision : useName" + userName);
}
PostMethod postMethod = new PostMethod(userEPURL);
//add basic auth header
postMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
//create request entity with the payload.
RequestEntity requestEntity = new StringRequestEntity(encodedUser,
contentType, null);
postMethod.setRequestEntity(requestEntity);
//create http client
HttpClient httpClient = new HttpClient();
//send the request
int responseStatus = httpClient.executeMethod(postMethod);
logger.info("SCIM - create user operation returned with response code: " + responseStatus);
String response = postMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("Create User Response: " + response);
}
if (scimClient.evaluateResponseStatus(responseStatus)) {
//try to decode the scim object to verify that it gets decoded without issue.
scimClient.decodeSCIMResponse(response, SCIMConstants.identifyFormat(contentType),
objectType);
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(response, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} catch (CharonException e) {
throw new IdentitySCIMException(
"Error in encoding the object to be provisioned for user with id: " + userName, e);
} catch (UnsupportedEncodingException e) {
throw new IdentitySCIMException("Error in creating request for provisioning the user with id: " + userName,
e);
} catch (IOException | BadRequestException e) {
throw new IdentitySCIMException(
"Error in invoking provisioning operation for the user with id: " + userName, e);
}
}
public void provisionDeleteUser() throws IdentitySCIMException {
String userName = null;
try {
//get provider details
String userEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USER_ENDPOINT);
userName = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USERNAME);
String password = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_PASSWORD);
String contentType = provider.getProperty(SCIMConstants.CONTENT_TYPE_HEADER);
if (contentType == null) {
contentType = SCIMConstants.APPLICATION_JSON;
}
/*get the userId of the user being provisioned from the particular provider by filtering
with user name*/
GetMethod getMethod = new GetMethod(userEPURL);
//add filter query parameter
getMethod.setQueryString(USER_FILTER + ((User) scimObject).getUserName());
//add authorization headers
getMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
//create http client
HttpClient httpFilterClient = new HttpClient();
//send the request
int responseStatus = httpFilterClient.executeMethod(getMethod);
String response = getMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("SCIM - filter operation inside 'delete user' provisioning " +
"returned with response code: " + responseStatus);
logger.debug("Filter User Response: " + response);
}
SCIMClient scimClient = new SCIMClient();
if (scimClient.evaluateResponseStatus(responseStatus)) {
//decode the response to extract the userId.
ListedResource listedResource = scimClient.decodeSCIMResponseWithListedResource(
response, SCIMConstants.identifyFormat(contentType), objectType);
List<SCIMObject> users = listedResource.getScimObjects();
String userId = null;
//we expect only one user in the list
for (SCIMObject user : users) {
userId = ((User) user).getId();
}
String url = userEPURL + "/" + userId;
//now send the delete request.
DeleteMethod deleteMethod = new DeleteMethod(url);
deleteMethod.addRequestHeader(
SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
HttpClient httpDeleteClient = new HttpClient();
int deleteResponseStatus = httpDeleteClient.executeMethod(deleteMethod);
String deleteResponse = deleteMethod.getResponseBodyAsString();
logger.info("SCIM - delete user operation returned with response code: " +
deleteResponseStatus);
if (!scimClient.evaluateResponseStatus(deleteResponseStatus)) {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
deleteResponse, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
response, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} catch (CharonException | IOException | BadRequestException e) {
throw new IdentitySCIMException("Error in provisioning 'delete user' operation for user :" + userName, e);
}
}
public void provisionUpdateUser() throws IdentitySCIMException {
String userName = null;
try {
//get provider details
String userEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USER_ENDPOINT);
userName = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USERNAME);
String password = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_PASSWORD);
String contentType = provider.getProperty(SCIMConstants.CONTENT_TYPE_HEADER);
if (contentType == null) {
contentType = SCIMConstants.APPLICATION_JSON;
}
/*get the userId of the user being provisioned from the particular provider by filtering
with user name*/
GetMethod getMethod = new GetMethod(userEPURL);
//add filter query parameter
getMethod.setQueryString(USER_FILTER + ((User) scimObject).getUserName());
//add authorization headers
getMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
//create http client
HttpClient httpFilterClient = new HttpClient();
//send the request
int responseStatus = httpFilterClient.executeMethod(getMethod);
String response = getMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("SCIM - filter operation inside 'delete user' provisioning " +
"returned with response code: " + responseStatus);
logger.debug("Filter User Response: " + response);
}
SCIMClient scimClient = new SCIMClient();
if (scimClient.evaluateResponseStatus(responseStatus)) {
//decode the response to extract the userId.
ListedResource listedResource = scimClient.decodeSCIMResponseWithListedResource(
response, SCIMConstants.identifyFormat(contentType), objectType);
List<SCIMObject> users = listedResource.getScimObjects();
String userId = null;
//we expect only one user in the list
for (SCIMObject user : users) {
userId = ((User) user).getId();
if (userId == null) {
logger.error("Trying to update a user entry which doesn't support SCIM. " +
"Usually internal carbon User entries such as admin role doesn't support SCIM attributes.");
return;
}
}
String url = userEPURL + "/" + userId;
//now send the update request.
PutMethod putMethod = new PutMethod(url);
putMethod.addRequestHeader(
SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
String encodedUser = scimClient.encodeSCIMObject(
(AbstractSCIMObject) scimObject, SCIMConstants.identifyFormat(contentType));
RequestEntity putRequestEntity = new StringRequestEntity(
encodedUser, contentType, null);
putMethod.setRequestEntity(putRequestEntity);
HttpClient httpUpdateClient = new HttpClient();
int updateResponseStatus = httpUpdateClient.executeMethod(putMethod);
String updateResponse = putMethod.getResponseBodyAsString();
logger.info("SCIM - update user operation returned with response code: " +
updateResponseStatus);
if (!scimClient.evaluateResponseStatus(updateResponseStatus)) {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
updateResponse, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
response, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} catch (CharonException | IOException | BadRequestException e) {
throw new IdentitySCIMException("Error in provisioning 'update user' operation for user :" + userName);
}
}
public void provisionPatchUser() throws IdentitySCIMException {
String userName = null;
try {
//get provider details
String userEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USER_ENDPOINT);
userName = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USERNAME);
String password = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_PASSWORD);
String contentType = provider.getProperty(SCIMConstants.CONTENT_TYPE_HEADER);
if (StringUtils.isEmpty(contentType)) {
contentType = SCIMConstants.APPLICATION_JSON;
}
/*get the userId of the user being provisioned from the particular provider by filtering
with user name*/
GetMethod getMethod = new GetMethod(userEPURL);
//add filter query parameter
getMethod.setQueryString(USER_FILTER + ((User) scimObject).getUserName());
//add authorization headers
getMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
//create http client
HttpClient httpFilterClient = new HttpClient();
//send the request
int responseStatus = httpFilterClient.executeMethod(getMethod);
String response = getMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("SCIM - filter operation inside 'delete user' provisioning " +
"returned with response code: " + responseStatus);
logger.debug("Filter User Response: " + response);
}
SCIMClient scimClient = new SCIMClient();
if (scimClient.evaluateResponseStatus(responseStatus)) {
//decode the response to extract the userId.
ListedResource listedResource = scimClient.decodeSCIMResponseWithListedResource(
response, SCIMConstants.identifyFormat(contentType), objectType);
List<SCIMObject> users = listedResource.getScimObjects();
String userId = null;
//we expect only one user in the list
for (SCIMObject user : users) {
userId = ((User) user).getId();
if (StringUtils.isEmpty(userId)) {
logger.error("Trying to update a user entry which doesn't support SCIM. " +
"Usually internal carbon User entries such as admin role doesn't support SCIM attributes.");
return;
}
}
String url = userEPURL + "/" + userId;
PostMethod patchMethod = new PostMethod(url) {
@Override
public String getName() {
return "PATCH";
}
};
patchMethod.addRequestHeader(
SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
String encodedUser = scimClient.encodeSCIMObject(
(AbstractSCIMObject) scimObject, SCIMConstants.identifyFormat(contentType));
RequestEntity putRequestEntity = new StringRequestEntity(
encodedUser, contentType, null);
patchMethod.setRequestEntity(putRequestEntity);
HttpClient httpUpdateClient = new HttpClient();
int updateResponseStatus = httpUpdateClient.executeMethod(patchMethod);
String updateResponse = patchMethod.getResponseBodyAsString();
logger.info("SCIM - update user operation returned with response code: " +
updateResponseStatus);
if (!scimClient.evaluateResponseStatus(updateResponseStatus)) {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
updateResponse, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
response, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} catch (CharonException | IOException | BadRequestException e) {
throw new IdentitySCIMException("Error in provisioning 'update user' operation for user :" + userName, e);
}
}
public void provisionCreateGroup() throws IdentitySCIMException {
String userName = null;
try {
//get provider details
String userEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USER_ENDPOINT);
String groupEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_GROUP_ENDPOINT);
userName = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USERNAME);
String password = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_PASSWORD);
String contentType = provider.getProperty(SCIMConstants.CONTENT_TYPE_HEADER);
if (contentType == null) {
contentType = SCIMConstants.APPLICATION_JSON;
}
SCIMClient scimClient = new SCIMClient();
//get list of users in the group, if any, by userNames
List<String> users = ((Group) scimObject).getMembersWithDisplayName();
Group copiedGroup = null;
if (CollectionUtils.isNotEmpty(users)) {
//create a deep copy of the group since we are going to update the member ids
copiedGroup = (Group) CopyUtil.deepCopy(scimObject);
//delete existing members in the group since we are going to update it with
copiedGroup.deleteAttribute(SCIMConstants.GroupSchemaConstants.MEMBERS);
//create http client
HttpClient httpFilterUserClient = new HttpClient();
//create get method for filtering
GetMethod getMethod = new GetMethod(userEPURL);
getMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(
userName, password));
//get corresponding userIds
for (String user : users) {
String filter = USER_FILTER + user;
getMethod.setQueryString(filter);
int responseCode = httpFilterUserClient.executeMethod(getMethod);
String response = getMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("SCIM - 'filter user' operation inside 'create group' provisioning " +
"returned with response code: " + responseCode);
logger.debug("Filter User Response: " + response);
}
//check for success of the response
if (scimClient.evaluateResponseStatus(responseCode)) {
ListedResource listedUserResource =
scimClient.decodeSCIMResponseWithListedResource(
response, SCIMConstants.identifyFormat(contentType),
SCIMConstants.USER_INT);
List<SCIMObject> filteredUsers = listedUserResource.getScimObjects();
String userId = null;
for (SCIMObject filteredUser : filteredUsers) {
//we expect only one result here
userId = ((User) filteredUser).getId();
}
copiedGroup.setGroupMember(userId, user);
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
response, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
}
}
//provision create group operation
HttpClient httpCreateGroupClient = new HttpClient();
PostMethod postMethod = new PostMethod(groupEPURL);
//add basic auth header
postMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
//encode group
String encodedGroup = null;
if (copiedGroup != null) {
encodedGroup = scimClient.encodeSCIMObject(copiedGroup,
SCIMConstants.identifyFormat(contentType));
} else {
encodedGroup = scimClient.encodeSCIMObject((AbstractSCIMObject) scimObject,
SCIMConstants.identifyFormat(contentType));
}
//create request entity with the payload.
RequestEntity requestEntity = new StringRequestEntity(encodedGroup, contentType, null);
postMethod.setRequestEntity(requestEntity);
//send the request
int responseStatus = httpCreateGroupClient.executeMethod(postMethod);
logger.info("SCIM - create group operation returned with response code: " + responseStatus);
String postResponse = postMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("Create Group Response: " + postResponse);
}
if (scimClient.evaluateResponseStatus(responseStatus)) {
//try to decode the scim object to verify that it gets decoded without issue.
scimClient.decodeSCIMResponse(postResponse, SCIMConstants.JSON,
objectType);
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(postResponse, SCIMConstants.JSON);
logger.error(exception.getDescription());
}
} catch (BadRequestException | IOException | CharonException e) {
throw new IdentitySCIMException("Error in provisioning 'create group' operation for user :" + userName, e);
}
}
public void provisionDeleteGroup() throws IdentitySCIMException {
String userName = null;
try {
//get provider details
String groupEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_GROUP_ENDPOINT);
userName = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USERNAME);
String password = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_PASSWORD);
String contentType = provider.getProperty(SCIMConstants.CONTENT_TYPE_HEADER);
if (contentType == null) {
contentType = SCIMConstants.APPLICATION_JSON;
}
/*get the groupId of the group being provisioned from the particular provider by filtering
with display name*/
GetMethod getMethod = new GetMethod(groupEPURL);
//add filter query parameter
getMethod.setQueryString(GROUP_FILTER + ((Group) scimObject).getDisplayName());
//add authorization headers
getMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
//create http client
HttpClient httpFilterClient = new HttpClient();
//send the request
int responseStatus = httpFilterClient.executeMethod(getMethod);
String response = getMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("SCIM - filter operation inside 'delete group' provisioning " +
"returned with response code: " + responseStatus);
logger.debug("Filter Group Response: " + response);
}
SCIMClient scimClient = new SCIMClient();
if (scimClient.evaluateResponseStatus(responseStatus)) {
//decode the response to extract the groupId.
ListedResource listedResource = scimClient.decodeSCIMResponseWithListedResource(
response, SCIMConstants.identifyFormat(contentType), objectType);
List<SCIMObject> groups = listedResource.getScimObjects();
String groupId = null;
//we expect only one user in the list
for (SCIMObject group : groups) {
groupId = ((Group) group).getId();
}
String url = groupEPURL + "/" + groupId;
//now send the delete request.
DeleteMethod deleteMethod = new DeleteMethod(url);
deleteMethod.addRequestHeader(
SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
HttpClient httpDeleteClient = new HttpClient();
int deleteResponseStatus = httpDeleteClient.executeMethod(deleteMethod);
String deleteResponse = deleteMethod.getResponseBodyAsString();
logger.info("SCIM - delete group operation returned with response code: " +
deleteResponseStatus);
if (!scimClient.evaluateResponseStatus(deleteResponseStatus)) {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
deleteResponse, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
response, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} catch (CharonException | IOException | BadRequestException e) {
throw new IdentitySCIMException("Error in provisioning 'delete group' operation for user :" + userName, e);
}
}
public void provisionUpdateGroup() throws IdentitySCIMException {
String userName = null;
try {
//get provider details
String groupEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_GROUP_ENDPOINT);
String userEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USER_ENDPOINT);
userName = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USERNAME);
String password = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_PASSWORD);
String contentType = provider.getProperty(SCIMConstants.CONTENT_TYPE_HEADER);
if (contentType == null) {
contentType = SCIMConstants.APPLICATION_JSON;
}
/*get the groupId of the group being provisioned from the particular provider by filtering
with display name*/
GetMethod getMethod = new GetMethod(groupEPURL);
//add filter query parameter
//check if role name is updated
if (additionalProvisioningInformation != null && (Boolean) additionalProvisioningInformation.get(
SCIMCommonConstants.IS_ROLE_NAME_CHANGED_ON_UPDATE)) {
getMethod.setQueryString(
GROUP_FILTER + additionalProvisioningInformation.get(SCIMCommonConstants.OLD_GROUP_NAME));
} else {
getMethod.setQueryString(GROUP_FILTER + ((Group) scimObject).getDisplayName());
}
//add authorization headers
getMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
//create http client
HttpClient httpFilterClient = new HttpClient();
//send the request
int responseStatus = httpFilterClient.executeMethod(getMethod);
String response = getMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("SCIM - filter operation inside 'update group' provisioning " +
"returned with response code: " + responseStatus);
logger.debug("Filter Group Response: " + response);
}
SCIMClient scimClient = new SCIMClient();
if (scimClient.evaluateResponseStatus(responseStatus)) {
//decode the response to extract the groupId.
ListedResource listedResource = scimClient.decodeSCIMResponseWithListedResource(
response, SCIMConstants.identifyFormat(contentType), objectType);
List<SCIMObject> groups = listedResource.getScimObjects();
String groupId = null;
//we expect only one user in the list
for (SCIMObject group : groups) {
groupId = ((Group) group).getId();
}
String url = groupEPURL + "/" + groupId;
//now start sending the update request.
//get list of users in the group, if any, by userNames
List<String> users = ((Group) scimObject).getMembersWithDisplayName();
Group copiedGroup = null;
if (CollectionUtils.isNotEmpty(users)) {
//create a deep copy of the group since we are going to update the member ids
copiedGroup = (Group) CopyUtil.deepCopy(scimObject);
//delete existing members in the group since we are going to update it with
copiedGroup.deleteAttribute(SCIMConstants.GroupSchemaConstants.MEMBERS);
//create http client
HttpClient httpFilterUserClient = new HttpClient();
//create get method for filtering
GetMethod getUserMethod = new GetMethod(userEPURL);
getUserMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(
userName, password));
//get corresponding userIds
for (String user : users) {
String filter = USER_FILTER + user;
getUserMethod.setQueryString(filter);
int responseCode = httpFilterUserClient.executeMethod(getUserMethod);
String filterUserResponse = getUserMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("SCIM - 'filter user' operation inside 'update group' provisioning " +
"returned with response code: " + responseCode);
logger.debug("Filter User Response: " + filterUserResponse);
}
//check for success of the response
if (scimClient.evaluateResponseStatus(responseCode)) {
ListedResource listedUserResource =
scimClient.decodeSCIMResponseWithListedResource(
filterUserResponse, SCIMConstants.identifyFormat(contentType),
SCIMConstants.USER_INT);
List<SCIMObject> filteredUsers = listedUserResource.getScimObjects();
String userId = null;
for (SCIMObject filteredUser : filteredUsers) {
//we expect only one result here
userId = ((User) filteredUser).getId();
}
copiedGroup.setGroupMember(userId, user);
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
filterUserResponse, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
}
}
//now send the update request.
PutMethod putMethod = new PutMethod(url);
putMethod.addRequestHeader(
SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
String encodedGroup = scimClient.encodeSCIMObject(
(AbstractSCIMObject) scimObject, SCIMConstants.identifyFormat(contentType));
RequestEntity putRequestEntity = new StringRequestEntity(
encodedGroup, contentType, null);
putMethod.setRequestEntity(putRequestEntity);
HttpClient httpUpdateClient = new HttpClient();
int updateResponseStatus = httpUpdateClient.executeMethod(putMethod);
String updateResponse = putMethod.getResponseBodyAsString();
logger.info("SCIM - update group operation returned with response code: " +
updateResponseStatus);
if (!scimClient.evaluateResponseStatus(updateResponseStatus)) {
//decode scim exception and extract the specific error message.
AbstractCharonException exception = scimClient.decodeSCIMException(
updateResponse, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
response, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} catch (CharonException | IOException | BadRequestException e) {
throw new IdentitySCIMException("Error in provisioning 'delete group' operation for user :" + userName, e);
}
}
public void provisionPatchGroup() throws IdentitySCIMException {
String userName = null;
try {
//get provider details
String groupEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_GROUP_ENDPOINT);
String userEPURL = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USER_ENDPOINT);
userName = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_USERNAME);
String password = provider.getProperty(SCIMConfigConstants.ELEMENT_NAME_PASSWORD);
String contentType = provider.getProperty(SCIMConstants.CONTENT_TYPE_HEADER);
if (contentType == null) {
contentType = SCIMConstants.APPLICATION_JSON;
}
/*get the groupId of the group being provisioned from the particular provider by filtering
with display name*/
GetMethod getMethod = new GetMethod(groupEPURL);
//add filter query parameter
//check if role name is updated
if (additionalProvisioningInformation != null && (Boolean) additionalProvisioningInformation.get(
SCIMCommonConstants.IS_ROLE_NAME_CHANGED_ON_UPDATE)) {
getMethod.setQueryString(
GROUP_FILTER + additionalProvisioningInformation.get(SCIMCommonConstants.OLD_GROUP_NAME));
} else {
getMethod.setQueryString(GROUP_FILTER + ((Group) scimObject).getDisplayName());
}
//add authorization headers
getMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
//create http client
HttpClient httpFilterClient = new HttpClient();
//send the request
int responseStatus = httpFilterClient.executeMethod(getMethod);
String response = getMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("SCIM - filter operation inside 'update group' provisioning " +
"returned with response code: " + responseStatus);
logger.debug("Filter Group Response: " + response);
}
SCIMClient scimClient = new SCIMClient();
if (scimClient.evaluateResponseStatus(responseStatus)) {
//decode the response to extract the groupId.
ListedResource listedResource = scimClient.decodeSCIMResponseWithListedResource(
response, SCIMConstants.identifyFormat(contentType), objectType);
List<SCIMObject> groups = listedResource.getScimObjects();
String groupId = null;
//we expect only one user in the list
for (SCIMObject group : groups) {
groupId = ((Group) group).getId();
}
String url = groupEPURL + "/" + groupId;
//now start sending the update request.
//get list of users in the group, if any, by userNames
List<String> users = ((Group) scimObject).getMembersWithDisplayName();
Group copiedGroup = null;
if (CollectionUtils.isNotEmpty(users)) {
//create a deep copy of the group since we are going to update the member ids
copiedGroup = (Group) CopyUtil.deepCopy(scimObject);
//delete existing members in the group since we are going to update it with
copiedGroup.deleteAttribute(SCIMConstants.GroupSchemaConstants.MEMBERS);
//create http client
HttpClient httpFilterUserClient = new HttpClient();
//create get method for filtering
GetMethod getUserMethod = new GetMethod(userEPURL);
getUserMethod.addRequestHeader(SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(
userName, password));
//get corresponding userIds
for (String user : users) {
String filter = USER_FILTER + user;
getUserMethod.setQueryString(filter);
int responseCode = httpFilterUserClient.executeMethod(getUserMethod);
String filterUserResponse = getUserMethod.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("SCIM - 'filter user' operation inside 'update group' provisioning " +
"returned with response code: " + responseCode);
logger.debug("Filter User Response: " + filterUserResponse);
}
//check for success of the response
if (scimClient.evaluateResponseStatus(responseCode)) {
ListedResource listedUserResource =
scimClient.decodeSCIMResponseWithListedResource(
filterUserResponse, SCIMConstants.identifyFormat(contentType),
SCIMConstants.USER_INT);
List<SCIMObject> filteredUsers = listedUserResource.getScimObjects();
String userId = null;
for (SCIMObject filteredUser : filteredUsers) {
//we expect only one result here
userId = ((User) filteredUser).getId();
}
copiedGroup.setGroupMember(userId, user);
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
filterUserResponse, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
}
}
//now send the update request.
PostMethod patchMethod = new PostMethod(url){
@Override
public String getName() {
return "PATCH";
}
};
patchMethod.addRequestHeader(
SCIMConstants.AUTHORIZATION_HEADER,
BasicAuthUtil.getBase64EncodedBasicAuthHeader(userName, password));
String encodedGroup = scimClient.encodeSCIMObject(
(AbstractSCIMObject) scimObject, SCIMConstants.identifyFormat(contentType));
RequestEntity putRequestEntity = new StringRequestEntity(
encodedGroup, contentType, null);
patchMethod.setRequestEntity(putRequestEntity);
HttpClient httpUpdateClient = new HttpClient();
int updateResponseStatus = httpUpdateClient.executeMethod(patchMethod);
String updateResponse = patchMethod.getResponseBodyAsString();
logger.info("SCIM - update group operation returned with response code: " +
updateResponseStatus);
if (!scimClient.evaluateResponseStatus(updateResponseStatus)) {
//decode scim exception and extract the specific error message.
AbstractCharonException exception = scimClient.decodeSCIMException(
updateResponse, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} else {
//decode scim exception and extract the specific error message.
AbstractCharonException exception =
scimClient.decodeSCIMException(
response, SCIMConstants.identifyFormat(contentType));
logger.error(exception.getDescription());
}
} catch (CharonException | IOException | BadRequestException e) {
throw new IdentitySCIMException("Error in provisioning 'delete group' operation for user : " + userName, e);
}
}
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p/>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
try {
if (SCIMConstants.USER_INT == objectType) {
switch (provisioningMethod) {
case SCIMConstants.DELETE:
provisionDeleteUser();
break;
case SCIMConstants.POST:
provisionCreateUser();
break;
case SCIMConstants.PUT:
provisionUpdateUser();
break;
default:
break;
}
} else if (SCIMConstants.GROUP_INT == objectType) {
switch (provisioningMethod) {
case SCIMConstants.DELETE:
provisionDeleteGroup();
break;
case SCIMConstants.POST:
provisionCreateGroup();
break;
case SCIMConstants.PUT:
provisionUpdateGroup();
break;
default:
break;
}
}
} catch (IdentitySCIMException e) {
logger.error("Error occurred while user provisioning", e);
}
}
}