001/* 002 * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nelson Silva 016 */ 017package org.nuxeo.ecm.platform.oauth2.providers; 018 019import java.io.IOException; 020import java.io.Serializable; 021import java.util.Arrays; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import com.google.api.client.auth.oauth2.TokenResponse; 028import com.google.api.client.http.javanet.NetHttpTransport; 029import com.google.api.client.json.jackson.JacksonFactory; 030import org.apache.commons.lang.StringUtils; 031 032import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; 033import com.google.api.client.auth.oauth2.BearerToken; 034import com.google.api.client.auth.oauth2.ClientParametersAuthentication; 035import com.google.api.client.auth.oauth2.Credential; 036import com.google.api.client.http.GenericUrl; 037import com.google.api.client.http.HttpExecuteInterceptor; 038import com.google.api.client.http.HttpTransport; 039import com.google.api.client.json.JsonFactory; 040 041import org.nuxeo.ecm.core.api.NuxeoException; 042import org.nuxeo.ecm.platform.oauth2.tokens.NuxeoOAuth2Token; 043import org.nuxeo.ecm.platform.oauth2.tokens.OAuth2TokenStore; 044import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 045 046import javax.servlet.http.HttpServletRequest; 047 048public class NuxeoOAuth2ServiceProvider implements OAuth2ServiceProvider { 049 050 public static final String SCHEMA = "oauth2ServiceProvider"; 051 052 /** Global instance of the HTTP transport. */ 053 protected static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); 054 055 /** Global instance of the JSON factory. */ 056 protected static final JsonFactory JSON_FACTORY = new JacksonFactory(); 057 058 public static final String CODE_URL_PARAMETER = "code"; 059 060 public static final String ERROR_URL_PARAMETER = "error"; 061 062 protected String serviceName; 063 064 protected Long id; 065 066 private String tokenServerURL; 067 068 private String authorizationServerURL; 069 070 private String clientId; 071 072 private String clientSecret; 073 074 private List<String> scopes; 075 076 private boolean enabled; 077 078 protected OAuth2ServiceUserStore serviceUserStore; 079 080 protected OAuth2TokenStore tokenStore; 081 082 @Override 083 public String getAuthorizationUrl(HttpServletRequest request) { 084 return getAuthorizationCodeFlow() 085 .newAuthorizationUrl() 086 .setRedirectUri(getCallbackUrl(request)) 087 .build(); 088 } 089 090 protected String getCallbackUrl(HttpServletRequest request) { 091 String serverURL = VirtualHostHelper.getBaseURL(request); 092 093 if (serverURL.endsWith("/")) { 094 serverURL = serverURL.substring(0, serverURL.length() - 1); 095 } 096 097 return serverURL + "/site/oauth2/" + serviceName + "/callback"; 098 } 099 100 @Override 101 public Credential handleAuthorizationCallback(HttpServletRequest request) { 102 103 // Checking if there was an error such as the user denied access 104 String error = getError(request); 105 if (error != null) { 106 throw new NuxeoException("There was an error: \"" + error + "\"."); 107 } 108 109 // Checking conditions on the "code" URL parameter 110 String code = getAuthorizationCode(request); 111 if (code == null) { 112 throw new NuxeoException("There is not code provided as QueryParam."); 113 } 114 115 try { 116 AuthorizationCodeFlow flow = getAuthorizationCodeFlow(); 117 118 String redirectUri = getCallbackUrl(request); 119 120 TokenResponse tokenResponse = flow.newTokenRequest(code) 121 .setScopes(scopes.isEmpty() ? null : scopes) // some providers do not support the 'scopes' param 122 .setRedirectUri(redirectUri).execute(); 123 124 // Create a unique userId to use with the credential store 125 String userId = getOrCreateServiceUser(request, tokenResponse.getAccessToken()); 126 127 return flow.createAndStoreCredential(tokenResponse, userId); 128 } catch (IOException e) { 129 throw new NuxeoException("Failed to retrieve credential", e); 130 } 131 } 132 133 /** 134 * Load a credential from the token store with the userId returned by getServiceUser() as key. 135 */ 136 @Override 137 public Credential loadCredential(String user) { 138 String userId = getServiceUserId(user); 139 try { 140 return userId != null ? getAuthorizationCodeFlow().loadCredential(userId) : null; 141 } catch (IOException e) { 142 throw new NuxeoException("Failed to load credential for " + user, e); 143 } 144 } 145 146 /** 147 * Returns the userId to use for token entries. 148 * Should be overriden by subclasses wanting to rely on a different field as key. 149 */ 150 protected String getServiceUserId(String key) { 151 Map<String, Serializable> filter = new HashMap<>(); 152 filter.put(NuxeoOAuth2Token.KEY_NUXEO_LOGIN, key); 153 return getServiceUserStore().find(filter); 154 } 155 156 /** 157 * Retrieves or creates a service user. 158 * Should be overriden by subclasses wanting to rely on a different field as key. 159 */ 160 protected String getOrCreateServiceUser(HttpServletRequest request, String accessToken) throws IOException { 161 String nuxeoLogin = request.getUserPrincipal().getName(); 162 String userId = getServiceUserId(nuxeoLogin); 163 if (userId == null) { 164 userId = getServiceUserStore().store(nuxeoLogin); 165 } 166 return userId; 167 } 168 169 public AuthorizationCodeFlow getAuthorizationCodeFlow() { 170 Credential.AccessMethod method = BearerToken.authorizationHeaderAccessMethod(); 171 GenericUrl tokenServerUrl = new GenericUrl(tokenServerURL); 172 HttpExecuteInterceptor clientAuthentication = new ClientParametersAuthentication(clientId, clientSecret); 173 String authorizationServerUrl = authorizationServerURL; 174 175 return new AuthorizationCodeFlow.Builder(method, HTTP_TRANSPORT, JSON_FACTORY, tokenServerUrl, 176 clientAuthentication, clientId, authorizationServerUrl) 177 .setScopes(scopes) 178 .setCredentialDataStore(getCredentialDataStore()) 179 .build(); 180 } 181 182 protected OAuth2ServiceUserStore getServiceUserStore() { 183 if (serviceUserStore == null) { 184 serviceUserStore = new OAuth2ServiceUserStore(serviceName); 185 } 186 return serviceUserStore; 187 } 188 189 public OAuth2TokenStore getCredentialDataStore() { 190 if (tokenStore == null) { 191 tokenStore = new OAuth2TokenStore(serviceName); 192 } 193 return tokenStore; 194 } 195 196 protected String getError(HttpServletRequest request) { 197 String error = request.getParameter(ERROR_URL_PARAMETER); 198 return StringUtils.isBlank(error) ? null : error; 199 } 200 201 // Checking conditions on the "code" URL parameter 202 protected String getAuthorizationCode(HttpServletRequest request) { 203 String code = request.getParameter(CODE_URL_PARAMETER); 204 return StringUtils.isBlank(code) ? null : code; 205 } 206 207 @Override 208 public String getServiceName() { 209 return serviceName; 210 } 211 212 @Override 213 public Long getId() { 214 return id; 215 } 216 217 @Override 218 public String getTokenServerURL() { 219 return tokenServerURL; 220 } 221 222 @Override 223 public String getClientId() { 224 return clientId; 225 } 226 227 @Override 228 public String getClientSecret() { 229 return clientSecret; 230 } 231 232 @Override 233 public List<String> getScopes() { 234 return scopes; 235 } 236 237 @Override 238 public String getAuthorizationServerURL() { 239 return authorizationServerURL; 240 } 241 242 @Override 243 public boolean isEnabled() { 244 return enabled; 245 } 246 247 @Override 248 public void setEnabled(Boolean enabled) { 249 this.enabled = enabled; 250 } 251 252 @Override 253 public boolean isProviderAvailable() { 254 return isEnabled() && getClientSecret() != null && getClientId() != null; 255 } 256 257 @Override 258 public void setServiceName(String serviceName) { 259 this.serviceName = serviceName; 260 } 261 262 @Override 263 public void setId(Long id) { 264 this.id = id; 265 } 266 267 @Override 268 public void setTokenServerURL(String tokenServerURL) { 269 this.tokenServerURL = tokenServerURL; 270 } 271 272 @Override 273 public void setAuthorizationServerURL(String authorizationServerURL) { 274 this.authorizationServerURL = authorizationServerURL; 275 } 276 277 @Override 278 public void setClientId(String clientId) { 279 this.clientId = clientId; 280 } 281 282 @Override 283 public void setClientSecret(String clientSecret) { 284 this.clientSecret = clientSecret; 285 } 286 287 @Override 288 public void setScopes(String... scopes) { 289 this.scopes = (scopes == null) ? Collections.emptyList() : Arrays.asList(scopes); 290 } 291}