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