/* * Copyright (c) 2014, 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.provisioning.connector.google; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.services.admin.directory.Directory; import com.google.api.services.admin.directory.DirectoryScopes; import com.google.api.services.admin.directory.model.User; import com.google.api.services.admin.directory.model.UserName; import com.google.api.services.admin.directory.model.Users; import org.apache.axiom.util.base64.Base64Utils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.identity.application.common.model.Property; import org.wso2.carbon.identity.core.util.IdentityIOStreamUtils; import org.wso2.carbon.identity.provisioning.AbstractOutboundProvisioningConnector; import org.wso2.carbon.identity.provisioning.IdentityProvisioningConstants; import org.wso2.carbon.identity.provisioning.IdentityProvisioningException; import org.wso2.carbon.identity.provisioning.ProvisionedIdentifier; import org.wso2.carbon.identity.provisioning.ProvisioningEntity; import org.wso2.carbon.identity.provisioning.ProvisioningEntityType; import org.wso2.carbon.identity.provisioning.ProvisioningOperation; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; public class GoogleProvisioningConnector extends AbstractOutboundProvisioningConnector { private static final long serialVersionUID = -6152718786151333233L; private static final Log log = LogFactory.getLog(GoogleProvisioningConnector.class); private static SecureRandom random = new SecureRandom(); private static File googlePrvKey = null; private GoogleProvisioningConnectorConfig configHolder; @Override /** * */ public void init(Property[] provisioningProperties) throws IdentityProvisioningException { Properties configs = new Properties(); if (provisioningProperties != null && provisioningProperties.length > 0) { for (Property property : provisioningProperties) { if (GoogleConnectorConstants.PRIVATE_KEY.equals(property.getName())) { FileOutputStream fos = null; try { byte[] decodedBytes = Base64Utils.decode(property.getValue()); googlePrvKey = new File("googlePrvKey"); fos = new FileOutputStream(googlePrvKey); fos.write(decodedBytes); } catch (IOException e) { log.error("Error while generating private key file object", e); }finally { IdentityIOStreamUtils.flushOutputStream(fos); IdentityIOStreamUtils.closeOutputStream(fos); } } configs.put(property.getName(), property.getValue()); if (IdentityProvisioningConstants.JIT_PROVISIONING_ENABLED.equals(property .getName()) && "1".equals(property.getValue())) { jitProvisioningEnabled = true; } } } configHolder = new GoogleProvisioningConnectorConfig(configs); } @Override public String getClaimDialectUri() throws IdentityProvisioningException { // dialect uri is service provider specific - not governed by the // connector. return null; } @Override public ProvisionedIdentifier provision(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { ProvisionedIdentifier identifier = null; if (provisioningEntity != null) { if (provisioningEntity.isJitProvisioning() && !isJitProvisioningEnabled()) { log.debug("JIT provisioning disabled for Google connector"); return null; } if (provisioningEntity.getEntityType() == ProvisioningEntityType.USER) { if (provisioningEntity.getOperation() == ProvisioningOperation.DELETE) { deleteUser(provisioningEntity); // creates a provisioned identifier for the de-provisioned user. identifier = new ProvisionedIdentifier(); identifier.setIdentifier(null); } else if (provisioningEntity.getOperation() == ProvisioningOperation.POST) { String provisionedId = createUser(provisioningEntity); // creates a provisioned identifier for the provisioned user. identifier = new ProvisionedIdentifier(); identifier.setIdentifier(provisionedId); } else if (provisioningEntity.getOperation() == ProvisioningOperation.PUT) { updateUser(provisioningEntity); } else { log.warn("Unsupported provisioning opertaion for Google Provisioning Connector."); } } else { log.warn("Unsupported provisioning opertaion for Google Provisioning Connector."); } } return identifier; } protected void updateUser(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); if (isDebugEnabled) { log.debug("Triggering update operation for Google Provisioning Connector"); } ProvisionedIdentifier provisionedIdentifier = provisioningEntity.getIdentifier(); if (provisionedIdentifier != null && provisionedIdentifier.getIdentifier() != null) { User updateUser = updateGoogleUser(provisioningEntity); if (updateUser == null) { return; } Directory.Users.Update request; try { request = getDirectoryService().users().update( provisionedIdentifier.getIdentifier(), updateUser); request.execute(); } catch (IOException e) { throw new IdentityProvisioningException("Error while updating Google user : " + provisioningEntity.getEntityName(), e); } if (isDebugEnabled) { log.debug("updating user :" + provisioningEntity.getEntityName() + " with the primaryEmail : " + provisionedIdentifier.getIdentifier()); } } else { throw new IdentityProvisioningException( "Cannot updating Google user, provisionedIdentifier is invalide."); } if (log.isTraceEnabled()) { log.trace("Ending updatingUser() of " + GoogleProvisioningConnector.class); } } protected String createUser(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); if (isDebugEnabled) { log.debug("Triggering create operation for Google Provisioning Connector"); } User createdUser = null; try { User newUser = new User(); newUser = buildGoogleUser(provisioningEntity); Directory.Users.Insert request = getDirectoryService().users().insert(newUser); createdUser = request.execute(); } catch (IOException e) { throw new IdentityProvisioningException("Error while creating user : " + provisioningEntity.getEntityName(), e); } if (isDebugEnabled) { log.debug("Returning created user's email : " + createdUser.getPrimaryEmail()); } if (log.isTraceEnabled()) { log.trace("Ending createUser() of " + GoogleProvisioningConnector.class); } return createdUser.getPrimaryEmail(); } /** * Delete provisioned user from google account * * @param provisioningEntity * @throws IdentityProvisioningException */ protected void deleteUser(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); if (isDebugEnabled) { log.debug("Triggering delete operation for Google Provisioning Connector"); } ProvisionedIdentifier provisionedIdentifier = provisioningEntity.getIdentifier(); if (provisionedIdentifier != null && provisionedIdentifier.getIdentifier() != null) { User deletingUser = new User(); deletingUser.setPrimaryEmail(provisionedIdentifier.getIdentifier()); Directory.Users.Delete request; try { request = getDirectoryService().users().delete( provisionedIdentifier.getIdentifier()); request.execute(); } catch (IOException e) { if (((GoogleJsonResponseException) e).getStatusCode() == 404) { log.warn("Exception while deleting user from google. User may be already deleted from google"); if (log.isDebugEnabled()) { log.debug("Exception while deleting user from google. User may be already deleted from google", e); } } else { throw new IdentityProvisioningException("Error while deleting Google user : " + provisioningEntity.getEntityName(), e); } } if (isDebugEnabled) { log.debug("Deleted user :" + provisioningEntity.getEntityName() + " with the primaryEmail : " + provisionedIdentifier.getIdentifier()); } } else { throw new IdentityProvisioningException( "Cannot delete Google user, provisionedIdentifier is invalide."); } if (log.isTraceEnabled()) { log.trace("Ending deleteUser() of " + GoogleProvisioningConnector.class); } } /** * @return * @throws IdentityProvisioningException */ protected String listUsers(String query) throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); if (isDebugEnabled) { log.debug("Starting listUsers() of " + GoogleProvisioningConnector.class); } StringBuilder sb = new StringBuilder(); List<User> allUsers = new ArrayList<>(); Directory.Users.List request; try { request = getDirectoryService().users().list().setCustomer("my_customer"); // Get all users do { try { Users currentPage = request.execute(); allUsers.addAll(currentPage.getUsers()); request.setPageToken(currentPage.getNextPageToken()); } catch (IOException e) { log.error("Error while retrieving user info, continue to retrieve", e); request.setPageToken(null); } } while (request.getPageToken() != null && request.getPageToken().length() > 0); // Print all users for (User currentUser : allUsers) { sb.append(currentUser.getPrimaryEmail() + "\n"); if (isDebugEnabled) { log.debug("List Google users : " + currentUser.getPrimaryEmail()); } } } catch (IOException e) { throw new IdentityProvisioningException(e); } if (isDebugEnabled) { log.debug("Ending listUsers() of " + GoogleProvisioningConnector.class); } return sb.toString(); } /** * Build and returns a Directory service object authorized with the service accounts that act on * behalf of the given user. * * @return Directory service object that is ready to make requests. * @throws IdentityProvisioningException */ protected Directory getDirectoryService() throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); if (isDebugEnabled) { log.debug("Starting getDirectoryService() of " + GoogleProvisioningConnector.class); } String serviceAccountEmailKey = "google_prov_service_acc_email"; String adminEmailKey = "google_prov_admin_email"; String privateKeyKey = "google_prov_private_key"; String applicationNameKey = "google_prov_application_name"; /** Email of the Service Account */ String serviceAccountId = this.configHolder.getValue(serviceAccountEmailKey); /** Admin email */ String serviceAccountUser = this.configHolder.getValue(adminEmailKey); /** Path to the Service Account's Private Key file */ String serviceAccountPrivateKeyString = this.configHolder.getValue(privateKeyKey); /** Application name */ String applicationName = this.configHolder.getValue(applicationNameKey); HttpTransport httpTransport = new NetHttpTransport(); JacksonFactory jsonFactory = new JacksonFactory(); if (isDebugEnabled) { log.debug("serviceAccountId" + serviceAccountId); log.debug("setServiceAccountScopes" + Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER)); log.debug("setServiceAccountUser" + serviceAccountUser); } Directory service = null; try { GoogleCredential credential = new GoogleCredential.Builder() .setTransport(httpTransport).setJsonFactory(jsonFactory) .setServiceAccountId(serviceAccountId) .setServiceAccountScopes(Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER)) .setServiceAccountUser(serviceAccountUser) .setServiceAccountPrivateKeyFromP12File(googlePrvKey).build(); service = new Directory.Builder(httpTransport, jsonFactory, credential) .setHttpRequestInitializer(credential).setApplicationName(applicationName) .build(); } catch (GeneralSecurityException | IOException e) { throw new IdentityProvisioningException("Error while obtaining connection from google", e); } if (log.isDebugEnabled()) { log.debug("Ending getDirectoryService() of " + GoogleProvisioningConnector.class); } return service; } /** * Buld Google user object to provision * * @param provisioningEntity * @return */ protected User buildGoogleUser(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { User newUser = new User(); UserName username = new UserName(); List<String> wso2IsUsernames = getUserNames(provisioningEntity.getAttributes()); String wso2IsUsername = null; if (CollectionUtils.isNotEmpty(wso2IsUsernames)) { // first element must be the user name. wso2IsUsername = wso2IsUsernames.get(0); } String primaryEmailClaimKey = "google_prov_email_claim_dropdown"; String domainNameKey = "google_prov_domain_name"; String defaultFamilyNameKey = "google_prov_familyname"; String defaultGivenNameKey = "google_prov_givenname"; String familyNameClaimKey = "google_prov_familyname_claim_dropdown"; String givenNameClaimKey = "google_prov_givenname_claim_dropdown"; String provisioningPatternKey = "google_prov_pattern"; String provisioningSeparatorKey = "google_prov_separator"; String idpName_key = "identityProviderName"; String userIdClaimUriKey = "userIdClaimUri"; Map<String, String> requiredAttributes = getSingleValuedClaims(provisioningEntity .getAttributes()); /** Provisioning Pattern */ String provisioningPattern = this.configHolder.getValue(provisioningPatternKey); if (StringUtils.isBlank(provisioningPattern)) { log.info("Provisioning pattern is not defined, hence using default provisioning pattern"); provisioningPattern = GoogleConnectorConstants.PropertyConfig.DEFAULT_PROVISIONING_PATTERN; } String provisioningSeparator = this.configHolder.getValue(provisioningSeparatorKey); if (StringUtils.isBlank(provisioningSeparator)) { log.info("Provisioning separator is not defined, hence using default provisioning separator"); provisioningSeparator = GoogleConnectorConstants.PropertyConfig.DEFAULT_PROVISIONING_SEPERATOR; } String idpName = this.configHolder.getValue(idpName_key); String userIdClaimURL = this.configHolder.getValue(userIdClaimUriKey); String provisioningDomain = this.configHolder.getValue(domainNameKey); String userId = provisioningEntity.getEntityName(); if (StringUtils.isNotBlank(requiredAttributes.get(userIdClaimURL))) { userId = requiredAttributes.get(userIdClaimURL); } String userIdFromPattern = null; if (provisioningPattern != null) { userIdFromPattern = buildUserId(provisioningEntity, provisioningPattern, provisioningSeparator, idpName); } if (StringUtils.isNotBlank(userIdFromPattern)) { userId = userIdFromPattern; } if(StringUtils.isEmpty(userId)){ throw new IdentityProvisioningException("Could not find Provisioning User Identification"); } if (StringUtils.isNotBlank(provisioningDomain) && !userId.endsWith(provisioningDomain)) { userId = userId.replaceAll("@", ".").concat("@").concat(provisioningDomain); } // Set given name String givenNameClaim = this.configHolder.getValue(givenNameClaimKey); String givenNameValue = requiredAttributes.get(givenNameClaim); if (StringUtils.isBlank(givenNameValue)) { String defaultGivenNameValue = this.configHolder.getValue(defaultGivenNameKey); if (StringUtils.isNotBlank(defaultGivenNameValue)) { givenNameValue = defaultGivenNameValue; } else { givenNameValue = wso2IsUsername; } } if (log.isDebugEnabled()) { log.debug("New Google user given name : " + givenNameValue); } username.setGivenName(givenNameValue); // Set family name String familyNameClaim = this.configHolder.getValue(familyNameClaimKey); String familyNameValue = requiredAttributes.get(familyNameClaim); if (StringUtils.isBlank(familyNameValue)) { String defaultFamilyNameValue = this.configHolder.getValue(defaultFamilyNameKey); if (StringUtils.isNotBlank(defaultFamilyNameValue)) { familyNameValue = defaultFamilyNameValue; } else { familyNameValue = wso2IsUsername; } } if (log.isDebugEnabled()) { log.debug("New Google user family name : " + familyNameValue); } username.setFamilyName(familyNameValue); newUser.setName(username); //set primary email if (log.isDebugEnabled()) { log.debug("New Google user primary email : " + userId); } newUser.setPrimaryEmail(userId); if (log.isDebugEnabled()) { try { log.debug("Building Google user : " + newUser.toPrettyString()); } catch (IOException e) { log.debug("Building Google user : " + newUser.toString()); } } newUser.setPassword(generatePassword()); return newUser; } /** * Buld Google user object to provision * * @param provisioningEntity * @return */ protected User updateGoogleUser(ProvisioningEntity provisioningEntity) { User updateUser = new User(); updateUser.setPrimaryEmail(provisioningEntity.getIdentifier().getIdentifier()); UserName username = new UserName(); String defaultFamilyNameKey = "google_prov_familyname"; String defaultGivenNameKey = "google_prov_givenname"; String familyNameClaimKey = "google_prov_familyname_claim_dropdown"; String givenNameClaimKey = "google_prov_givenname_claim_dropdown"; Map<String, String> requiredAttributes = getSingleValuedClaims(provisioningEntity .getAttributes()); if (MapUtils.isEmpty(requiredAttributes)) { return null; } // Set given name String givenNameClaim = this.configHolder.getValue(givenNameClaimKey); String givenNameValue = requiredAttributes.get(givenNameClaim); if (StringUtils.isBlank(givenNameValue)) { String defaultGivenNameValue = this.configHolder.getValue(defaultGivenNameKey); if (StringUtils.isNotBlank(defaultGivenNameValue)) { givenNameValue = defaultGivenNameValue; } } if (log.isDebugEnabled()) { log.debug("New Google user given name : " + givenNameValue); } username.setGivenName(givenNameValue); // Set family name String familyNameClaim = this.configHolder.getValue(familyNameClaimKey); String familyNameValue = requiredAttributes.get(familyNameClaim); if (StringUtils.isBlank(familyNameValue)) { String defaultFamilyNameValue = this.configHolder.getValue(defaultFamilyNameKey); if (StringUtils.isNotBlank(defaultFamilyNameValue)) { familyNameValue = defaultFamilyNameValue; } } if (log.isDebugEnabled()) { log.debug("New Google user family name : " + familyNameValue); } username.setFamilyName(familyNameValue); updateUser.setName(username); updateUser.setPassword(generatePassword()); return updateUser; } /** * Generates (random) password for user to be provisioned * * @return */ protected String generatePassword() { return new BigInteger(130, random).toString(32); } }