/* * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed 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. * * Contributors: * Nelson Silva */ package org.nuxeo.ecm.platform.oauth2.providers; import java.io.IOException; import java.io.Serializable; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.jackson.JacksonFactory; import org.apache.commons.lang.StringUtils; import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; import com.google.api.client.auth.oauth2.BearerToken; import com.google.api.client.auth.oauth2.ClientParametersAuthentication; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpExecuteInterceptor; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.JsonFactory; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.platform.oauth2.tokens.NuxeoOAuth2Token; import org.nuxeo.ecm.platform.oauth2.tokens.OAuth2TokenStore; import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; import javax.servlet.http.HttpServletRequest; public class NuxeoOAuth2ServiceProvider implements OAuth2ServiceProvider { public static final String SCHEMA = "oauth2ServiceProvider"; /** Global instance of the HTTP transport. */ protected static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); /** Global instance of the JSON factory. */ protected static final JsonFactory JSON_FACTORY = new JacksonFactory(); public static final String CODE_URL_PARAMETER = "code"; public static final String ERROR_URL_PARAMETER = "error"; protected String serviceName; protected Long id; private String tokenServerURL; private String authorizationServerURL; private String clientId; private String clientSecret; private List<String> scopes; private boolean enabled; protected OAuth2ServiceUserStore serviceUserStore; protected OAuth2TokenStore tokenStore; @Override public String getAuthorizationUrl(HttpServletRequest request) { return getAuthorizationCodeFlow() .newAuthorizationUrl() .setRedirectUri(getCallbackUrl(request)) .build(); } protected String getCallbackUrl(HttpServletRequest request) { String serverURL = VirtualHostHelper.getBaseURL(request); if (serverURL.endsWith("/")) { serverURL = serverURL.substring(0, serverURL.length() - 1); } return serverURL + "/site/oauth2/" + serviceName + "/callback"; } @Override public Credential handleAuthorizationCallback(HttpServletRequest request) { // Checking if there was an error such as the user denied access String error = getError(request); if (error != null) { throw new NuxeoException("There was an error: \"" + error + "\"."); } // Checking conditions on the "code" URL parameter String code = getAuthorizationCode(request); if (code == null) { throw new NuxeoException("There is not code provided as QueryParam."); } try { AuthorizationCodeFlow flow = getAuthorizationCodeFlow(); String redirectUri = getCallbackUrl(request); TokenResponse tokenResponse = flow.newTokenRequest(code) .setScopes(scopes.isEmpty() ? null : scopes) // some providers do not support the 'scopes' param .setRedirectUri(redirectUri).execute(); // Create a unique userId to use with the credential store String userId = getOrCreateServiceUser(request, tokenResponse.getAccessToken()); return flow.createAndStoreCredential(tokenResponse, userId); } catch (IOException e) { throw new NuxeoException("Failed to retrieve credential", e); } } /** * Load a credential from the token store with the userId returned by getServiceUser() as key. */ @Override public Credential loadCredential(String user) { String userId = getServiceUserId(user); try { return userId != null ? getAuthorizationCodeFlow().loadCredential(userId) : null; } catch (IOException e) { throw new NuxeoException("Failed to load credential for " + user, e); } } /** * Returns the userId to use for token entries. * Should be overriden by subclasses wanting to rely on a different field as key. */ protected String getServiceUserId(String key) { Map<String, Serializable> filter = new HashMap<>(); filter.put(NuxeoOAuth2Token.KEY_NUXEO_LOGIN, key); return getServiceUserStore().find(filter); } /** * Retrieves or creates a service user. * Should be overriden by subclasses wanting to rely on a different field as key. */ protected String getOrCreateServiceUser(HttpServletRequest request, String accessToken) throws IOException { String nuxeoLogin = request.getUserPrincipal().getName(); String userId = getServiceUserId(nuxeoLogin); if (userId == null) { userId = getServiceUserStore().store(nuxeoLogin); } return userId; } public AuthorizationCodeFlow getAuthorizationCodeFlow() { Credential.AccessMethod method = BearerToken.authorizationHeaderAccessMethod(); GenericUrl tokenServerUrl = new GenericUrl(tokenServerURL); HttpExecuteInterceptor clientAuthentication = new ClientParametersAuthentication(clientId, clientSecret); String authorizationServerUrl = authorizationServerURL; return new AuthorizationCodeFlow.Builder(method, HTTP_TRANSPORT, JSON_FACTORY, tokenServerUrl, clientAuthentication, clientId, authorizationServerUrl) .setScopes(scopes) .setCredentialDataStore(getCredentialDataStore()) .build(); } protected OAuth2ServiceUserStore getServiceUserStore() { if (serviceUserStore == null) { serviceUserStore = new OAuth2ServiceUserStore(serviceName); } return serviceUserStore; } public OAuth2TokenStore getCredentialDataStore() { if (tokenStore == null) { tokenStore = new OAuth2TokenStore(serviceName); } return tokenStore; } protected String getError(HttpServletRequest request) { String error = request.getParameter(ERROR_URL_PARAMETER); return StringUtils.isBlank(error) ? null : error; } // Checking conditions on the "code" URL parameter protected String getAuthorizationCode(HttpServletRequest request) { String code = request.getParameter(CODE_URL_PARAMETER); return StringUtils.isBlank(code) ? null : code; } @Override public String getServiceName() { return serviceName; } @Override public Long getId() { return id; } @Override public String getTokenServerURL() { return tokenServerURL; } @Override public String getClientId() { return clientId; } @Override public String getClientSecret() { return clientSecret; } @Override public List<String> getScopes() { return scopes; } @Override public String getAuthorizationServerURL() { return authorizationServerURL; } @Override public boolean isEnabled() { return enabled; } @Override public void setEnabled(Boolean enabled) { this.enabled = enabled; } @Override public boolean isProviderAvailable() { return isEnabled() && getClientSecret() != null && getClientId() != null; } @Override public void setServiceName(String serviceName) { this.serviceName = serviceName; } @Override public void setId(Long id) { this.id = id; } @Override public void setTokenServerURL(String tokenServerURL) { this.tokenServerURL = tokenServerURL; } @Override public void setAuthorizationServerURL(String authorizationServerURL) { this.authorizationServerURL = authorizationServerURL; } @Override public void setClientId(String clientId) { this.clientId = clientId; } @Override public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; } @Override public void setScopes(String... scopes) { this.scopes = (scopes == null) ? Collections.emptyList() : Arrays.asList(scopes); } }