/* * oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. * * Copyright (c) 2014, Gluu */ package org.xdi.oxauth.uma.ws.rs; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.xdi.oxauth.model.common.uma.UmaRPT; import org.xdi.oxauth.model.error.ErrorResponseFactory; import org.xdi.oxauth.model.uma.RptIntrospectionResponse; import org.xdi.oxauth.model.uma.UmaConstants; import org.xdi.oxauth.model.uma.UmaErrorResponseType; import org.xdi.oxauth.model.uma.UmaPermission; import org.xdi.oxauth.model.uma.persistence.ResourceSetPermission; import org.xdi.oxauth.service.uma.AbstractRPTManager; import org.xdi.oxauth.service.uma.RptManager; import org.xdi.oxauth.service.uma.ScopeService; import org.xdi.oxauth.service.uma.UmaValidationService; import org.xdi.oxauth.util.ServerUtil; import com.google.common.collect.Lists; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import com.wordnik.swagger.annotations.ApiResponse; import com.wordnik.swagger.annotations.ApiResponses; /** * The endpoint at which the host requests the status of an RPT presented to it by a requester. * The endpoint is RPT introspection profile implementation defined by * http://docs.kantarainitiative.org/uma/draft-uma-core.html#uma-bearer-token-profile * * @author Yuriy Zabrovarnyy */ @Path("/rpt/status") @Api(value = "/rpt/status", description = "The endpoint at which the host requests the status of an RPT presented to it by a requester." + " The endpoint is RPT introspection profile implementation defined by UMA specification") public class RptStatusWS { @Inject private Logger log; @Inject private ErrorResponseFactory errorResponseFactory; @Inject private RptManager rptManager; @Inject private UmaValidationService umaValidationService; @Inject private ScopeService umaScopeService; @POST @Produces({UmaConstants.JSON_MEDIA_TYPE}) @ApiOperation(value = "The resource server MUST determine a received RPT's status, including both whether it is active and, if so, its associated authorization data, before giving or refusing access to the client. An RPT is associated with a set of authorization data that governs whether the client is authorized for access. The token's nature and format are dictated by its profile; the profile might allow it to be self-contained, such that the resource server is able to determine its status locally, or might require or allow the resource server to make a run-time introspection request of the authorization server that issued the token.", produces = UmaConstants.JSON_MEDIA_TYPE, notes = "The endpoint MAY allow other parameters to provide further context to\n" + " the query. For instance, an authorization service may need to know\n" + " the IP address of the client accessing the protected resource in\n" + " order to determine the appropriateness of the token being presented.\n" + "\n" + " To prevent unauthorized token scanning attacks, the endpoint MUST\n" + " also require some form of authorization to access this endpoint, such\n" + " as client authentication as described in OAuth 2.0 [RFC6749] or a\n" + " separate OAuth 2.0 access token such as the bearer token described in\n" + " OAuth 2.0 Bearer Token Usage [RFC6750]. The methods of managing and\n" + " validating these authentication credentials are out of scope of this\n" + " specification.\n" ) @ApiResponses(value = { @ApiResponse(code = 401, message = "Unauthorized") }) public Response requestRptStatus(@HeaderParam("Authorization") String authorization, @FormParam("token") @ApiParam(value = "The string value of the token. For access tokens,\n" + " this is the \"access_token\" value returned from the token endpoint\n" + " defined in OAuth 2.0 [RFC6749] section 5.1. For refresh tokens,\n" + " this is the \"refresh_token\" value returned from the token endpoint\n" + " as defined in OAuth 2.0 [RFC6749] section 5.1. Other token types\n" + " are outside the scope of this specification.", required = true) String rptAsString, @FormParam("token_type_hint") @ApiParam(value = "A hint about the type of the token\n" + " submitted for introspection. The protected resource re MAY pass\n" + " this parameter in order to help the authorization server to\n" + " optimize the token lookup. If the server is unable to locate the\n" + " token using the given hint, it MUST extend its search across all\n" + " of its supported token types. An authorization server MAY ignore\n" + " this parameter, particularly if it is able to detect the token\n" + " type automatically. Values for this field are defined in OAuth\n" + " Token Revocation [RFC7009].", required = false) String tokenTypeHint) { try { umaValidationService.assertHasProtectionScope(authorization); final UmaRPT rpt = rptManager.getRPTByCode(rptAsString); if (rpt != null && AbstractRPTManager.isGat(rpt.getCode())) { return gatResponse(rpt); } if (!isValid(rpt)) { return Response.status(Response.Status.OK). entity(new RptIntrospectionResponse(false)). cacheControl(ServerUtil.cacheControl(true)). build(); } final List<UmaPermission> permissions = buildStatusResponsePermissions(rpt); // active status final RptIntrospectionResponse statusResponse = new RptIntrospectionResponse(); statusResponse.setActive(true); statusResponse.setExpiresAt(rpt.getExpirationDate()); statusResponse.setIssuedAt(rpt.getCreationDate()); statusResponse.setPermissions(permissions); // convert manually to avoid possible conflict between resteasy providers, e.g. jettison, jackson final String entity = ServerUtil.asJson(statusResponse); return Response.status(Response.Status.OK).entity(entity).cacheControl(ServerUtil.cacheControl(true)).build(); } catch (Exception ex) { log.error("Exception happened", ex); if (ex instanceof WebApplicationException) { throw (WebApplicationException) ex; } throw new WebApplicationException(Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(errorResponseFactory.getUmaJsonErrorResponse(UmaErrorResponseType.SERVER_ERROR)).build()); } } private Response gatResponse(UmaRPT rpt) throws IOException { if (!isValid(rpt)) { return Response.status(Response.Status.OK). entity(new RptIntrospectionResponse(false)). cacheControl(ServerUtil.cacheControl(true)). build(); } UmaPermission permission = new UmaPermission(); permission.setScopes(rpt.getPermissions()); permission.setExpiresAt(rpt.getExpirationDate()); permission.setIssuedAt(new Date()); final RptIntrospectionResponse statusResponse = new RptIntrospectionResponse(); statusResponse.setActive(true); statusResponse.setExpiresAt(rpt.getExpirationDate()); statusResponse.setIssuedAt(rpt.getCreationDate()); statusResponse.setPermissions(Lists.newArrayList(permission)); // convert manually to avoid possible conflict between resteasy providers, e.g. jettison, jackson final String entity = ServerUtil.asJson(statusResponse); return Response.status(Response.Status.OK). entity(entity). cacheControl(ServerUtil.cacheControl(true)). build(); } private boolean isValid(UmaRPT p_rpt) { if (p_rpt != null) { p_rpt.checkExpired(); return p_rpt.isValid(); } return false; } private boolean isValid(ResourceSetPermission resourceSetPermission) { if (resourceSetPermission != null) { resourceSetPermission.checkExpired(); return resourceSetPermission.isValid(); } return false; } private List<UmaPermission> buildStatusResponsePermissions(UmaRPT p_rpt) { final List<UmaPermission> result = new ArrayList<UmaPermission>(); if (p_rpt != null) { final List<ResourceSetPermission> rptPermissions = rptManager.getRptPermissions(p_rpt); if (rptPermissions != null && !rptPermissions.isEmpty()) { for (ResourceSetPermission permission : rptPermissions) { if (isValid(permission)) { final UmaPermission toAdd = ServerUtil.convert(permission, umaScopeService); if (toAdd != null) { result.add(toAdd); } } else { log.debug("Ignore permission, skip it in response because permission is not valid. Permission dn: {}, rpt dn: {}", permission.getDn(), p_rpt.getDn()); } } } } return result; } @GET @Consumes({UmaConstants.JSON_MEDIA_TYPE}) @Produces({UmaConstants.JSON_MEDIA_TYPE}) @ApiOperation(value = "Not allowed") @ApiResponses(value = { @ApiResponse(code = 405, message = "Introspection of RPT is not allowed by GET HTTP method.") }) public Response requestRptStatusGet(@HeaderParam("Authorization") String authorization, @FormParam("token") String rpt, @FormParam("token_type_hint") String tokenTypeHint) { throw new WebApplicationException(Response.status(405).entity("Introspection of RPT is not allowed by GET HTTP method.").build()); } }