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