NIFI-13559 Removed Legacy Access Resource REST Methods (#9091)

- Removed GET /access for Access Status
- Removed GET /access/config for Login Configuration
- Removed GET /access/token/expiration for Access Token Expiration
This commit is contained in:
David Handermann 2024-07-19 08:15:19 -05:00 committed by GitHub
parent ee7a905c40
commit e72b099887
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 4 additions and 454 deletions

View File

@ -70,7 +70,7 @@ echo
echo "Checking NiFi REST API Access (expect status: 400)"
# Return code is 400 instead of 200 because of an invalid SNI
test "400" = "$(docker exec "${container_name}" bash -c "curl -ksSo /dev/null -w %{http_code} -m 10 --retry 5 --retry-connrefused --retry-max-time 60 https://${ip}:${port}/nifi-api/access")"
test "400" = "$(docker exec "${container_name}" bash -c "curl -ksSo /dev/null -w %{http_code} -m 10 --retry 5 --retry-connrefused --retry-max-time 60 https://${ip}:${port}/nifi-api/authentication/configuration")"
echo
echo "Stopping NiFi container"

View File

@ -1,45 +0,0 @@
/*
* 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.api.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.xml.bind.annotation.XmlType;
/**
* Details for the access configuration.
*/
@XmlType(name = "accessConfig")
public class AccessConfigurationDTO {
private Boolean supportsLogin;
/**
* @return Indicates whether or not this NiFi supports user login.
*/
@Schema(description = "Indicates whether or not this NiFi supports user login.",
accessMode = Schema.AccessMode.READ_ONLY
)
public Boolean getSupportsLogin() {
return supportsLogin;
}
public void setSupportsLogin(Boolean supportsLogin) {
this.supportsLogin = supportsLogin;
}
}

View File

@ -1,82 +0,0 @@
/*
* 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.api.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.xml.bind.annotation.XmlRootElement;
/**
* A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds the users access status.
*/
@XmlRootElement(name = "accessStatus")
public class AccessStatusDTO {
public static enum Status {
UNKNOWN,
ACTIVE
}
private String identity;
private String username;
private String status;
private String message;
/**
* @return the user identity
*/
@Schema(description = "The user identity.",
accessMode = Schema.AccessMode.READ_ONLY
)
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
/**
* @return the user access status
*/
@Schema(description = "The user access status.",
accessMode = Schema.AccessMode.READ_ONLY
)
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
/**
* @return additional details about the user access status
*/
@Schema(description = "Additional details about the user access status.",
accessMode = Schema.AccessMode.READ_ONLY
)
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -1,43 +0,0 @@
/*
* 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.api.entity;
import jakarta.xml.bind.annotation.XmlRootElement;
import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
/**
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a AccessConfigurationDTO.
*/
@XmlRootElement(name = "accessConfigurationEntity")
public class AccessConfigurationEntity extends Entity {
private AccessConfigurationDTO config;
/**
* The AccessConfigurationDTO that is being serialized.
*
* @return The AccessConfigurationDTO object
*/
public AccessConfigurationDTO getConfig() {
return config;
}
public void setConfig(AccessConfigurationDTO config) {
this.config = config;
}
}

View File

@ -1,43 +0,0 @@
/*
* 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.api.entity;
import jakarta.xml.bind.annotation.XmlRootElement;
import org.apache.nifi.web.api.dto.AccessStatusDTO;
/**
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a AccessStatusDTO.
*/
@XmlRootElement(name = "accessStatusEntity")
public class AccessStatusEntity extends Entity {
private AccessStatusDTO accessStatus;
/**
* The AccessStatusDTO that is being serialized.
*
* @return The AccessStatusDTO object
*/
public AccessStatusDTO getAccessStatus() {
return accessStatus;
}
public void setAccessStatus(AccessStatusDTO accessStatus) {
this.accessStatus = accessStatus;
}
}

View File

@ -1,36 +0,0 @@
/*
* 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.api.entity;
import org.apache.nifi.web.api.dto.AccessTokenExpirationDTO;
import jakarta.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "accessTokenExpirationEntity")
public class AccessTokenExpirationEntity extends Entity {
private AccessTokenExpirationDTO accessTokenExpiration;
public AccessTokenExpirationDTO getAccessTokenExpiration() {
return accessTokenExpiration;
}
public void setAccessTokenExpiration(AccessTokenExpirationDTO accessTokenExpiration) {
this.accessTokenExpiration = accessTokenExpiration;
}
}

View File

@ -38,7 +38,7 @@ import static org.mockito.Mockito.when;
public class DataTransferExcludedDoSFilterTest {
private static final String DATA_TRANSFER_URI = "/nifi-api/data-transfer";
private static final String ACCESS_URI = "/nifi-api/access";
private static final String CONFIGURATION_URI = "/nifi-api/authentication/configuration";
@Mock
private FilterConfig filterConfig;
@ -62,7 +62,7 @@ public class DataTransferExcludedDoSFilterTest {
@Test
public void testDoFilterChain() throws ServletException, IOException {
when(request.getRequestURI()).thenReturn(ACCESS_URI);
when(request.getRequestURI()).thenReturn(CONFIGURATION_URI);
filter.doFilterChain(filterChain, request, response);

View File

@ -17,7 +17,6 @@
package org.apache.nifi.web.api;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Collections;
import java.util.Optional;
@ -49,41 +48,18 @@ import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authentication.exception.AuthenticationNotSupportedException;
import org.apache.nifi.authentication.exception.IdentityAccessException;
import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.authorization.util.IdentityMappingUtil;
import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
import org.apache.nifi.web.api.dto.AccessStatusDTO;
import org.apache.nifi.web.api.dto.AccessTokenExpirationDTO;
import org.apache.nifi.web.api.entity.AccessConfigurationEntity;
import org.apache.nifi.web.api.entity.AccessStatusEntity;
import org.apache.nifi.web.api.entity.AccessTokenExpirationEntity;
import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.apache.nifi.web.security.LogoutException;
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
import org.apache.nifi.web.security.UntrustedProxyException;
import org.apache.nifi.web.security.cookie.ApplicationCookieName;
import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
import org.apache.nifi.web.security.logout.LogoutRequest;
import org.apache.nifi.web.security.logout.LogoutRequestManager;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.apache.nifi.web.security.x509.X509AuthenticationProvider;
import org.apache.nifi.web.security.x509.X509AuthenticationRequestToken;
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
/**
* RESTful endpoint for managing access.
@ -95,131 +71,12 @@ public class AccessResource extends ApplicationResource {
private static final Logger logger = LoggerFactory.getLogger(AccessResource.class);
protected static final String AUTHENTICATION_NOT_ENABLED_MSG = "User authentication/authorization is only supported when running over HTTPS.";
private X509CertificateExtractor certificateExtractor;
private X509AuthenticationProvider x509AuthenticationProvider;
private X509PrincipalExtractor principalExtractor;
private LoginIdentityProvider loginIdentityProvider;
private JwtAuthenticationProvider jwtAuthenticationProvider;
private JwtLogoutListener jwtLogoutListener;
private JwtDecoder jwtDecoder;
private BearerTokenProvider bearerTokenProvider;
private BearerTokenResolver bearerTokenResolver;
private LogoutRequestManager logoutRequestManager;
/**
* Retrieves the access configuration for this NiFi.
*
* @param httpServletRequest the servlet request
* @return A accessConfigurationEntity
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("config")
@Operation(
summary = "Retrieves the access configuration for this NiFi",
responses = @ApiResponse(content = @Content(schema = @Schema(implementation = AccessConfigurationEntity.class)))
)
public Response getLoginConfig(@Context HttpServletRequest httpServletRequest) {
final AccessConfigurationDTO accessConfiguration = new AccessConfigurationDTO();
// specify whether login should be supported and only support for secure requests
accessConfiguration.setSupportsLogin(loginIdentityProvider != null && httpServletRequest.isSecure());
// create the response entity
final AccessConfigurationEntity entity = new AccessConfigurationEntity();
entity.setConfig(accessConfiguration);
// generate the response
return generateOkResponse(entity).build();
}
/**
* Gets the status the client's access.
*
* @param httpServletRequest the servlet request
* @return A accessStatusEntity
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("")
@Operation(
summary = "Gets the status the client's access",
description = NON_GUARANTEED_ENDPOINT,
responses = @ApiResponse(content = @Content(schema = @Schema(implementation = AccessStatusEntity.class)))
)
@ApiResponses(
value = {
@ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(responseCode = "401", description = "Unable to determine access status because the client could not be authenticated."),
@ApiResponse(responseCode = "403", description = "Unable to determine access status because the client is not authorized to make this request."),
@ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it."),
@ApiResponse(responseCode = "500", description = "Unable to determine access status because an unexpected error occurred.")
}
)
public Response getAccessStatus(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) {
if (!httpServletRequest.isSecure()) {
throw new AuthenticationNotSupportedException(AUTHENTICATION_NOT_ENABLED_MSG);
}
final AccessStatusDTO accessStatus = new AccessStatusDTO();
try {
final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(httpServletRequest);
if (certificates == null) {
final String bearerToken = bearerTokenResolver.resolve(httpServletRequest);
if (bearerToken == null) {
accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name());
accessStatus.setMessage("Access Unknown: Certificate and Token not found.");
} else {
try {
final BearerTokenAuthenticationToken authenticationToken = new BearerTokenAuthenticationToken(bearerToken);
final Authentication authentication = jwtAuthenticationProvider.authenticate(authenticationToken);
final NiFiUserDetails userDetails = (NiFiUserDetails) authentication.getPrincipal();
final String identity = userDetails.getUsername();
accessStatus.setIdentity(identity);
accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
accessStatus.setMessage("Access Granted: Token authenticated.");
} catch (final AuthenticationException iae) {
applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.AUTHORIZATION_BEARER);
throw iae;
}
}
} else {
try {
final String proxiedEntitiesChain = httpServletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN);
final String proxiedEntityGroups = httpServletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITY_GROUPS);
final X509AuthenticationRequestToken x509Request = new X509AuthenticationRequestToken(
proxiedEntitiesChain, proxiedEntityGroups, principalExtractor, certificates, httpServletRequest.getRemoteAddr());
final Authentication authenticationResponse = x509AuthenticationProvider.authenticate(x509Request);
final NiFiUser nifiUser = ((NiFiUserDetails) authenticationResponse.getDetails()).getNiFiUser();
accessStatus.setIdentity(nifiUser.getIdentity());
accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
accessStatus.setMessage("Access Granted: Certificate authenticated.");
} catch (final IllegalArgumentException iae) {
throw new InvalidAuthenticationException(iae.getMessage(), iae);
}
}
} catch (final UntrustedProxyException upe) {
throw new AccessDeniedException(upe.getMessage(), upe);
} catch (final AuthenticationServiceException ase) {
throw new AdministrationException(ase.getMessage(), ase);
}
final AccessStatusEntity entity = new AccessStatusEntity();
entity.setAccessStatus(accessStatus);
return generateOkResponse(entity).build();
}
/**
* Creates a token for accessing the REST API via username/password stored as a cookie in the browser.
*
@ -292,36 +149,6 @@ public class AccessResource extends ApplicationResource {
return generateCreatedResponse(uri, bearerToken).build();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/token/expiration")
@Operation(
summary = "Get expiration for current Access Token",
description = NON_GUARANTEED_ENDPOINT,
responses = @ApiResponse(content = @Content(schema = @Schema(implementation = AccessTokenExpirationEntity.class)))
)
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "Access Token Expiration found"),
@ApiResponse(responseCode = "401", description = "Access Token not authorized"),
@ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
}
)
public Response getAccessTokenExpiration() {
final String bearerToken = bearerTokenResolver.resolve(httpServletRequest);
if (bearerToken == null) {
throw new IllegalStateException("Access Token not found");
} else {
final Jwt jwt = jwtDecoder.decode(bearerToken);
final Instant expiration = jwt.getExpiresAt();
final AccessTokenExpirationDTO accessTokenExpiration = new AccessTokenExpirationDTO();
accessTokenExpiration.setExpiration(expiration);
final AccessTokenExpirationEntity accessTokenExpirationEntity = new AccessTokenExpirationEntity();
accessTokenExpirationEntity.setAccessTokenExpiration(accessTokenExpiration);
return Response.ok(accessTokenExpirationEntity).build();
}
}
@DELETE
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.WILDCARD)
@ -446,30 +273,10 @@ public class AccessResource extends ApplicationResource {
this.bearerTokenResolver = bearerTokenResolver;
}
public void setJwtAuthenticationProvider(JwtAuthenticationProvider jwtAuthenticationProvider) {
this.jwtAuthenticationProvider = jwtAuthenticationProvider;
}
public void setJwtDecoder(final JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
public void setJwtLogoutListener(final JwtLogoutListener jwtLogoutListener) {
this.jwtLogoutListener = jwtLogoutListener;
}
public void setX509AuthenticationProvider(X509AuthenticationProvider x509AuthenticationProvider) {
this.x509AuthenticationProvider = x509AuthenticationProvider;
}
public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
this.principalExtractor = principalExtractor;
}
public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
this.certificateExtractor = certificateExtractor;
}
public void setLogoutRequestManager(LogoutRequestManager logoutRequestManager) {
this.logoutRequestManager = logoutRequestManager;
}

View File

@ -32,7 +32,6 @@ import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.api.dto.AuthenticationConfigurationDTO;
import org.apache.nifi.web.api.entity.AccessConfigurationEntity;
import org.apache.nifi.web.api.entity.AuthenticationConfigurationEntity;
import org.apache.nifi.web.configuration.AuthenticationConfiguration;
import org.apache.nifi.web.util.RequestUriBuilder;
@ -66,7 +65,7 @@ public class AuthenticationResource extends ApplicationResource {
@Path("/configuration")
@Operation(
summary = "Retrieves the authentication configuration endpoint and status information",
responses = @ApiResponse(content = @Content(schema = @Schema(implementation = AccessConfigurationEntity.class)))
responses = @ApiResponse(content = @Content(schema = @Schema(implementation = AuthenticationConfigurationEntity.class)))
)
public Response getAuthenticationConfiguration() {
final AuthenticationConfigurationDTO configuration = new AuthenticationConfigurationDTO();

View File

@ -606,11 +606,6 @@
<bean id="accessResource" class="org.apache.nifi.web.api.AccessResource" scope="singleton">
<property name="logoutRequestManager" ref="logoutRequestManager" />
<property name="loginIdentityProvider" ref="loginIdentityProvider"/>
<property name="x509AuthenticationProvider" ref="x509AuthenticationProvider"/>
<property name="certificateExtractor" ref="certificateExtractor"/>
<property name="principalExtractor" ref="principalExtractor"/>
<property name="jwtAuthenticationProvider" ref="jwtAuthenticationProvider"/>
<property name="jwtDecoder" ref="jwtDecoder" />
<property name="jwtLogoutListener" ref="jwtLogoutListener"/>
<property name="bearerTokenProvider" ref="bearerTokenProvider"/>
<property name="bearerTokenResolver" ref="bearerTokenResolver"/>

View File

@ -74,8 +74,6 @@ import java.util.stream.Collectors;
@EnableMethodSecurity
public class WebSecurityConfiguration {
private static final List<String> UNFILTERED_PATHS = List.of(
"/access",
"/access/config",
"/access/token",
"/access/logout/complete",
"/authentication/configuration"