All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.gravitee.am.gateway.handler.uma.resources.endpoint.ResourceRegistrationEndpoint Maven / Gradle / Ivy

/**
 * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gravitee.am.gateway.handler.uma.resources.endpoint;

import io.gravitee.am.common.exception.oauth2.InvalidRequestException;
import io.gravitee.am.common.jwt.JWT;
import io.gravitee.am.common.utils.ConstantKeys;
import io.gravitee.am.gateway.handler.common.jwt.SubjectManager;
import io.gravitee.am.gateway.handler.common.vertx.utils.UriBuilderRequest;
import io.gravitee.am.gateway.handler.uma.resources.response.ResourceResponse;
import io.gravitee.am.model.Domain;
import io.gravitee.am.model.oidc.Client;
import io.gravitee.am.model.uma.Resource;
import io.gravitee.am.service.ResourceService;
import io.gravitee.am.service.exception.ResourceNotFoundException;
import io.gravitee.am.service.exception.UserNotFoundException;
import io.gravitee.am.service.model.NewResource;
import io.gravitee.common.http.HttpHeaders;
import io.gravitee.common.http.HttpStatusCode;
import io.gravitee.common.http.MediaType;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import io.vertx.core.Handler;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.rxjava3.ext.web.RoutingContext;

import static io.gravitee.am.gateway.handler.uma.constants.UMAConstants.RESOURCE_ID;
import static io.gravitee.am.gateway.handler.uma.constants.UMAConstants.RESOURCE_REGISTRATION_PATH;
import static io.gravitee.am.gateway.handler.uma.constants.UMAConstants.UMA_PATH;

/**
 * 
 * This endpoint is part of the UMA 2.0 Protection API.
 * It enables the resource server (API) to put resources under the protection of the Authorization Server
 * on behalf of the resource owner, and manage them over time.
 * See here
 * 
* * @author Alexandre FARIA (contact at alexandrefaria.net) * @author GraviteeSource Team */ public class ResourceRegistrationEndpoint implements Handler { private final ResourceService resourceService; private final Domain domain; private final SubjectManager subjectManager; public ResourceRegistrationEndpoint(Domain domain, ResourceService resourceService, SubjectManager subjectManager) { this.domain = domain; this.resourceService = resourceService; this.subjectManager = subjectManager; } @Override public void handle(RoutingContext context) { JWT accessToken = context.get(ConstantKeys.TOKEN_CONTEXT_KEY); Client client = context.get(ConstantKeys.CLIENT_CONTEXT_KEY); subjectManager.findUserIdBySub(accessToken) .switchIfEmpty(Single.error(() -> new UserNotFoundException(accessToken.getSub()))) .flatMap(userId -> this.resourceService.listByDomainAndClientAndUser(domain.getId(), client.getId(), userId.id()) .map(Resource::getId) .collect(JsonArray::new, JsonArray::add)) .subscribe( buffer -> context.response() .putHeader(HttpHeaders.CACHE_CONTROL, "no-store") .putHeader(HttpHeaders.PRAGMA, "no-cache") .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .setStatusCode(buffer.isEmpty() ? HttpStatusCode.NO_CONTENT_204 : HttpStatusCode.OK_200) .end(Json.encodePrettily(buffer)) , error -> context.fail(error) ); } public void create(RoutingContext context) { JWT accessToken = context.get(ConstantKeys.TOKEN_CONTEXT_KEY); Client client = context.get(ConstantKeys.CLIENT_CONTEXT_KEY); String basePath = UriBuilderRequest.resolveProxyRequest(context); this.extractRequest(context) .flatMap(request -> subjectManager.findUserIdBySub(accessToken) .switchIfEmpty(Single.error(() -> new UserNotFoundException(accessToken.getSub()))) .flatMap(userId -> this.resourceService.create(request, domain.getId(), client.getId(), userId.id()/*todo degraded mode: should we use full id here as well?*/))) .subscribe( resource -> { final String resourceLocation = resourceLocation(basePath, resource); context.response() .putHeader(HttpHeaders.CACHE_CONTROL, "no-store") .putHeader(HttpHeaders.PRAGMA, "no-cache") .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .putHeader(HttpHeaders.LOCATION, resourceLocation) .setStatusCode(HttpStatusCode.CREATED_201) .end(Json.encodePrettily(ResourceResponse.from(resource, resourceLocation))); } , error -> context.fail(error) ); } public void get(RoutingContext context) { JWT accessToken = context.get(ConstantKeys.TOKEN_CONTEXT_KEY); Client client = context.get(ConstantKeys.CLIENT_CONTEXT_KEY); String resource_id = context.request().getParam(RESOURCE_ID); subjectManager.findUserIdBySub(accessToken) .switchIfEmpty(Maybe.error(() -> new UserNotFoundException(accessToken.getSub()))) .flatMap(useId -> this.resourceService.findByDomainAndClientAndUserAndResource(domain.getId(), client.getId(), useId.id()/*todo degraded mode: should we use full id here as well?*/, resource_id)) .switchIfEmpty(Single.error(new ResourceNotFoundException(resource_id))) .subscribe( resource -> context.response() .putHeader(HttpHeaders.CACHE_CONTROL, "no-store") .putHeader(HttpHeaders.PRAGMA, "no-cache") .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .setStatusCode(HttpStatusCode.OK_200) .end(Json.encodePrettily(ResourceResponse.from(resource))) , error -> context.fail(error) ); } /** * https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-federated-authz-2.0.html#reg-api * The spec state that if the resource can not be found, it must result in a 404. * By the way this may be better than a 403 to avoid confirming ids to a potential attacks. * * @param context */ public void update(RoutingContext context) { JWT accessToken = context.get(ConstantKeys.TOKEN_CONTEXT_KEY); Client client = context.get(ConstantKeys.CLIENT_CONTEXT_KEY); String resource_id = context.request().getParam(RESOURCE_ID); this.extractRequest(context) .flatMap(request -> subjectManager.findUserIdBySub(accessToken) .switchIfEmpty(Single.error(() -> new UserNotFoundException(accessToken.getSub()))) .flatMap(userId -> this.resourceService.update(request, domain.getId(), client.getId(), userId.id()/*todo degraded mode: should we use full id here as well?*/, resource_id))) .subscribe( resource -> context.response() .putHeader(HttpHeaders.CACHE_CONTROL, "no-store") .putHeader(HttpHeaders.PRAGMA, "no-cache") .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .setStatusCode(HttpStatusCode.OK_200) .end(Json.encodePrettily(ResourceResponse.from(resource))) , error -> context.fail(error) ); } public void delete(RoutingContext context) { JWT accessToken = context.get(ConstantKeys.TOKEN_CONTEXT_KEY); Client client = context.get(ConstantKeys.CLIENT_CONTEXT_KEY); String resource_id = context.request().getParam(RESOURCE_ID); subjectManager.findUserIdBySub(accessToken) .flatMapCompletable(userId -> this.resourceService.delete(domain.getId(), client.getId(), userId.id()/*todo degraded mode: should we use full id here as well?*/, resource_id)) .subscribe( () -> context.response() .putHeader(HttpHeaders.CACHE_CONTROL, "no-store") .putHeader(HttpHeaders.PRAGMA, "no-cache") .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .setStatusCode(HttpStatusCode.NO_CONTENT_204) .end() , error -> context.fail(error) ); } private Single extractRequest(RoutingContext context) { return Single.just(context.getBodyAsJson()) .flatMap(this::bodyValidation) .map(body -> body.mapTo(NewResource.class)); } private Single bodyValidation(JsonObject body) { //Only one field is required from the spec, others are tag as optional if (body == null || !body.containsKey("resource_scopes")) { return Single.error(new InvalidRequestException("missing resource_scopes")); } return Single.just(body); } private String resourceLocation(String basePath, Resource resource) { return basePath + UMA_PATH + RESOURCE_REGISTRATION_PATH + "/" + resource.getId(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy