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 protected String description; 069 070 private String tokenServerURL; 071 072 private String authorizationServerURL; 073 074 protected String userAuthorizationURL; 075 076 private String clientId; 077 078 private String clientSecret; 079 080 private List<String> scopes; 081 082 private boolean enabled; 083 084 protected OAuth2ServiceUserStore serviceUserStore; 085 086 protected OAuth2TokenStore tokenStore; 087 088 @Override 089 public String getAuthorizationUrl(HttpServletRequest request) { 090 return getAuthorizationCodeFlow() 091 .newAuthorizationUrl() 092 .setRedirectUri(getCallbackUrl(request)) 093 .build(); 094 } 095 096 @Override 097 public String getAuthorizationUrl(String serverURL) { 098 return getAuthorizationCodeFlow() 099 .newAuthorizationUrl() 100 .setRedirectUri(getCallbackUrl(serverURL)) 101 .build(); 102 } 103 104 protected String getCallbackUrl(HttpServletRequest request) { 105 return getCallbackUrl(VirtualHostHelper.getBaseURL(request)); 106 } 107 108 protected String getCallbackUrl(String serverURL) { 109 if (serverURL.endsWith("/")) { 110 serverURL = serverURL.substring(0, serverURL.length() - 1); 111 } 112 113 return serverURL + "/site/oauth2/" + serviceName + "/callback"; 114 } 115 116 @Override 117 public Credential handleAuthorizationCallback(HttpServletRequest request) { 118 119 // Checking if there was an error such as the user denied access 120 String error = getError(request); 121 if (error != null) { 122 throw new NuxeoException("There was an error: \"" + error + "\"."); 123 } 124 125 // Checking conditions on the "code" URL parameter 126 String code = getAuthorizationCode(request); 127 if (code == null) { 128 throw new NuxeoException("There is not code provided as QueryParam."); 129 } 130 131 try { 132 AuthorizationCodeFlow flow = getAuthorizationCodeFlow(); 133 134 String redirectUri = getCallbackUrl(request); 135 136 TokenResponse tokenResponse = flow.newTokenRequest(code) 137 .setScopes(scopes.isEmpty() ? null : scopes) // some providers do not support the 'scopes' param 138 .setRedirectUri(redirectUri).execute(); 139 140 // Create a unique userId to use with the credential store 141 String userId = getOrCreateServiceUser(request, tokenResponse.getAccessToken()); 142 143 return flow.createAndStoreCredential(tokenResponse, userId); 144 } catch (IOException e) { 145 throw new NuxeoException("Failed to retrieve credential", e); 146 } 147 } 148 149 /** 150 * Load a credential from the token store with the userId returned by getServiceUser() as key. 151 */ 152 @Override 153 public Credential loadCredential(String user) { 154 String userId = getServiceUserId(user); 155 try { 156 return userId != null ? getAuthorizationCodeFlow().loadCredential(userId) : null; 157 } catch (IOException e) { 158 throw new NuxeoException("Failed to load credential for " + user, e); 159 } 160 } 161 162 /** 163 * Returns the userId to use for token entries. 164 * Should be overriden by subclasses wanting to rely on a different field as key. 165 */ 166 protected String getServiceUserId(String key) { 167 Map<String, Serializable> filter = new HashMap<>(); 168 filter.put(NuxeoOAuth2Token.KEY_NUXEO_LOGIN, key); 169 return getServiceUserStore().find(filter); 170 } 171 172 /** 173 * Retrieves or creates a service user. 174 * Should be overriden by subclasses wanting to rely on a different field as key. 175 */ 176 protected String getOrCreateServiceUser(HttpServletRequest request, String accessToken) throws IOException { 177 String nuxeoLogin = request.getUserPrincipal().getName(); 178 String userId = getServiceUserId(nuxeoLogin); 179 if (userId == null) { 180 userId = getServiceUserStore().store(nuxeoLogin); 181 } 182 return userId; 183 } 184 185 public AuthorizationCodeFlow getAuthorizationCodeFlow() { 186 Credential.AccessMethod method = BearerToken.authorizationHeaderAccessMethod(); 187 GenericUrl tokenServerUrl = new GenericUrl(tokenServerURL); 188 HttpExecuteInterceptor clientAuthentication = new ClientParametersAuthentication(clientId, clientSecret); 189 String authorizationServerUrl = authorizationServerURL; 190 191 return new AuthorizationCodeFlow.Builder(method, HTTP_TRANSPORT, JSON_FACTORY, tokenServerUrl, 192 clientAuthentication, clientId, authorizationServerUrl) 193 .setScopes(scopes) 194 .setCredentialDataStore(getCredentialDataStore()) 195 .build(); 196 } 197 198 protected OAuth2ServiceUserStore getServiceUserStore() { 199 if (serviceUserStore == null) { 200 serviceUserStore = new OAuth2ServiceUserStore(serviceName); 201 } 202 return serviceUserStore; 203 } 204 205 public OAuth2TokenStore getCredentialDataStore() { 206 if (tokenStore == null) { 207 tokenStore = new OAuth2TokenStore(serviceName); 208 } 209 return tokenStore; 210 } 211 212 protected String getError(HttpServletRequest request) { 213 String error = request.getParameter(ERROR_URL_PARAMETER); 214 return StringUtils.isBlank(error) ? null : error; 215 } 216 217 // Checking conditions on the "code" URL parameter 218 protected String getAuthorizationCode(HttpServletRequest request) { 219 String code = request.getParameter(CODE_URL_PARAMETER); 220 return StringUtils.isBlank(code) ? null : code; 221 } 222 223 @Override 224 public String getServiceName() { 225 return serviceName; 226 } 227 228 @Override 229 public Long getId() { 230 return id; 231 } 232 233 @Override 234 public String getDescription() { 235 return description; 236 } 237 238 @Override 239 public String getTokenServerURL() { 240 return tokenServerURL; 241 } 242 243 @Override 244 public String getUserAuthorizationURL() { 245 return userAuthorizationURL; 246 } 247 248 @Override 249 public String getClientId() { 250 return clientId; 251 } 252 253 @Override 254 public String getClientSecret() { 255 return clientSecret; 256 } 257 258 @Override 259 public List<String> getScopes() { 260 return scopes; 261 } 262 263 @Override 264 public String getAuthorizationServerURL() { 265 return authorizationServerURL; 266 } 267 268 @Override 269 public boolean isEnabled() { 270 return enabled; 271 } 272 273 @Override 274 public void setEnabled(Boolean enabled) { 275 this.enabled = enabled; 276 } 277 278 @Override 279 public boolean isProviderAvailable() { 280 return isEnabled() && getClientSecret() != null && getClientId() != null; 281 } 282 283 @Override 284 public void setServiceName(String serviceName) { 285 this.serviceName = serviceName; 286 } 287 288 @Override 289 public void setId(Long id) { 290 this.id = id; 291 } 292 293 @Override 294 public void setDescription(String description) { 295 this.description = description; 296 } 297 298 @Override 299 public void setTokenServerURL(String tokenServerURL) { 300 this.tokenServerURL = tokenServerURL; 301 } 302 303 @Override 304 public void setUserAuthorizationURL(String userAuthorizationURL) { 305 this.userAuthorizationURL = userAuthorizationURL; 306 } 307 308 @Override 309 public void setAuthorizationServerURL(String authorizationServerURL) { 310 this.authorizationServerURL = authorizationServerURL; 311 } 312 313 @Override 314 public void setClientId(String clientId) { 315 this.clientId = clientId; 316 } 317 318 @Override 319 public void setClientSecret(String clientSecret) { 320 this.clientSecret = clientSecret; 321 } 322 323 @Override 324 public void setScopes(String... scopes) { 325 this.scopes = (scopes == null) ? Collections.emptyList() : Arrays.asList(scopes); 326 } 327}