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