001/* 002 * (C) Copyright 2016 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 * Gabriel Barata <[email protected]> 018 */ 019package org.nuxeo.ecm.restapi.server.jaxrs; 020 021import static org.nuxeo.ecm.platform.oauth2.Constants.TOKEN_SERVICE; 022import static org.nuxeo.ecm.platform.oauth2.tokens.NuxeoOAuth2Token.SCHEMA; 023 024import java.io.IOException; 025import java.io.Serializable; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030import java.util.Objects; 031import java.util.stream.Collectors; 032 033import javax.servlet.http.HttpServletRequest; 034import javax.ws.rs.Consumes; 035import javax.ws.rs.DELETE; 036import javax.ws.rs.GET; 037import javax.ws.rs.POST; 038import javax.ws.rs.PUT; 039import javax.ws.rs.Path; 040import javax.ws.rs.PathParam; 041import javax.ws.rs.core.Context; 042import javax.ws.rs.core.MediaType; 043import javax.ws.rs.core.Response; 044import javax.ws.rs.core.Response.Status; 045import javax.ws.rs.core.Response.StatusType; 046 047import org.nuxeo.ecm.core.api.DocumentModel; 048import org.nuxeo.ecm.core.api.NuxeoException; 049import org.nuxeo.ecm.core.api.NuxeoPrincipal; 050import org.nuxeo.ecm.directory.Session; 051import org.nuxeo.ecm.directory.api.DirectoryService; 052import org.nuxeo.ecm.platform.oauth2.clients.OAuth2Client; 053import org.nuxeo.ecm.platform.oauth2.clients.OAuth2ClientService; 054import org.nuxeo.ecm.platform.oauth2.providers.AbstractOAuth2UserEmailProvider; 055import org.nuxeo.ecm.platform.oauth2.providers.NuxeoOAuth2ServiceProvider; 056import org.nuxeo.ecm.platform.oauth2.providers.OAuth2ServiceProvider; 057import org.nuxeo.ecm.platform.oauth2.providers.OAuth2ServiceProviderRegistry; 058import org.nuxeo.ecm.platform.oauth2.tokens.NuxeoOAuth2Token; 059import org.nuxeo.ecm.platform.oauth2.tokens.OAuth2TokenStore; 060import org.nuxeo.ecm.webengine.model.WebObject; 061import org.nuxeo.ecm.webengine.model.exceptions.WebResourceNotFoundException; 062import org.nuxeo.ecm.webengine.model.exceptions.WebSecurityException; 063import org.nuxeo.ecm.webengine.model.impl.AbstractResource; 064import org.nuxeo.ecm.webengine.model.impl.ResourceTypeImpl; 065import org.nuxeo.runtime.api.Framework; 066 067import com.fasterxml.jackson.databind.ObjectMapper; 068import com.google.api.client.auth.oauth2.Credential; 069 070/** 071 * Endpoint to retrieve OAuth2 authentication data 072 * 073 * @since 8.4 074 */ 075@WebObject(type = "oauth2") 076public class OAuth2Object extends AbstractResource<ResourceTypeImpl> { 077 078 public static final String TOKEN_DIR = "oauth2Tokens"; 079 080 /** 081 * Lists all oauth2 service providers. 082 * 083 * @since 9.2 084 */ 085 @GET 086 @Path("provider") 087 public List<NuxeoOAuth2ServiceProvider> getProviders(@Context HttpServletRequest request) { 088 return getProviders(); 089 } 090 091 /** 092 * Retrieves oauth2 data for a given provider. 093 */ 094 @GET 095 @Path("provider/{providerId}") 096 public Response getProvider(@PathParam("providerId") String providerId, @Context HttpServletRequest request) { 097 return Response.ok(getProvider(providerId)).build(); 098 } 099 100 /** 101 * Creates a new OAuth2 service provider. 102 * 103 * @since 9.2 104 */ 105 @POST 106 @Path("provider") 107 @Consumes(MediaType.APPLICATION_JSON) 108 public Response addProvider(@Context HttpServletRequest request, NuxeoOAuth2ServiceProvider provider) { 109 checkPermission(null); 110 Framework.doPrivileged(() -> { 111 OAuth2ServiceProviderRegistry registry = Framework.getService(OAuth2ServiceProviderRegistry.class); 112 registry.addProvider(provider.getServiceName(), provider.getDescription(), provider.getTokenServerURL(), 113 provider.getAuthorizationServerURL(), provider.getUserAuthorizationURL(), provider.getClientId(), 114 provider.getClientSecret(), provider.getScopes(), provider.isEnabled()); 115 }); 116 return Response.ok(getProvider(provider.getServiceName())).build(); 117 } 118 119 /** 120 * Updates an OAuth2 service provider. 121 * 122 * @since 9.2 123 */ 124 @PUT 125 @Path("provider/{providerId}") 126 @Consumes(MediaType.APPLICATION_JSON) 127 public Response updateProvider(@PathParam("providerId") String providerId, @Context HttpServletRequest request, 128 NuxeoOAuth2ServiceProvider provider) { 129 checkPermission(null); 130 getProvider(providerId); 131 Framework.doPrivileged(() -> { 132 OAuth2ServiceProviderRegistry registry = Framework.getService(OAuth2ServiceProviderRegistry.class); 133 registry.updateProvider(providerId, provider); 134 }); 135 return Response.ok(getProvider(provider.getServiceName())).build(); 136 } 137 138 /** 139 * Deletes an OAuth2 service provider. 140 * 141 * @since 9.2 142 */ 143 @DELETE 144 @Path("provider/{providerId}") 145 public Response deleteProvider(@PathParam("providerId") String providerId, @Context HttpServletRequest request) { 146 checkPermission(null); 147 getProvider(providerId); 148 Framework.doPrivileged(() -> { 149 OAuth2ServiceProviderRegistry registry = Framework.getService(OAuth2ServiceProviderRegistry.class); 150 registry.deleteProvider(providerId); 151 }); 152 return Response.noContent().build(); 153 } 154 155 /** 156 * Retrieves a valid access token for a given provider and the current user. If expired, the token will be 157 * refreshed. 158 */ 159 @GET 160 @Path("provider/{providerId}/token") 161 public Response getToken(@PathParam("providerId") String providerId, @Context HttpServletRequest request) 162 throws IOException { 163 164 NuxeoOAuth2ServiceProvider provider = getProvider(providerId); 165 166 String username = request.getUserPrincipal().getName(); 167 NuxeoOAuth2Token token = getToken(provider, username); 168 if (token == null) { 169 return Response.status(Status.NOT_FOUND).build(); 170 } 171 Credential credential = getCredential(provider, token); 172 173 if (credential == null) { 174 return Response.status(Status.NOT_FOUND).build(); 175 } 176 Long expiresInSeconds = credential.getExpiresInSeconds(); 177 if (expiresInSeconds != null && expiresInSeconds <= 0) { 178 credential.refreshToken(); 179 } 180 Map<String, Object> result = new HashMap<>(); 181 result.put("token", credential.getAccessToken()); 182 return buildResponse(Status.OK, result); 183 } 184 185 /** 186 * Retrieves all OAuth2 tokens. 187 * 188 * @since 9.2 189 */ 190 @GET 191 @Path("token") 192 public List<NuxeoOAuth2Token> getTokens(@Context HttpServletRequest request) { 193 checkPermission(null); 194 return getTokens(); 195 } 196 197 /** 198 * Retrieves an OAuth2 provider token. 199 * 200 * @since 10.2 201 */ 202 @GET 203 @Path("token/provider/{providerId}/user/{nxuser}") 204 public Response getProviderToken(@PathParam("providerId") String providerId, @PathParam("nxuser") String nxuser, 205 @Context HttpServletRequest request) { 206 checkPermission(nxuser); 207 NuxeoOAuth2ServiceProvider provider = getProvider(providerId); 208 return Response.ok(getToken(provider, nxuser)).build(); 209 } 210 211 /** 212 * Retrieves an OAuth2 Token. 213 * 214 * @since 9.2 215 * 216 * @deprecated since 10.2 Use {@link #getProviderToken(String, String, HttpServletRequest)} instead. 217 */ 218 @Deprecated 219 @GET 220 @Path("token/{providerId}/{nxuser}") 221 public Response getToken(@PathParam("providerId") String providerId, @PathParam("nxuser") String nxuser, 222 @Context HttpServletRequest request) { 223 return getProviderToken(providerId, nxuser, request); 224 } 225 226 /** 227 * Updates an OAuth2 provider token. 228 * 229 * @since 10.2 230 */ 231 @PUT 232 @Path("token/provider/{providerId}/user/{nxuser}") 233 @Consumes(MediaType.APPLICATION_JSON) 234 public Response updateProviderToken(@PathParam("providerId") String providerId, @PathParam("nxuser") String nxuser, 235 @Context HttpServletRequest request, NuxeoOAuth2Token token) { 236 checkPermission(nxuser); 237 NuxeoOAuth2ServiceProvider provider = getProvider(providerId); 238 return Response.ok(updateToken(provider, nxuser, token)).build(); 239 } 240 241 /** 242 * Updates an OAuth2 Token. 243 * 244 * @since 9.2 245 * 246 * @deprecated since 10.2 Use {@link #updateProviderToken(String, String, HttpServletRequest, NuxeoOAuth2Token)} 247 * instead. 248 */ 249 @Deprecated 250 @PUT 251 @Path("token/{providerId}/{nxuser}") 252 @Consumes(MediaType.APPLICATION_JSON) 253 public Response updateToken(@PathParam("providerId") String providerId, @PathParam("nxuser") String nxuser, 254 @Context HttpServletRequest request, NuxeoOAuth2Token token) { 255 return updateProviderToken(providerId, nxuser, request, token); 256 } 257 258 /** 259 * Deletes an OAuth2 provider token. 260 * 261 * @since 10.2 262 */ 263 @DELETE 264 @Path("token/provider/{providerId}/user/{nxuser}") 265 public Response deleteProviderToken(@PathParam("providerId") String providerId, @PathParam("nxuser") String nxuser, 266 @Context HttpServletRequest request) { 267 checkPermission(nxuser); 268 NuxeoOAuth2ServiceProvider provider = getProvider(providerId); 269 deleteToken(getTokenDoc(provider, nxuser)); 270 return Response.noContent().build(); 271 } 272 273 /** 274 * Deletes an OAuth2 Token. 275 * 276 * @since 9.2 277 * 278 * @deprecated since 10.2 Use {@link #deleteProviderToken(String, String, HttpServletRequest)} instead. 279 */ 280 @Deprecated 281 @DELETE 282 @Path("token/{providerId}/{nxuser}") 283 public Response deleteToken(@PathParam("providerId") String providerId, @PathParam("nxuser") String nxuser, 284 @Context HttpServletRequest request) { 285 return deleteProviderToken(providerId, nxuser, request); 286 } 287 288 /** 289 * Retrieves all oauth2 provider tokens for the current user. 290 * 291 * @since 10.2 292 */ 293 @GET 294 @Path("token/provider") 295 public List<NuxeoOAuth2Token> getProviderUserTokens(@Context HttpServletRequest request) { 296 checkNotAnonymousUser(); 297 String nxuser = request.getUserPrincipal().getName(); 298 return getTokens(nxuser).stream() // filter: make sure no client tokens are retrieved 299 .filter(token -> token.getClientId() == null).collect(Collectors.toList()); 300 } 301 302 /** 303 * Retrieves all oauth2 client tokens for the current user. 304 * 305 * @since 10.2 306 */ 307 @GET 308 @Path("token/client") 309 public List<NuxeoOAuth2Token> getClientUserTokens(@Context HttpServletRequest request) { 310 checkNotAnonymousUser(); 311 String nxuser = request.getUserPrincipal().getName(); 312 return getTokens(nxuser).stream() // filter: make sure no provider tokens are retrieved 313 .filter(token -> token.getClientId() != null).collect(Collectors.toList()); 314 } 315 316 /** 317 * Retrieves a oauth2 client token. 318 * 319 * @since 10.2 320 */ 321 @GET 322 @Path("token/client/{clientId}/user/{nxuser}") 323 public Response getClientToken(@PathParam("clientId") String clientId, @PathParam("nxuser") String nxuser, 324 @Context HttpServletRequest request) { 325 checkPermission(nxuser); 326 OAuth2Client client = getClient(clientId); 327 return Response.ok(getToken(client, nxuser)).build(); 328 } 329 330 /** 331 * Updates an OAuth2 client token. 332 * 333 * @since 10.2 334 */ 335 @PUT 336 @Path("token/client/{clientId}/user/{nxuser}") 337 @Consumes(MediaType.APPLICATION_JSON) 338 public Response updateClientToken(@PathParam("clientId") String clientId, @PathParam("nxuser") String nxuser, 339 @Context HttpServletRequest request, NuxeoOAuth2Token token) { 340 checkPermission(nxuser); 341 OAuth2Client client = Framework.getService(OAuth2ClientService.class).getClient(clientId); 342 return Response.ok(updateToken(client, nxuser, token)).build(); 343 } 344 345 /** 346 * Deletes a oauth2 client token. 347 * 348 * @since 10.2 349 */ 350 @DELETE 351 @Path("token/client/{clientId}/user/{nxuser}") 352 public Response deleteClientToken(@PathParam("clientId") String clientId, @PathParam("nxuser") String nxuser, 353 @Context HttpServletRequest request) { 354 checkPermission(nxuser); 355 OAuth2Client client = Framework.getService(OAuth2ClientService.class).getClient(clientId); 356 deleteToken(getTokenDoc(client, nxuser)); 357 return Response.noContent().build(); 358 } 359 360 /** 361 * Retrieves oauth2 clients. 362 * 363 * @since 10.2 364 */ 365 @GET 366 @Path("client") 367 public List<OAuth2Client> getClients(@Context HttpServletRequest request) { 368 return Framework.getService(OAuth2ClientService.class).getClients(); 369 } 370 371 /** 372 * Retrieves a oauth2 client. 373 * 374 * @since 10.2 375 */ 376 @GET 377 @Path("client/{clientId}") 378 public Response getClient(@PathParam("clientId") String clientId, 379 @Context HttpServletRequest request) { 380 OAuth2Client client = getClient(clientId); 381 return Response.ok(client).build(); 382 } 383 384 protected List<NuxeoOAuth2ServiceProvider> getProviders() { 385 OAuth2ServiceProviderRegistry registry = Framework.getService(OAuth2ServiceProviderRegistry.class); 386 return registry.getProviders() 387 .stream() 388 .filter(NuxeoOAuth2ServiceProvider.class::isInstance) 389 .map(provider -> (NuxeoOAuth2ServiceProvider) provider) 390 .collect(Collectors.toList()); 391 } 392 393 protected NuxeoOAuth2ServiceProvider getProvider(String providerId) { 394 OAuth2ServiceProvider provider = Framework.getService(OAuth2ServiceProviderRegistry.class) 395 .getProvider(providerId); 396 if (provider == null || !(provider instanceof NuxeoOAuth2ServiceProvider)) { 397 throw new WebResourceNotFoundException("Invalid provider: " + providerId); 398 } 399 return (NuxeoOAuth2ServiceProvider) provider; 400 } 401 402 protected List<NuxeoOAuth2Token> getTokens() { 403 return getTokens((String) null); 404 } 405 406 protected List<NuxeoOAuth2Token> getTokens(String nxuser) { 407 return Framework.doPrivileged(() -> { 408 DirectoryService ds = Framework.getService(DirectoryService.class); 409 try (Session session = ds.open(TOKEN_DIR)) { 410 Map<String, Serializable> filter = new HashMap<>(); 411 if (nxuser != null) { 412 filter.put(NuxeoOAuth2Token.KEY_NUXEO_LOGIN, nxuser); 413 } 414 List<DocumentModel> docs = session.query(filter, Collections.emptySet(), Collections.emptyMap(), true, 415 0, 0); 416 return docs.stream().map(NuxeoOAuth2Token::new).collect(Collectors.toList()); 417 } 418 }); 419 } 420 421 protected OAuth2Client getClient(String clientId) { 422 OAuth2Client client = Framework.getService(OAuth2ClientService.class).getClient(clientId); 423 if (client == null) { 424 throw new WebResourceNotFoundException("Invalid client: " + clientId); 425 } 426 return client; 427 } 428 429 protected DocumentModel getTokenDoc(NuxeoOAuth2ServiceProvider provider, String nxuser) { 430 Map<String, Serializable> filter = new HashMap<>(); 431 filter.put("serviceName", provider.getServiceName()); 432 filter.put(NuxeoOAuth2Token.KEY_NUXEO_LOGIN, nxuser); 433 List<DocumentModel> tokens = Framework.doPrivileged(() -> { 434 List<DocumentModel> entries = provider.getCredentialDataStore().query(filter); 435 return entries.stream().filter(Objects::nonNull).collect(Collectors.toList()); 436 }); 437 if (tokens.size() > 1) { 438 throw new NuxeoException("Found multiple " + provider.getId() + " accounts for " + nxuser); 439 } else if (tokens.isEmpty()) { 440 throw new WebResourceNotFoundException("No token found for provider: " + provider.getServiceName()); 441 } else { 442 return tokens.get(0); 443 } 444 } 445 446 protected DocumentModel getTokenDoc(OAuth2Client client, String nxuser) { 447 Map<String, Serializable> filter = new HashMap<>(); 448 filter.put("clientId", client.getId()); 449 filter.put(NuxeoOAuth2Token.KEY_NUXEO_LOGIN, nxuser); 450 OAuth2TokenStore tokenStore = new OAuth2TokenStore(TOKEN_SERVICE); 451 List<DocumentModel> tokens = tokenStore.query(filter).stream() 452 .filter(Objects::nonNull).collect(Collectors.toList()); 453 if (tokens.size() > 1) { 454 throw new NuxeoException("Found multiple " + client.getId() + " accounts for " + nxuser); 455 } else if (tokens.size() == 0) { 456 throw new WebResourceNotFoundException("No token found for client: " + client.getId()); 457 } else { 458 return tokens.get(0); 459 } 460 } 461 462 protected NuxeoOAuth2Token getToken(NuxeoOAuth2ServiceProvider provider, String nxuser) { 463 return new NuxeoOAuth2Token(getTokenDoc(provider, nxuser)); 464 } 465 466 protected NuxeoOAuth2Token getToken(OAuth2Client client, String nxuser) { 467 return new NuxeoOAuth2Token(getTokenDoc(client, nxuser)); 468 } 469 470 protected NuxeoOAuth2Token updateToken(NuxeoOAuth2ServiceProvider provider, String nxuser, NuxeoOAuth2Token token) { 471 updateTokenDoc(token, getTokenDoc(provider, nxuser)); 472 return getToken(provider, nxuser); 473 } 474 475 protected NuxeoOAuth2Token updateToken(OAuth2Client client, String nxuser, NuxeoOAuth2Token token) { 476 updateTokenDoc(token, getTokenDoc(client, nxuser)); 477 return getToken(client, nxuser); 478 } 479 480 protected void updateTokenDoc(NuxeoOAuth2Token token, DocumentModel entry) { 481 entry.setProperty(SCHEMA, "serviceName", token.getServiceName()); 482 entry.setProperty(SCHEMA, "nuxeoLogin", token.getNuxeoLogin()); 483 entry.setProperty(SCHEMA, "clientId", token.getClientId()); 484 entry.setProperty(SCHEMA, "isShared", token.isShared()); 485 entry.setProperty(SCHEMA, "sharedWith", token.getSharedWith()); 486 entry.setProperty(SCHEMA, "serviceLogin", token.getServiceLogin()); 487 entry.setProperty(SCHEMA, "creationDate", token.getCreationDate()); 488 Framework.doPrivileged(() -> { 489 DirectoryService ds = Framework.getService(DirectoryService.class); 490 try (Session session = ds.open(TOKEN_DIR)) { 491 session.updateEntry(entry); 492 } 493 }); 494 } 495 496 protected void deleteToken(DocumentModel token) { 497 Framework.doPrivileged(() -> { 498 DirectoryService ds = Framework.getService(DirectoryService.class); 499 try (Session session = ds.open(TOKEN_DIR)) { 500 session.deleteEntry(token); 501 } 502 }); 503 } 504 505 protected Credential getCredential(NuxeoOAuth2ServiceProvider provider, NuxeoOAuth2Token token) { 506 return provider.loadCredential((provider instanceof AbstractOAuth2UserEmailProvider) ? token.getServiceLogin() 507 : token.getNuxeoLogin()); 508 } 509 510 protected Response buildResponse(StatusType status, Object obj) throws IOException { 511 ObjectMapper mapper = new ObjectMapper(); 512 String message = mapper.writeValueAsString(obj); 513 514 return Response.status(status) 515 .header("Content-Length", message.getBytes("UTF-8").length) 516 .type(MediaType.APPLICATION_JSON + "; charset=UTF-8") 517 .entity(message) 518 .build(); 519 } 520 521 protected void checkPermission(String nxuser) { 522 if (!hasPermission(nxuser)) { 523 throw new WebSecurityException("You do not have permissions to perform this operation."); 524 } 525 } 526 527 protected boolean hasPermission(String nxuser) { 528 NuxeoPrincipal principal = getContext().getCoreSession().getPrincipal(); 529 return principal.isAdministrator() || (nxuser == null ? false : nxuser.equals(principal.getName())); 530 } 531 532 protected void checkNotAnonymousUser() { 533 NuxeoPrincipal principal = getContext().getCoreSession().getPrincipal(); 534 if (principal.isAnonymous()) { 535 throw new WebSecurityException("You do not have permissions to perform this operation."); 536 } 537 } 538 539}