/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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.apache.usergrid.security.sso; import com.codahale.metrics.Counter; import com.google.inject.Injector; import org.apache.commons.lang.RandomStringUtils; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.apache.usergrid.management.*; import org.apache.usergrid.persistence.core.metrics.MetricsFactory; import org.apache.usergrid.persistence.exceptions.EntityNotFoundException; import org.apache.usergrid.security.tokens.TokenInfo; import org.codehaus.jackson.JsonNode; import org.glassfish.jersey.apache.connector.ApacheClientProperties; import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.jackson.JacksonFeature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.MediaType; import java.util.*; /** * Created by ayeshadastagiri on 6/23/16. */ public class UsergridExternalProvider implements ExternalSSOProvider { private static final Logger logger = LoggerFactory.getLogger(ApigeeSSO2Provider.class); private static final String SSO_PROCESSING_TIME = "sso.processing_time"; private static final String SSO_TOKENS_REJECTED = "sso.tokens_rejected"; private static final String SSO_TOKENS_VALIDATED = "sso.tokens_validated"; public static final String USERGRID_CENTRAL_URL = "usergrid.external.sso.url"; public static final String CENTRAL_CONNECTION_POOL_SIZE = "usergrid.central.connection.pool.size"; public static final String CENTRAL_CONNECTION_TIMEOUT = "usergrid.central.connection.timeout"; public static final String CENTRAL_READ_TIMEOUT = "usergrid.central.read.timeout"; private static final String SSO_CREATED_LOCAL_ADMINS = "sso.created_local_admins"; protected ManagementService management; protected MetricsFactory metricsFactory; protected Properties properties; private static Client jerseyClient = null; @Autowired private Injector injector; @Autowired private ApplicationCreator applicationCreator; @Autowired public void setManagement(ManagementService management) { this.management = management; } @Autowired public void setProperties(Properties properties) { this.properties = properties; } @Autowired public void setMetricFactory() { this.metricsFactory = injector.getInstance(MetricsFactory.class); } MetricsFactory getMetricsFactory() { return metricsFactory; } @Override public TokenInfo validateAndReturnTokenInfo(String token, long ttl) throws Exception { throw new UnsupportedOperationException("Returning user info not supported from external Usergrid SSO tokens"); } @Override public Map<String, Object> getAllTokenDetails(String token, String keyUrl) throws Exception { throw new UnsupportedOperationException("Returning all token details info not supported from external Usergrid SSO tokens"); } @Override public String getExternalSSOUrl() { return properties.getProperty(USERGRID_CENTRAL_URL); } @Override public UserInfo validateAndReturnUserInfo(String token, long ttl) throws Exception { if (token == null) { throw new IllegalArgumentException("ext_access_token must be specified"); } if (ttl == -1) { throw new IllegalArgumentException("ttl must be specified"); } com.codahale.metrics.Timer processingTimer = getMetricsFactory().getTimer( UsergridExternalProvider.class, SSO_PROCESSING_TIME); com.codahale.metrics.Timer.Context timerContext = processingTimer.time(); try { // look up user via UG Central's /management/me endpoint. JsonNode accessInfoNode = getMeFromUgCentral(token); JsonNode userNode = accessInfoNode.get("user"); String username = userNode.get("username").asText(); // if user does not exist locally then we need to fix that UserInfo userInfo = management.getAdminUserByUsername(username); UUID userId = userInfo == null ? null : userInfo.getUuid(); if (userId == null) { // create local user and and organizations they have on the central Usergrid instance logger.info("User {} does not exist locally, creating", username); String name = userNode.get("name").asText(); String email = userNode.get("email").asText(); String dummyPassword = RandomStringUtils.randomAlphanumeric(40); JsonNode orgsNode = userNode.get("organizations"); Iterator<String> fieldNames = orgsNode.getFieldNames(); if (!fieldNames.hasNext()) { // no organizations for user exist in response from central Usergrid SSO // so create user's personal organization and use username as organization name fieldNames = Collections.singletonList(username).iterator(); } // create user and any organizations that user is supposed to have while (fieldNames.hasNext()) { String orgName = fieldNames.next(); if (userId == null) { // // haven't created user yet so do that now OrganizationOwnerInfo ownerOrgInfo = management.createOwnerAndOrganization( orgName, username, name, email, dummyPassword, true, false); applicationCreator.createSampleFor(ownerOrgInfo.getOrganization()); userId = ownerOrgInfo.getOwner().getUuid(); userInfo = ownerOrgInfo.getOwner(); Counter createdAdminsCounter = getMetricsFactory().getCounter( UsergridExternalProvider.class, SSO_CREATED_LOCAL_ADMINS); createdAdminsCounter.inc(); logger.info("Created user {} and org {}", username, orgName); } else { // already created user, so just create an org final OrganizationInfo organization = management.createOrganization(orgName, userInfo, true); applicationCreator.createSampleFor(organization); logger.info("Created user {}'s other org {}", username, orgName); } } } return userInfo; } catch (Exception e) { timerContext.stop(); logger.debug("Error validating external token", e); throw e; } } @Override public Map<String, String> getDecodedTokenDetails(String token) { throw new UnsupportedOperationException("Not currently supported with Usergrid external tokens"); } /** * Look up Admin User via UG Central's /management/me endpoint. * * @param extAccessToken Access token issued by UG Central of Admin User * @return JsonNode representation of AccessInfo object for Admin User * @throws EntityNotFoundException if access_token is not valid. */ private JsonNode getMeFromUgCentral(String extAccessToken) throws EntityNotFoundException { // prepare to count tokens validated and rejected Counter tokensRejectedCounter = getMetricsFactory().getCounter( UsergridExternalProvider.class, SSO_TOKENS_REJECTED); Counter tokensValidatedCounter = getMetricsFactory().getCounter( UsergridExternalProvider.class, SSO_TOKENS_VALIDATED); // create URL of central Usergrid's /management/me endpoint String externalUrl = properties.getProperty(USERGRID_CENTRAL_URL).trim(); // be lenient about trailing slash externalUrl = !externalUrl.endsWith("/") ? externalUrl + "/" : externalUrl; String me = externalUrl + "management/me?access_token=" + extAccessToken; // use our favorite HTTP client to GET /management/me Client client = getJerseyClient(); final org.codehaus.jackson.JsonNode accessInfoNode; try { accessInfoNode = client.target(me).request() .accept(MediaType.APPLICATION_JSON_TYPE) .get(org.codehaus.jackson.JsonNode.class); tokensValidatedCounter.inc(); } catch (Exception e) { // user not found 404 tokensRejectedCounter.inc(); String msg = "Cannot find Admin User associated with " + extAccessToken; throw new EntityNotFoundException(msg, e); } return accessInfoNode; } private Client getJerseyClient() { if (jerseyClient == null) { synchronized (this) { // create HTTPClient and with configured connection pool int poolSize = 100; // connections final String poolSizeStr = properties.getProperty(CENTRAL_CONNECTION_POOL_SIZE); if (poolSizeStr != null) { poolSize = Integer.parseInt(poolSizeStr); } PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(); connectionManager.setMaxTotal(poolSize); int timeout = 20000; // ms final String timeoutStr = properties.getProperty(CENTRAL_CONNECTION_TIMEOUT); if (timeoutStr != null) { timeout = Integer.parseInt(timeoutStr); } int readTimeout = 20000; // ms final String readTimeoutStr = properties.getProperty(CENTRAL_READ_TIMEOUT); if (readTimeoutStr != null) { readTimeout = Integer.parseInt(readTimeoutStr); } ClientConfig clientConfig = new ClientConfig(); clientConfig.register(new JacksonFeature()); clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, connectionManager); clientConfig.connectorProvider(new ApacheConnectorProvider()); jerseyClient = ClientBuilder.newClient(clientConfig); jerseyClient.property(ClientProperties.CONNECT_TIMEOUT, timeout); jerseyClient.property(ClientProperties.READ_TIMEOUT, readTimeout); } } return jerseyClient; } }