Skip to content

Commit 89ac5a2

Browse files
authored
feat: introduce error codes for saf authentication (#1692)
* introduce error codes for saf auth Signed-off-by: jandadav <janda.david@gmail.com> * improve coverage Signed-off-by: jandadav <janda.david@gmail.com>
1 parent 51f3db3 commit 89ac5a2

File tree

11 files changed

+249
-54
lines changed

11 files changed

+249
-54
lines changed

apiml-security-common/src/main/java/org/zowe/apiml/security/common/error/AuthExceptionHandler.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,15 @@
99
*/
1010
package org.zowe.apiml.security.common.error;
1111

12-
import org.zowe.apiml.security.common.token.TokenExpireException;
13-
import org.zowe.apiml.security.common.token.TokenFormatNotValidException;
14-
import org.zowe.apiml.security.common.token.TokenNotProvidedException;
15-
import org.zowe.apiml.security.common.token.TokenNotValidException;
16-
import org.zowe.apiml.message.api.ApiMessageView;
17-
import org.zowe.apiml.message.core.MessageService;
1812
import com.fasterxml.jackson.databind.ObjectMapper;
1913
import lombok.extern.slf4j.Slf4j;
2014
import org.springframework.http.HttpStatus;
21-
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
22-
import org.springframework.security.authentication.BadCredentialsException;
23-
import org.springframework.security.authentication.InsufficientAuthenticationException;
15+
import org.springframework.security.authentication.*;
2416
import org.springframework.security.core.AuthenticationException;
2517
import org.springframework.stereotype.Component;
18+
import org.zowe.apiml.message.api.ApiMessageView;
19+
import org.zowe.apiml.message.core.MessageService;
20+
import org.zowe.apiml.security.common.token.*;
2621

2722
import javax.servlet.ServletException;
2823
import javax.servlet.http.HttpServletRequest;
@@ -69,14 +64,24 @@ public void handleException(HttpServletRequest request, HttpServletResponse resp
6964
}
7065
else if (ex instanceof InvalidCertificateException) {
7166
handleInvalidCertificate(response, ex);
72-
} else if (ex instanceof AuthenticationException) {
67+
}
68+
else if (ex instanceof ZosAuthenticationException) {
69+
handleZosAuthenticationException(response, (ZosAuthenticationException) ex);
70+
}
71+
else if (ex instanceof AuthenticationException) {
7372
handleAuthenticationException(request, response, ex);
7473
}
7574
else {
7675
throw new ServletException(ex);
7776
}
7877
}
7978

79+
private void handleZosAuthenticationException(HttpServletResponse response, ZosAuthenticationException ex) throws ServletException {
80+
final ApiMessageView message = messageService.createMessage(ex.getPlatformError().errorMessage, ex.getMessage()).mapToView();
81+
final HttpStatus status = ex.getPlatformError().responseCode;
82+
writeErrorResponse(message, status, response);
83+
}
84+
8085
// 400
8186
private void handleAuthenticationRequired(HttpServletRequest request, HttpServletResponse response, RuntimeException ex) throws ServletException {
8287
log.debug(ERROR_MESSAGE_400, ex.getMessage());
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
11+
package org.zowe.apiml.security.common.error;
12+
13+
import org.springframework.http.HttpStatus;
14+
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
18+
/**
19+
* Provides explanation for error codes for authentication as described at
20+
* documentation for BPX4PWD:
21+
* https://www.ibm.com/support/knowledgecenter/SSLTBW_2.4.0/com.ibm.zos.v2r4.bpxb100/pwd.htm
22+
*/
23+
@SuppressWarnings("squid:S1192") //literal repeated for readability
24+
public enum PlatformPwdErrno {
25+
UNKNOWN("UNKNOWN", 0, "Unknown error",
26+
"org.zowe.apiml.security.platform.errno.UNKNOWN", HttpStatus.INTERNAL_SERVER_ERROR),
27+
EACCES("EACCES", 111, "Permission is denied; the specified password is incorrect",
28+
"org.zowe.apiml.security.platform.errno.EACCES", HttpStatus.UNAUTHORIZED),
29+
EINVAL("EINVAL", 121, "Invalid user name or password is invalid",
30+
"org.zowe.apiml.security.platform.errno.EINVAL", HttpStatus.UNAUTHORIZED),
31+
EMVSERR("EMVSERR", 157, "An MVS environmental error has been detected",
32+
"org.zowe.apiml.security.platform.errno.ERROR", HttpStatus.INTERNAL_SERVER_ERROR),
33+
EMVSEXPIRE("EMVSEXPIRE", 168, "The password for the specified identity has expired",
34+
"org.zowe.apiml.security.platform.errno.EMVSEXPIRE", HttpStatus.UNAUTHORIZED),
35+
EMVSPASSWORD("EMVSPASSWORD", 169, "The new password is not valid",
36+
"org.zowe.apiml.security.platform.errno.EMVSPASSWORD", HttpStatus.BAD_REQUEST),
37+
EMVSSAF2ERR("EMVSSAF2ERR", 164, "An error occurred in the security product",
38+
"org.zowe.apiml.security.platform.errno.ERROR", HttpStatus.UNAUTHORIZED),
39+
EMVSSAFEXTRERR("EMVSSAFEXTRERR", 163, "The username access has been revoked",
40+
"org.zowe.apiml.security.platform.errno.EMVSSAFEXTRERR", HttpStatus.UNAUTHORIZED),
41+
ENOSYS("ENOSYS", 134, "The function is not supported on this system",
42+
"org.zowe.apiml.security.platform.errno.ERROR", HttpStatus.INTERNAL_SERVER_ERROR),
43+
EPERM("EPERM", 139, "The calling address space is not authorized to use this service or a load from a not program-controlled library was done in the address space",
44+
"org.zowe.apiml.security.platform.errno.ERROR", HttpStatus.INTERNAL_SERVER_ERROR),
45+
ESRCH("ESRCH", 143, "The identity that was specified is not defined to the security product",
46+
"org.zowe.apiml.security.platform.errno.ESRCH", HttpStatus.UNAUTHORIZED);
47+
48+
private static final Map<Integer, PlatformPwdErrno> BY_ERRNO = new HashMap<>();
49+
50+
static {
51+
for (PlatformPwdErrno e : values()) {
52+
BY_ERRNO.put(e.errno, e);
53+
}
54+
}
55+
56+
public final String shortErrorName;
57+
public final int errno;
58+
public final String explanation;
59+
public final String errorMessage;
60+
public final HttpStatus responseCode;
61+
62+
private PlatformPwdErrno(String shortErrorName, int errno, String explanation, String errorMessage, HttpStatus responseCode) {
63+
this.shortErrorName = shortErrorName;
64+
this.errno = errno;
65+
this.explanation = explanation;
66+
this.errorMessage = errorMessage;
67+
this.responseCode = responseCode;
68+
}
69+
70+
public static PlatformPwdErrno valueOfErrno(int errno) {
71+
return BY_ERRNO.getOrDefault(errno, null);
72+
}
73+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.error;
11+
12+
import org.springframework.security.core.AuthenticationException;
13+
import org.zowe.apiml.security.common.auth.saf.PlatformReturned;
14+
15+
public class ZosAuthenticationException extends AuthenticationException {
16+
17+
protected final PlatformPwdErrno platformPwdErrno;
18+
19+
private static final long serialVersionUID = 6652673387938170807L;
20+
21+
public ZosAuthenticationException(PlatformReturned platformReturned) {
22+
super("z/OS Authentication exception");
23+
if (platformReturned == null) {
24+
throw new IllegalArgumentException("PlatformReturned must not be null");
25+
}
26+
this.platformPwdErrno = PlatformPwdErrno.valueOfErrno(platformReturned.getErrno());
27+
}
28+
29+
@Override
30+
public String getMessage() {
31+
return platformPwdErrno.shortErrorName + ": " + platformPwdErrno.explanation;
32+
}
33+
34+
public PlatformPwdErrno getPlatformError() {
35+
return platformPwdErrno;
36+
}
37+
}

apiml-security-common/src/main/resources/security-common-log-messages.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,62 @@ messages:
2929
reason: "The service has accepted the authentication of the user but the user does not have access rights to the resource."
3030
action: "Contact your security administrator to give you access."
3131

32+
- key: org.zowe.apiml.security.platform.errno.UNKNOWN
33+
number: ZWEAT409
34+
type: ERROR
35+
text: "The platform returned error: %s"
36+
reason: "The platform responded with unknown errno code."
37+
action: "Please submit an issue with this message."
38+
39+
- key: org.zowe.apiml.security.platform.errno.EACCES
40+
number: ZWEAT410
41+
type: ERROR
42+
text: "The platform returned error: %s"
43+
reason: "The specified password is incorrect."
44+
action: "Provide correct password."
45+
46+
- key: org.zowe.apiml.security.platform.errno.ERROR
47+
number: ZWEAT411
48+
type: ERROR
49+
text: "The platform returned error: %s"
50+
reason: "The platform returned error, specified in the error message."
51+
action: "Contact your security administrator with the message."
52+
53+
- key: org.zowe.apiml.security.platform.errno.EMVSEXPIRE
54+
number: ZWEAT412
55+
type: ERROR
56+
text: "The platform returned error: %s"
57+
reason: "The specified password is expired."
58+
action: "Contact your security administrator to reset your password."
59+
60+
- key: org.zowe.apiml.security.platform.errno.EMVSPASSWORD
61+
number: ZWEAT413
62+
type: ERROR
63+
text: "The platform returned error: %s"
64+
reason: "The new password is not valid."
65+
action: "Provide valid password."
66+
67+
- key: org.zowe.apiml.security.platform.errno.EMVSSAFEXTRERR
68+
number: ZWEAT414
69+
type: ERROR
70+
text: "The platform returned error: %s"
71+
reason: "The user name access has been revoked."
72+
action: "Contact your security administrator to unsuspend your account."
73+
74+
- key: org.zowe.apiml.security.platform.errno.ESRCH
75+
number: ZWEAT415
76+
type: ERROR
77+
text: "The platform returned error: %s"
78+
reason: "The user name does not exist in the system."
79+
action: "Provide correct user name."
80+
81+
- key: org.zowe.apiml.security.platform.errno.EINVAL
82+
number: ZWEAT416
83+
type: ERROR
84+
text: "The platform returned error: %s"
85+
reason: "The specified user name or password is invalid."
86+
action: "Provide correct user name or password."
87+
3288
# TLS,Certificate messages
3389
# 500-599
3490

apiml-security-common/src/test/java/org/zowe/apiml/security/common/error/AuthExceptionHandlerTest.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@
99
*/
1010
package org.zowe.apiml.security.common.error;
1111

12-
import org.zowe.apiml.security.common.token.TokenFormatNotValidException;
13-
import org.zowe.apiml.security.common.token.TokenNotProvidedException;
14-
import org.zowe.apiml.security.common.token.TokenNotValidException;
15-
import org.zowe.apiml.message.core.Message;
16-
import org.zowe.apiml.message.core.MessageService;
17-
import org.zowe.apiml.message.yaml.YamlMessageService;
1812
import com.fasterxml.jackson.databind.ObjectMapper;
1913
import org.junit.jupiter.api.BeforeEach;
2014
import org.junit.jupiter.api.Test;
@@ -27,11 +21,13 @@
2721
import org.springframework.http.MediaType;
2822
import org.springframework.mock.web.MockHttpServletRequest;
2923
import org.springframework.mock.web.MockHttpServletResponse;
30-
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
31-
import org.springframework.security.authentication.AuthenticationServiceException;
32-
import org.springframework.security.authentication.BadCredentialsException;
33-
import org.springframework.security.authentication.InsufficientAuthenticationException;
24+
import org.springframework.security.authentication.*;
3425
import org.springframework.test.context.junit.jupiter.SpringExtension;
26+
import org.zowe.apiml.message.core.Message;
27+
import org.zowe.apiml.message.core.MessageService;
28+
import org.zowe.apiml.message.yaml.YamlMessageService;
29+
import org.zowe.apiml.security.common.auth.saf.PlatformReturned;
30+
import org.zowe.apiml.security.common.token.*;
3531

3632
import javax.servlet.ServletException;
3733
import java.io.IOException;
@@ -178,6 +174,13 @@ void testInvalidCertificateException() throws ServletException {
178174
assertEquals(HttpStatus.FORBIDDEN.value(), httpServletResponse.getStatus());
179175
}
180176

177+
@Test
178+
void testZosAuthenticationExceptionException() throws ServletException {
179+
PlatformReturned platformReturned = PlatformReturned.builder().success(false).errno(PlatformPwdErrno.EACCES.errno).build();
180+
authExceptionHandler.handleException(httpServletRequest, httpServletResponse, new ZosAuthenticationException(platformReturned));
181+
assertEquals(HttpStatus.UNAUTHORIZED.value(), httpServletResponse.getStatus());
182+
}
183+
181184
@Test
182185
void testAuthenticationFailure_whenOccurUnexpectedException() throws ServletException {
183186
assertThrows(ServletException.class, () -> {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
11+
package org.zowe.apiml.security.common.error;
12+
13+
import org.junit.jupiter.api.Nested;
14+
import org.junit.jupiter.api.Test;
15+
import org.zowe.apiml.security.common.auth.saf.PlatformReturned;
16+
17+
import static org.hamcrest.MatcherAssert.assertThat;
18+
import static org.hamcrest.Matchers.is;
19+
20+
class ZosAuthenticationExceptionTest {
21+
22+
private final PlatformReturned returned = PlatformReturned.builder().errno(PlatformPwdErrno.EACCES.errno).build();
23+
private final ZosAuthenticationException underTest = new ZosAuthenticationException(returned);
24+
25+
@Nested
26+
class GivenPlatformReturned {
27+
28+
@Test
29+
void messageSourcedFromEnum() {
30+
assertThat(underTest.getMessage(),
31+
is(PlatformPwdErrno.EACCES.shortErrorName
32+
+ ": " + PlatformPwdErrno.EACCES.explanation));
33+
}
34+
35+
@Test
36+
void enumValueCanBeRetrieved() {
37+
assertThat(underTest.getPlatformError(), is(PlatformPwdErrno.EACCES));
38+
}
39+
}
40+
41+
}

gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/saf/MockPlatformUser.java

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

1212
import org.zowe.apiml.security.common.auth.saf.PlatformReturned;
13+
import org.zowe.apiml.security.common.error.PlatformPwdErrno;
1314

1415
public class MockPlatformUser implements PlatformUser {
1516
public static final String VALID_USERID = "USER";
@@ -23,7 +24,7 @@ public PlatformReturned authenticate(String userid, String password) {
2324
return null;
2425
}
2526
else {
26-
return PlatformReturned.builder().success(false).build();
27+
return PlatformReturned.builder().success(false).errno(PlatformPwdErrno.EACCES.errno).build();
2728
}
2829
}
2930

@@ -32,7 +33,7 @@ public Object changePassword(String userid, String password, String newPassword)
3233
if (userid.equalsIgnoreCase(VALID_USERID) && password.equalsIgnoreCase(VALID_PASSWORD) && !newPassword.equalsIgnoreCase(password)) {
3334
return null;
3435
} else {
35-
return PlatformReturned.builder().success(false).build();
36+
return PlatformReturned.builder().success(false).errno(PlatformPwdErrno.EMVSPASSWORD.errno).build();
3637
}
3738
}
3839
}

gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/saf/ZosAuthenticationException.java

Lines changed: 0 additions & 21 deletions
This file was deleted.

gateway-service/src/main/java/org/zowe/apiml/gateway/security/login/saf/ZosAuthenticationProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
import org.springframework.beans.factory.InitializingBean;
1616
import org.springframework.beans.factory.annotation.Value;
1717
import org.springframework.security.authentication.AuthenticationProvider;
18-
import org.springframework.security.authentication.BadCredentialsException;
1918
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
2019
import org.springframework.security.core.Authentication;
2120
import org.springframework.stereotype.Component;
2221
import org.zowe.apiml.gateway.security.login.LoginProvider;
2322
import org.zowe.apiml.gateway.security.service.AuthenticationService;
2423
import org.zowe.apiml.security.common.auth.saf.PlatformReturned;
24+
import org.zowe.apiml.security.common.error.ZosAuthenticationException;
2525
import org.zowe.apiml.security.common.login.LoginRequest;
2626

2727
@Component
@@ -55,7 +55,7 @@ public Authentication authenticate(Authentication authentication) {
5555
final String jwtToken = authenticationService.createJwtToken(userid, domain, null);
5656
return authenticationService.createTokenAuthentication(userid, jwtToken);
5757
} else {
58-
throw new BadCredentialsException("Username or password are invalid.");
58+
throw new ZosAuthenticationException(returned);
5959
}
6060
}
6161

0 commit comments

Comments
 (0)