diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/config/CompoundAuthProvider.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/config/CompoundAuthProvider.java index c6fcb6f0df..97925d64a7 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/config/CompoundAuthProvider.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/config/CompoundAuthProvider.java @@ -58,7 +58,12 @@ private void warnForDummyProvider(String defaultProviderName) { } private AuthenticationProvider getConfiguredLoginAuthProvider() { - return authProvidersMap.get(loginProvider.getAuthProviderBeanName()); + String providerBeanName = loginProvider.getAuthProviderBeanName(); + AuthenticationProvider authenticationProvider = authProvidersMap.get(providerBeanName); + if (authenticationProvider == null) { + log.warn("Login provider {} is not available.", providerBeanName); + } + return authenticationProvider; } public synchronized String getLoginAuthProviderName() { @@ -95,7 +100,10 @@ public synchronized void setLoginAuthProvider(String provider) { @Override public Authentication authenticate(Authentication authentication) { AuthenticationProvider configuredLoginAuthProvider = getConfiguredLoginAuthProvider(); - return configuredLoginAuthProvider.authenticate(authentication); + if (configuredLoginAuthProvider != null) { + return configuredLoginAuthProvider.authenticate(authentication); + } + return null; } /** @@ -121,6 +129,9 @@ public Authentication authenticate(Authentication authentication) { @Override public boolean supports(Class authentication) { AuthenticationProvider configuredLoginAuthProvider = getConfiguredLoginAuthProvider(); - return configuredLoginAuthProvider.supports(authentication); + if (configuredLoginAuthProvider != null) { + return configuredLoginAuthProvider.supports(authentication); + } + return false; } } diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/config/NewSecurityConfiguration.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/config/NewSecurityConfiguration.java index e4d0f1b778..667c62464b 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/config/NewSecurityConfiguration.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/config/NewSecurityConfiguration.java @@ -28,6 +28,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -41,8 +43,8 @@ import org.zowe.apiml.gateway.controllers.CacheServiceController; import org.zowe.apiml.gateway.controllers.SafResourceAccessController; import org.zowe.apiml.gateway.error.controllers.InternalServerErrorController; -import org.zowe.apiml.gateway.security.login.SuccessfulAccessTokenHandler; import org.zowe.apiml.gateway.security.login.FailedAccessTokenHandler; +import org.zowe.apiml.gateway.security.login.SuccessfulAccessTokenHandler; import org.zowe.apiml.gateway.security.login.x509.X509AuthenticationProvider; import org.zowe.apiml.gateway.security.query.QueryFilter; import org.zowe.apiml.gateway.security.query.SuccessfulQueryHandler; @@ -63,6 +65,7 @@ import org.zowe.apiml.security.common.handler.FailedAuthenticationHandler; import org.zowe.apiml.security.common.login.*; +import java.util.Collections; import java.util.Set; /** @@ -132,7 +135,7 @@ public SecurityFilterChain authenticationFunctionalityFilterChain(HttpSecurity h .anyRequest().permitAll() .and() - .x509() + .x509().userDetailsService(x509UserDetailsService()) .and() .logout() @@ -204,7 +207,7 @@ public SecurityFilterChain accessTokenFilterChain(HttpSecurity http) throws Exce .authorizeRequests() .anyRequest().permitAll() .and() - .x509() + .x509().userDetailsService(x509UserDetailsService()) .and() .authenticationProvider(compoundAuthProvider) // for authenticating credentials .authenticationProvider(tokenAuthenticationProvider) @@ -259,7 +262,7 @@ public SecurityFilterChain authProtectedEndpointsFilterChain(HttpSecurity http) .authorizeRequests() .anyRequest().authenticated() .and() - .x509() + .x509().userDetailsService(x509UserDetailsService()) .and() .authenticationProvider(compoundAuthProvider) // for authenticating credentials .apply(new CustomSecurityFilters()); @@ -273,23 +276,24 @@ public void configure(HttpSecurity http) { .addFilterBefore(loginFilter(http), org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter.class) .addFilterAfter(x509AuthenticationFilter(), org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter.class); } - } - private NonCompulsoryAuthenticationProcessingFilter loginFilter(HttpSecurity http) { - AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); - return new BasicAuthFilter("/**", - handlerInitializer.getAuthenticationFailureHandler(), - securityObjectMapper, - authenticationManager, - handlerInitializer.getResourceAccessExceptionHandler()); - } + private NonCompulsoryAuthenticationProcessingFilter loginFilter(HttpSecurity http) { + AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); + return new BasicAuthFilter("/**", + handlerInitializer.getAuthenticationFailureHandler(), + securityObjectMapper, + authenticationManager, + handlerInitializer.getResourceAccessExceptionHandler()); + } + private X509AuthenticationFilter x509AuthenticationFilter() { + return new X509AuthAwareFilter("/**", + handlerInitializer.getAuthenticationFailureHandler(), + x509AuthenticationProvider); + } - private X509AuthenticationFilter x509AuthenticationFilter() { - return new X509AuthAwareFilter("/**", - handlerInitializer.getAuthenticationFailureHandler(), - x509AuthenticationProvider); } + } @@ -498,7 +502,7 @@ public SecurityFilterChain certificateOrAuthEndpointsFilterChain(HttpSecurity ht // filter out API ML certificate .addFilterBefore(reversedCategorizeCertFilter(), org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter.class); } else { - http.x509(); // default x509 filter, authenticates trusted cert + http.x509().userDetailsService(x509UserDetailsService()); // default x509 filter, authenticates trusted cert } return http.authenticationProvider(compoundAuthProvider) // for authenticating credentials @@ -633,4 +637,9 @@ protected HttpSecurity baseConfigure(HttpSecurity http) throws Exception { .exceptionHandling().authenticationEntryPoint(handlerInitializer.getBasicAuthUnauthorizedHandler()) .and(); } + + private UserDetailsService x509UserDetailsService() { + return username -> new User(username, "", Collections.emptyList()); + } + } diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/dummy/DummyAuthenticationProvider.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/dummy/DummyAuthenticationProvider.java index bb887d4942..c8307ae22e 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/dummy/DummyAuthenticationProvider.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/dummy/DummyAuthenticationProvider.java @@ -11,6 +11,7 @@ package org.zowe.apiml.gateway.security.login.dummy; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -32,6 +33,7 @@ * Allows Gateway to run without mainframe (z/OSMF service) */ @Component +@ConditionalOnProperty(value = "apiml.security.auth.provider", havingValue = "dummy") public class DummyAuthenticationProvider extends DaoAuthenticationProvider { private static final String DUMMY_PROVIDER = "Dummy provider"; diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/dummy/InMemoryUserDetailsService.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/dummy/InMemoryUserDetailsService.java index dd2f8816db..7717fbb2ad 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/dummy/InMemoryUserDetailsService.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/dummy/InMemoryUserDetailsService.java @@ -14,6 +14,7 @@ import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; @@ -30,6 +31,7 @@ */ @Component @Qualifier("dummyService") +@ConditionalOnProperty(value = "apiml.security.auth.provider", havingValue = "dummy") public class InMemoryUserDetailsService implements UserDetailsService { private final BCryptPasswordEncoder passwordEncoder; diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/saf/ZosAuthenticationProvider.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/saf/ZosAuthenticationProvider.java index 7eae78fb88..301087fe92 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/saf/ZosAuthenticationProvider.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/saf/ZosAuthenticationProvider.java @@ -14,6 +14,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -26,6 +27,7 @@ @Component @Slf4j +@ConditionalOnExpression("#{('${apiml.security.auth.provider:zosmf}' == 'zosmf') or ('${apiml.security.auth.provider:zosmf}' == 'dummy') or ('${apiml.security.auth.provider:zosmf}' == 'saf')}") public class ZosAuthenticationProvider implements AuthenticationProvider, InitializingBean { private PlatformUser platformUser = null; diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/zosmf/ZosmfAuthenticationProvider.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/zosmf/ZosmfAuthenticationProvider.java index fc32365286..2ce9612721 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/zosmf/ZosmfAuthenticationProvider.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/zosmf/ZosmfAuthenticationProvider.java @@ -12,6 +12,7 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.ArrayUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -32,6 +33,7 @@ */ @Component @RequiredArgsConstructor +@ConditionalOnProperty(value = "apiml.security.auth.provider", havingValue = "zosmf", matchIfMissing = true) public class ZosmfAuthenticationProvider implements AuthenticationProvider { private final AuthenticationService authenticationService; diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/TokenCreationService.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/TokenCreationService.java index 158568845c..2bc873c9c0 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/TokenCreationService.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/TokenCreationService.java @@ -23,12 +23,14 @@ import org.zowe.apiml.security.common.error.AuthenticationTokenException; import org.zowe.apiml.security.common.token.TokenAuthentication; +import java.util.Optional; + @RequiredArgsConstructor @Service @Slf4j public class TokenCreationService { private final Providers providers; - private final ZosmfAuthenticationProvider zosmfAuthenticationProvider; + private final Optional zosmfAuthenticationProvider; private final PassTicketService passTicketService; private final AuthenticationService authenticationService; @@ -56,7 +58,9 @@ public String createJwtTokenWithoutCredentials(String user) { log.debug("Generating PassTicket for user: {} and ZOSMF applid: {}", user, zosmfApplId); String passTicket = passTicketService.generate(user, zosmfApplId); log.debug("Generated passticket: {}", passTicket); - return ((TokenAuthentication) zosmfAuthenticationProvider.authenticate(new UsernamePasswordAuthenticationToken(user, passTicket))) + return ((TokenAuthentication) zosmfAuthenticationProvider + .orElseThrow(() -> new IllegalStateException("The z/OSMF is not configured. The config value `apiml.security.auth.provider` should be set to `zosmf`.")) + .authenticate(new UsernamePasswordAuthenticationToken(user, passTicket))) .getCredentials(); } catch (IRRPassTicketGenerationException e) { throw new AuthenticationTokenException("Problem with generating PassTicket"); diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java index 5b85c0ffc6..841a473d76 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/TokenCreationServiceTest.java @@ -20,6 +20,8 @@ import org.zowe.apiml.security.common.error.AuthenticationTokenException; import org.zowe.apiml.security.common.token.TokenAuthentication; +import java.util.Optional; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -48,7 +50,7 @@ void setUp() { providers = mock(Providers.class); authenticationService = mock(AuthenticationService.class); - underTest = new TokenCreationService(providers, zosmfAuthenticationProvider, passTicketService, authenticationService); + underTest = new TokenCreationService(providers, Optional.of(zosmfAuthenticationProvider), passTicketService, authenticationService); underTest.zosmfApplId = "IZUDFLT"; } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/AccessTokenServiceTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/AccessTokenServiceTest.java index ffe3aefc79..b04a1058a0 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/AccessTokenServiceTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/pat/AccessTokenServiceTest.java @@ -213,12 +213,12 @@ void givenNotAuthorizedCall_thenDontAllowToRevokeTokensForUser() { given().contentType(ContentType.JSON).body(bodyContent).when() .post(VALIDATE_ENDPOINT) .then().statusCode(200); -// revoke all tokens fro USERNAME +// revoke all tokens for USERNAME Map requestBody = new HashMap<>(); requestBody.put("userId", SecurityUtils.USERNAME); given().contentType(ContentType.JSON).config(SslContext.clientCertApiml).body(requestBody) .when().delete(REVOKE_FOR_USER_ENDPOINT) - .then().statusCode(401); + .then().statusCode(403); // validate after revocation rule given().contentType(ContentType.JSON).body(bodyContent).when() .post(VALIDATE_ENDPOINT) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LoginTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LoginTest.java index 63e55b4e23..705f22984e 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LoginTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/LoginTest.java @@ -17,19 +17,28 @@ import io.restassured.response.Response; import io.restassured.specification.RequestSpecification; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHeaders; import org.json.JSONObject; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.http.HttpStatus; import org.zowe.apiml.security.common.login.LoginRequest; import org.zowe.apiml.util.TestWithStartedInstances; -import org.zowe.apiml.util.categories.*; -import org.zowe.apiml.util.config.*; +import org.zowe.apiml.util.categories.GeneralAuthenticationTest; +import org.zowe.apiml.util.categories.SAFAuthTest; +import org.zowe.apiml.util.categories.zOSMFAuthTest; +import org.zowe.apiml.util.config.ConfigReader; +import org.zowe.apiml.util.config.ItSslConfigFactory; +import org.zowe.apiml.util.config.SslContext; import org.zowe.apiml.util.http.HttpRequestUtils; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Optional; import java.util.stream.Stream; @@ -111,7 +120,7 @@ void givenValidCredentialsInBody(URI loginUrl) { @MethodSource("org.zowe.apiml.integration.authentication.providers.LoginTest#loginUrlsSource") void givenValidCredentialsInHeader(URI loginUrl) { String token = given() - .auth().preemptive().basic(getUsername(), new String(getPassword())) + .auth().preemptive().basic(getUsername(), getPassword()) .contentType(JSON) .when() .post(loginUrl) @@ -155,11 +164,11 @@ void givenInvalidCredentialsInBody(URI loginUrl) { void givenInvalidCredentialsInHeader(URI loginUrl) { String expectedMessage = "Invalid username or password for URL '" + getPath(loginUrl) + "'"; - LoginRequest loginRequest = new LoginRequest(INVALID_USERNAME, INVALID_PASSWORD.toCharArray()); + String headerValue = "Basic " + Base64.getEncoder().encodeToString((INVALID_USERNAME + ":" + INVALID_PASSWORD).getBytes(StandardCharsets.UTF_8)); given() .contentType(JSON) - .body(loginRequest) + .header(HttpHeaders.AUTHORIZATION, headerValue) .when() .post(loginUrl) .then() @@ -168,6 +177,7 @@ void givenInvalidCredentialsInHeader(URI loginUrl) { "messages.find { it.messageNumber == 'ZWEAG120E' }.messageContent", equalTo(expectedMessage) ); } + } @Nested diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/SafLoginTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/SafLoginTest.java index 8d8776f830..51c8d3322b 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/SafLoginTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/providers/SafLoginTest.java @@ -11,16 +11,25 @@ package org.zowe.apiml.integration.authentication.providers; import io.restassured.RestAssured; +import org.apache.http.HttpHeaders; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.zowe.apiml.security.common.login.LoginRequest; import org.zowe.apiml.util.SecurityUtils; import org.zowe.apiml.util.TestWithStartedInstances; import org.zowe.apiml.util.categories.SAFAuthTest; +import org.zowe.apiml.util.config.ConfigReader; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import static io.restassured.RestAssured.given; +import static io.restassured.http.ContentType.JSON; +import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; @@ -52,4 +61,47 @@ void givenValidCredentialsInBody(URI loginUrl) { } } } + + @Nested + class ExpiredPassword { + + private final String USERNAME = ConfigReader.environmentConfiguration().getCredentials().getUser(); + private final String EXPIRED_PASSWORD = "expiredPassword"; + + @ParameterizedTest(name = "givenExpiredAccountCredentialsInBody {index} {0} ") + @MethodSource("org.zowe.apiml.integration.authentication.providers.LoginTest#loginUrlsSource") + void givenExpiredAccountCredentialsInBody(URI loginUrl) { + LoginRequest loginRequest = new LoginRequest(USERNAME, EXPIRED_PASSWORD.toCharArray()); + + given() + .contentType(JSON) + .body(loginRequest) + .when() + .post(loginUrl) + .then() + .statusCode(is(SC_UNAUTHORIZED)) + .body( + "messages.find { it.messageNumber == 'ZWEAT412E' }.messageContent", containsString("expire") + ); + } + + @ParameterizedTest(name = "givenExpiredAccountCredentialsInHeader {index} {0} ") + @MethodSource("org.zowe.apiml.integration.authentication.providers.LoginTest#loginUrlsSource") + void givenExpiredAccountCredentialsInHeader(URI loginUrl) { + String headerValue = "Basic " + Base64.getEncoder().encodeToString((USERNAME + ":" + EXPIRED_PASSWORD).getBytes(StandardCharsets.UTF_8)); + + given() + .contentType(JSON) + .header(HttpHeaders.AUTHORIZATION, headerValue) + .when() + .post(loginUrl) + .then() + .statusCode(is(SC_UNAUTHORIZED)) + .body( + "messages.find { it.messageNumber == 'ZWEAT412E' }.messageContent", containsString("expire") + ); + } + + } + }