blob: 2f427c7468faa4916979b23b76f00527dfaf22e3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.nifi.web.security.oidc.revocation;
import org.apache.nifi.web.security.oidc.client.web.OidcRegistrationProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestOperations;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
/**
* Standard implementation for handling Token Revocation Requests using Spring REST Operations
*/
public class StandardTokenRevocationResponseClient implements TokenRevocationResponseClient {
static final String REVOCATION_ENDPOINT = "revocation_endpoint";
private static final Logger logger = LoggerFactory.getLogger(StandardTokenRevocationResponseClient.class);
private final RestOperations restOperations;
private final ClientRegistrationRepository clientRegistrationRepository;
public StandardTokenRevocationResponseClient(
final RestOperations restOperations,
final ClientRegistrationRepository clientRegistrationRepository
) {
this.restOperations = Objects.requireNonNull(restOperations, "REST Operations required");
this.clientRegistrationRepository = Objects.requireNonNull(clientRegistrationRepository, "Client Registry Repository required");
}
/**
* Get Revocation Response as described in RFC 7009 Section 2.2 or return success when the Revocation Endpoint is not configured
*
* @param revocationRequest Revocation Request is required
* @return Token Revocation Response
*/
@Override
public TokenRevocationResponse getRevocationResponse(final TokenRevocationRequest revocationRequest) {
Objects.requireNonNull(revocationRequest, "Revocation Request required");
final ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId(OidcRegistrationProperty.REGISTRATION_ID.getProperty());
final ResponseEntity<?> responseEntity = getResponseEntity(revocationRequest, clientRegistration);
final HttpStatusCode statusCode = responseEntity.getStatusCode();
return new TokenRevocationResponse(statusCode.is2xxSuccessful(), statusCode.value());
}
private ResponseEntity<?> getResponseEntity(final TokenRevocationRequest revocationRequest, final ClientRegistration clientRegistration) {
final RequestEntity<?> requestEntity = getRequestEntity(revocationRequest, clientRegistration);
if (requestEntity == null) {
return ResponseEntity.ok(null);
} else {
try {
final ResponseEntity<?> responseEntity = restOperations.exchange(requestEntity, String.class);
logger.debug("Token Revocation Request processing completed [HTTP {}]", responseEntity.getStatusCode());
return responseEntity;
} catch (final Exception e) {
logger.warn("Token Revocation Request processing failed", e);
return ResponseEntity.internalServerError().build();
}
}
}
private RequestEntity<?> getRequestEntity(final TokenRevocationRequest revocationRequest, final ClientRegistration clientRegistration) {
final RequestEntity<?> requestEntity;
final URI revocationEndpoint = getRevocationEndpoint(clientRegistration);
if (revocationEndpoint == null) {
requestEntity = null;
logger.info("OIDC Revocation Endpoint not found");
} else {
final LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add(OAuth2ParameterNames.TOKEN, revocationRequest.getToken());
final String tokenTypeHint = revocationRequest.getTokenTypeHint();
if (StringUtils.hasLength(tokenTypeHint)) {
parameters.add(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenTypeHint);
}
final HttpHeaders headers = new HttpHeaders();
final String clientId = clientRegistration.getClientId();
final String clientSecret = clientRegistration.getClientSecret();
headers.setBasicAuth(clientId, clientSecret, StandardCharsets.UTF_8);
requestEntity = RequestEntity.post(revocationEndpoint)
.headers(headers)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(parameters);
}
return requestEntity;
}
private URI getRevocationEndpoint(final ClientRegistration clientRegistration) {
final ClientRegistration.ProviderDetails providerDetails = clientRegistration.getProviderDetails();
final Map<String, Object> configurationMetadata = providerDetails.getConfigurationMetadata();
final Object revocationEndpoint = configurationMetadata.get(REVOCATION_ENDPOINT);
return revocationEndpoint == null ? null : URI.create(revocationEndpoint.toString());
}
}