Skip to content

Commit 51e8bd3

Browse files
authored
feat: Add support to change password via zOSMF (#2095)
* Add change password support to z/OSMF provider Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add unit tests + refactoring Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix checkstyle Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix change password request body Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Authenticate with new pw in case of change password success Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add forgotten files to git Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Remove unused constructor Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Remove code smells Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add unit tests Signed-off-by: at670475 <andrea.tabone@broadcom.com>
1 parent 0507c5d commit 51e8bd3

File tree

7 files changed

+309
-143
lines changed

7 files changed

+309
-143
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
package org.zowe.apiml.security.common.login;
11+
12+
import com.fasterxml.jackson.annotation.JsonProperty;
13+
import lombok.Data;
14+
import lombok.NoArgsConstructor;
15+
16+
/**
17+
* Represents the change password body request in JSON format
18+
*/
19+
@Data
20+
@NoArgsConstructor
21+
public class ChangePasswordRequest {
22+
@JsonProperty("userID")
23+
private String username;
24+
@JsonProperty("oldPwd")
25+
private String password;
26+
@JsonProperty("newPwd")
27+
private String newPassword;
28+
29+
public ChangePasswordRequest(LoginRequest loginRequest) {
30+
this.username = loginRequest.getUsername();
31+
this.password = loginRequest.getPassword();
32+
this.newPassword = loginRequest.getNewPassword();
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
package org.zowe.apiml.security.common.login;
11+
12+
import org.junit.jupiter.api.Nested;
13+
import org.junit.jupiter.api.Test;
14+
15+
import static org.junit.jupiter.api.Assertions.*;
16+
17+
class ChangePasswordRequestTest {
18+
public static final String NEW_PASS = "newPass";
19+
public static final String PASS = "pass";
20+
public static final String USERNAME = "user";
21+
22+
@Nested
23+
class WhenPassingLoginRequestObject {
24+
LoginRequest loginRequest = new LoginRequest(USERNAME, PASS, NEW_PASS);
25+
ChangePasswordRequest changePasswordRequest = new ChangePasswordRequest(loginRequest);
26+
27+
@Test
28+
void thenMapConstructor() {
29+
assertEquals(loginRequest.getUsername(), changePasswordRequest.getUsername());
30+
assertEquals(loginRequest.getPassword(), changePasswordRequest.getPassword());
31+
assertEquals(loginRequest.getNewPassword(), changePasswordRequest.getNewPassword());
32+
}
33+
}
34+
}

gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/zosmf/ZosmfAuthenticationProvider.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package org.zowe.apiml.gateway.security.login.zosmf;
1111

1212
import lombok.RequiredArgsConstructor;
13+
import org.apache.commons.lang.StringUtils;
1314
import org.springframework.security.authentication.AuthenticationProvider;
1415
import org.springframework.security.authentication.BadCredentialsException;
1516
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -18,6 +19,7 @@
1819
import org.zowe.apiml.gateway.security.service.AuthenticationService;
1920
import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService;
2021
import org.zowe.apiml.security.common.config.AuthConfigurationProperties;
22+
import org.zowe.apiml.security.common.login.LoginRequest;
2123
import org.zowe.apiml.security.common.token.TokenAuthentication;
2224
import org.zowe.apiml.security.common.token.InvalidTokenTypeException;
2325

@@ -44,7 +46,11 @@ public class ZosmfAuthenticationProvider implements AuthenticationProvider {
4446
@Override
4547
public Authentication authenticate(Authentication authentication) {
4648
final String user = authentication.getPrincipal().toString();
47-
49+
final String newPassword = LoginRequest.getNewPassword(authentication);
50+
if (StringUtils.isNotEmpty(newPassword)) {
51+
zosmfService.changePassword(authentication);
52+
authentication = new UsernamePasswordAuthenticationToken(user, newPassword);
53+
}
4854
final ZosmfService.AuthenticationResponse ar = zosmfService.authenticate(authentication);
4955

5056
switch (authConfigurationProperties.getZosmf().getJwtAutoconfiguration()) {

gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/zosmf/ZosmfService.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.springframework.web.client.*;
3131
import org.zowe.apiml.security.common.config.AuthConfigurationProperties;
3232
import org.zowe.apiml.security.common.error.ServiceNotAccessibleException;
33+
import org.zowe.apiml.security.common.login.ChangePasswordRequest;
34+
import org.zowe.apiml.security.common.login.LoginRequest;
3335
import org.zowe.apiml.security.common.token.TokenNotValidException;
3436

3537
import javax.annotation.PostConstruct;
@@ -146,6 +148,16 @@ public AuthenticationResponse authenticate(Authentication authentication) {
146148
return authenticationResponse;
147149
}
148150

151+
@Retryable(maxAttempts = 2, backoff = @Backoff(value = 1500))
152+
public ResponseEntity<String> changePassword(Authentication authentication) {
153+
ResponseEntity<String> changePasswordResponse;
154+
changePasswordResponse = issueChangePasswordRequest(
155+
authentication,
156+
getURI(getZosmfServiceId()) + ZOSMF_AUTHENTICATE_END_POINT,
157+
HttpMethod.PUT);
158+
return changePasswordResponse;
159+
}
160+
149161
/**
150162
* Checks if jwtToken is in the list of invalidated tokens.
151163
*
@@ -237,6 +249,30 @@ protected AuthenticationResponse issueAuthenticationRequest(Authentication authe
237249
}
238250
}
239251

252+
/**
253+
* PUT to provided url and return authentication response
254+
*
255+
* @param authentication
256+
* @param url String containing change password endpoint to be used
257+
* @return ResponseEntity
258+
*/
259+
protected ResponseEntity<String> issueChangePasswordRequest(Authentication authentication, String url, HttpMethod httpMethod) {
260+
log.debug("Changing password via z/OSMF");
261+
final HttpHeaders headers = new HttpHeaders();
262+
headers.add(ZOSMF_CSRF_HEADER, "");
263+
headers.setContentType(MediaType.APPLICATION_JSON);
264+
try {
265+
return restTemplateWithoutKeystore.exchange(
266+
url,
267+
httpMethod,
268+
new HttpEntity<>(new ChangePasswordRequest((LoginRequest) authentication.getCredentials()), headers), String.class);
269+
} catch (RuntimeException re) {
270+
log.warn("The check of z/OSMF JWT authentication endpoint has failed, ensure that the PTF for APAR PH34912 " +
271+
"(https://www.ibm.com/support/pages/apar/PH34912) has been installed. ");
272+
throw handleExceptionOnCall(url, re);
273+
}
274+
}
275+
240276
/**
241277
* Check if call to ZOSMF_AUTHENTICATE_END_POINT resolves
242278
*

gateway-service/src/test/java/org/zowe/apiml/gateway/security/login/zosmf/ZosmfAuthenticationProviderTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService;
3636
import org.zowe.apiml.security.common.config.AuthConfigurationProperties;
3737
import org.zowe.apiml.security.common.error.ServiceNotAccessibleException;
38+
import org.zowe.apiml.security.common.login.LoginRequest;
3839
import org.zowe.apiml.security.common.token.InvalidTokenTypeException;
3940
import org.zowe.apiml.security.common.token.TokenAuthentication;
4041

@@ -52,6 +53,7 @@ class ZosmfAuthenticationProviderTest {
5253

5354
private static final String USERNAME = "user";
5455
private static final String PASSWORD = "password";
56+
private static final String NEW_PASSWORD = "newPassword";
5557
private static final String SERVICE_ID = "service";
5658
private static final String HOST = "localhost";
5759
private static final int PORT = 0;
@@ -179,6 +181,39 @@ void thenThrowException() {
179181
}
180182
}
181183

184+
@Nested
185+
class WhenNewPasswordProvided {
186+
@Test
187+
void thenChangePasswordAndAuthenticate() {
188+
authConfigurationProperties.getZosmf().setServiceId(ZOSMF);
189+
LoginRequest loginRequest = new LoginRequest(USERNAME, PASSWORD, NEW_PASSWORD);
190+
usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(USERNAME, loginRequest);
191+
final Application application = createApplication(zosmfInstance);
192+
when(discovery.getApplication(ZOSMF)).thenReturn(application);
193+
194+
HttpHeaders headers = new HttpHeaders();
195+
headers.add(HttpHeaders.SET_COOKIE, COOKIE1);
196+
headers.add(HttpHeaders.SET_COOKIE, COOKIE2);
197+
when(restTemplate.exchange(Mockito.anyString(),
198+
Mockito.eq(HttpMethod.GET),
199+
Mockito.any(),
200+
Mockito.<Class<Object>>any()))
201+
.thenReturn(new ResponseEntity<>(getResponse(true), headers, HttpStatus.OK));
202+
203+
ZosmfService zosmfService = createZosmfService();
204+
ZosmfAuthenticationProvider zosmfAuthenticationProvider =
205+
new ZosmfAuthenticationProvider(authenticationService, zosmfService, authConfigurationProperties);
206+
207+
mockZosmfRealmRestCallResponse();
208+
Authentication tokenAuthentication
209+
= zosmfAuthenticationProvider.authenticate(usernamePasswordAuthentication);
210+
211+
assertTrue(tokenAuthentication.isAuthenticated());
212+
verify(zosmfService, times(1)).changePassword(any());
213+
assertEquals(USERNAME, tokenAuthentication.getPrincipal());
214+
}
215+
}
216+
182217
@Nested
183218
class WhenNoZosmfInstance {
184219
@Test

0 commit comments

Comments
 (0)