Skip to content

Commit

Permalink
Refactor: authentication schemes exception handling (#2317)
Browse files Browse the repository at this point in the history
* refactor: change exception handling in authentication schemes

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* Fix IT

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* Fix IT

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* Improve test coverage

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* Rename unit test

Signed-off-by: Yelyzaveta Chebanova <[email protected]>
  • Loading branch information
yelyzavetachebanova authored Apr 26, 2022
1 parent e96135a commit 5db1b80
Show file tree
Hide file tree
Showing 25 changed files with 574 additions and 767 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,25 @@ public PassTicketTestController(PassTicketService passTicketService) {
@ApiOperation(value = "Validate that the PassTicket in Authorization header is valid", tags = { "Test Operations" })
@HystrixCommand()
public void passticketTest(@RequestHeader("authorization") String authorization,
@RequestParam(value = "applId", defaultValue = "", required = false) String applId)
throws IRRPassTicketEvaluationException
{
@RequestHeader(value = "X-Zowe-Auth-Failure", required = false) String zoweAuthFailure,
@RequestParam(value = "applId", defaultValue = "", required = false) String applId)
throws IRRPassTicketEvaluationException {
if (authorization != null && authorization.toLowerCase().startsWith("basic")) {
String base64Credentials = authorization.substring("Basic".length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials), StandardCharsets.UTF_8);
String[] values = credentials.split(":", 2);
String userId = values[0];
String passTicket = values[1];
if (applId.isEmpty()) {
applId = defaultApplId;
if (zoweAuthFailure != null) {
throw new IllegalArgumentException("Scheme transformation happened and error was set in X-Zowe-Auth-Failure header");
} else {
String base64Credentials = authorization.substring("Basic".length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials), StandardCharsets.UTF_8);
String[] values = credentials.split(":", 2);
String userId = values[0];
String passTicket = values[1];
if (applId.isEmpty()) {
applId = defaultApplId;
}
passTicketService.evaluate(userId, applId, passTicket);
}
passTicketService.evaluate(userId, applId, passTicket);
} else {
throw new IllegalArgumentException("Missing Basic authorization header");
} else if (authorization != null && !authorization.toLowerCase().startsWith("basic") && zoweAuthFailure == null) {
throw new IllegalArgumentException("Neither scheme transformation happened not error was set in X-Zowe-Auth-Failure header");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@
import org.zowe.apiml.auth.Authentication;
import org.zowe.apiml.gateway.security.service.ServiceAuthenticationServiceImpl;
import org.zowe.apiml.gateway.security.service.schema.AuthenticationCommand;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSchemeException;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSource;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService;
import org.zowe.apiml.security.common.error.InvalidCertificateException;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.security.common.token.TokenExpireException;

import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
import static org.apache.http.HttpStatus.SC_OK;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.*;

Expand All @@ -35,13 +36,17 @@
* load balancer. The request will be modified after specific instance will be selected.
*/
public class ServiceAuthenticationFilter extends PreZuulFilter {
public static final String AUTH_FAIL_HEADER = "X-Zowe-Auth-Failure";

@Autowired
private ServiceAuthenticationServiceImpl serviceAuthenticationService;

@Autowired
private AuthSourceService authSourceService;

@Autowired
private MessageService messageService;

@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER + 6;
Expand All @@ -57,7 +62,6 @@ public Object run() {
final RequestContext context = RequestContext.getCurrentContext();

boolean rejected = false;
boolean badRequest = false;
AuthenticationCommand cmd = null;

final String serviceId = (String) context.get(SERVICE_ID_KEY);
Expand All @@ -68,15 +72,23 @@ public Object run() {

// Verify authentication source validity if it is required for the schema
if (authSource.isPresent() && !isSourceValidForCommand(authSource.get(), cmd)) {
rejected = true;
throw new AuthSchemeException("org.zowe.apiml.gateway.security.invalidToken");
}
} catch (TokenExpireException tee) {
cmd = null;
} catch (InvalidCertificateException ice) {
rejected = true;
badRequest = true;
} catch (AuthenticationException ae) {
rejected = true;
} catch (AuthSchemeException ase) {
String error;
if (ase.getParams() != null) {
error = this.messageService.createMessage(ase.getMessage(), (Object[]) ase.getParams()).mapToLogMessage();
} else {
error = this.messageService.createMessage(ase.getMessage()).mapToLogMessage();
}
context.addZuulRequestHeader(AUTH_FAIL_HEADER, error);
context.addZuulResponseHeader(AUTH_FAIL_HEADER, error);
context.setResponseStatusCode(SC_OK);
return null;
} catch (Exception e) {
throw new ZuulRuntimeException(
new ZuulException(e, HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getLocalizedMessage())
Expand All @@ -85,7 +97,7 @@ public Object run() {

if (rejected) {
context.setSendZuulResponse(false);
context.setResponseStatusCode(badRequest ? SC_BAD_REQUEST : SC_UNAUTHORIZED);
context.setResponseStatusCode(SC_UNAUTHORIZED);
} else if (cmd != null) {
try {
// Update ZUUL context by authentication schema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.zowe.apiml.gateway.security.service.schema.source.X509AuthSourceService;
import org.zowe.apiml.gateway.security.service.schema.source.X509CNAuthSourceService;
import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.passticket.PassTicketService;
import org.zowe.apiml.security.common.config.AuthConfigurationProperties;

Expand Down Expand Up @@ -71,8 +70,8 @@ public Providers loginProviders(
*/
@Bean
@Qualifier("x509MFAuthSourceService")
public X509AuthSourceService getX509MFAuthSourceService(X509AbstractMapper mapper, TokenCreationService tokenCreationService, AuthenticationService authenticationService, MessageService messageService) {
return new X509AuthSourceService(mapper, tokenCreationService, authenticationService, messageService);
public X509AuthSourceService getX509MFAuthSourceService(X509AbstractMapper mapper, TokenCreationService tokenCreationService, AuthenticationService authenticationService) {
return new X509AuthSourceService(mapper, tokenCreationService, authenticationService);
}

/**
Expand All @@ -82,7 +81,7 @@ public X509AuthSourceService getX509MFAuthSourceService(X509AbstractMapper mappe
*/
@Bean
@Qualifier("x509CNAuthSourceService")
public X509AuthSourceService getX509CNAuthSourceService(TokenCreationService tokenCreationService, AuthenticationService authenticationService, MessageService messageService) {
return new X509CNAuthSourceService(new X509CommonNameUserMapper(), tokenCreationService, authenticationService, messageService);
public X509AuthSourceService getX509CNAuthSourceService(TokenCreationService tokenCreationService, AuthenticationService authenticationService) {
return new X509CNAuthSourceService(new X509CommonNameUserMapper(), tokenCreationService, authenticationService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import org.apache.http.HttpRequest;
import org.apache.http.message.BasicHeader;
import org.springframework.stereotype.Component;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSchemeException;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSource;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.passticket.IRRPassTicketGenerationException;
import org.zowe.apiml.passticket.PassTicketService;
import org.zowe.apiml.auth.Authentication;
Expand All @@ -34,8 +34,6 @@
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import static org.zowe.apiml.gateway.security.service.schema.X509Scheme.AUTH_FAIL_HEADER;

/**
* This bean support PassTicket. Bean is responsible for getting PassTicket from
* SAF and generating new authentication header in request.
Expand All @@ -46,20 +44,17 @@ public class HttpBasicPassTicketScheme implements IAuthenticationScheme {
private final PassTicketService passTicketService;
private final AuthSourceService authSourceService;
private final AuthConfigurationProperties authConfigurationProperties;
private final MessageService messageService;
private final String cookieName;

public HttpBasicPassTicketScheme(
PassTicketService passTicketService,
AuthSourceService authSourceService,
AuthConfigurationProperties authConfigurationProperties,
MessageService messageService
AuthConfigurationProperties authConfigurationProperties
) {
this.passTicketService = passTicketService;
this.authSourceService = authSourceService;
this.authConfigurationProperties = authConfigurationProperties;
cookieName = authConfigurationProperties.getCookieProperties().getCookieName();
this.messageService = messageService;
}

@Override
Expand All @@ -69,40 +64,26 @@ public AuthenticationScheme getScheme() {

@Override
public AuthenticationCommand createCommand(Authentication authentication, AuthSource authSource) {
final RequestContext context = RequestContext.getCurrentContext();
// Check for error in context to use it in header "X-Zowe-Auth-Failure"
if (context.containsKey(AUTH_FAIL_HEADER)) {
String errorHeaderValue = context.get(AUTH_FAIL_HEADER).toString();
// this command should expire immediately after creation because it is build based on missing/incorrect authentication
return new PassTicketCommand(null, cookieName, System.currentTimeMillis(), errorHeaderValue);
}

final long before = System.currentTimeMillis();

if (authSource == null || authSource.getRawSource() == null) {
throw new AuthSchemeException("org.zowe.apiml.gateway.security.schema.missingAuthentication");
}

AuthSource.Parsed parsedAuthSource;
String error;
try {
parsedAuthSource = authSourceService.parse(authSource);
if (parsedAuthSource == null) {
throw new IllegalStateException("Error occurred while parsing authentication source");
}

if (parsedAuthSource.getUserId() == null) {
throw new AuthSchemeException("org.zowe.apiml.gateway.security.schema.x509.mappingFailed");
}
} catch (TokenNotValidException e) {
error = this.messageService.createMessage("org.zowe.apiml.gateway.security.invalidToken").mapToLogMessage();
return new PassTicketCommand(null, cookieName, null, error);
throw new AuthSchemeException("org.zowe.apiml.gateway.security.invalidToken");
} catch (TokenExpireException e) {
error = this.messageService.createMessage("org.zowe.apiml.gateway.security.expiredToken").mapToLogMessage();
return new PassTicketCommand(null, cookieName, null, error);
}

if (authSource == null || authSource.getRawSource() == null) {
error = this.messageService.createMessage("org.zowe.apiml.gateway.security.schema.missingAuthentication").mapToLogMessage();
return new PassTicketCommand(null, cookieName, null, error);
}
else if (parsedAuthSource == null) { // invalid authSource - can be due to
error = this.messageService.createMessage("org.zowe.apiml.gateway.security.scheme.x509ParsingError", "Cannot parse provided authentication source").mapToLogMessage();
return new PassTicketCommand(null, cookieName, null, error);
}
else if (parsedAuthSource.getUserId() == null) {
error = this.messageService.createMessage("org.zowe.apiml.gateway.security.schema.x509.mappingFailed").mapToLogMessage();
return new PassTicketCommand(null, cookieName, null, error);
throw new AuthSchemeException("org.zowe.apiml.gateway.security.expiredToken");
}

final String applId = authentication.getApplid();
Expand All @@ -111,17 +92,18 @@ else if (parsedAuthSource.getUserId() == null) {
try {
passTicket = passTicketService.generate(userId, applId);
} catch (IRRPassTicketGenerationException e) {
error = String.format("Could not generate PassTicket for user ID %s and APPLID %s", userId, applId);
return new PassTicketCommand(null, cookieName, null, error);
String error = String.format("Could not generate PassTicket for user ID %s and APPLID %s", userId, applId);
throw new AuthSchemeException("org.zowe.apiml.security.ticket.generateFailed", error);
}
final String encoded = Base64.getEncoder()
.encodeToString((userId + ":" + passTicket).getBytes(StandardCharsets.UTF_8));
final String value = "Basic " + encoded;

final long expiredAt = Math.min(before + authConfigurationProperties.getPassTicket().getTimeout() * 1000,
parsedAuthSource.getExpiration().getTime());
final long defaultExpirationTime = before + authConfigurationProperties.getPassTicket().getTimeout() * 1000L;
final long expirationTime = parsedAuthSource.getExpiration() != null ? parsedAuthSource.getExpiration().getTime() : defaultExpirationTime;
final Long expireAt = Math.min(defaultExpirationTime, expirationTime);

return new PassTicketCommand(value, cookieName, expiredAt, null);
return new PassTicketCommand(value, cookieName, expireAt);
}

@Override
Expand All @@ -137,21 +119,17 @@ public static class PassTicketCommand extends AuthenticationCommand {

private static final String COOKIE_HEADER = "cookie";

private final String authorizationValue;
private final String cookieName;
private final Long expireAt;
private final String errorValue;
String authorizationValue;
String cookieName;
Long expireAt;

@Override
public void apply(InstanceInfo instanceInfo) {
final RequestContext context = RequestContext.getCurrentContext();
if (authorizationValue != null) {
final RequestContext context = RequestContext.getCurrentContext();
context.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, authorizationValue);
JwtCommand.removeCookie(context, cookieName);
}
else {
JwtCommand.setErrorHeader(context, errorValue);
}
}

@Override
Expand All @@ -170,9 +148,6 @@ public void applyToRequest(HttpRequest request) {
);
}
}
else {
request.addHeader(AUTH_FAIL_HEADER, errorValue);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
package org.zowe.apiml.gateway.security.service.schema;

import com.netflix.zuul.context.RequestContext;
import org.apache.http.HttpRequest;
import org.zowe.apiml.util.CookieUtil;
import org.zowe.apiml.util.Cookies;

Expand All @@ -19,7 +18,6 @@
public abstract class JwtCommand extends AuthenticationCommand {

public static final String COOKIE_HEADER = "cookie";
public static final String AUTH_FAIL_HEADER = "X-Zowe-Auth-Failure";

public static void createCookie(Cookies cookies, String name, String token) {
HttpCookie jwtCookie = new HttpCookie(name, token);
Expand All @@ -39,15 +37,6 @@ public static void setCookie(RequestContext context, String name, String value)
);
}

public static void setErrorHeader(RequestContext context, String value) {
context.addZuulRequestHeader(AUTH_FAIL_HEADER, value);
context.addZuulResponseHeader(AUTH_FAIL_HEADER, value);
}

public static void addErrorHeader(HttpRequest request, String value) {
request.addHeader(AUTH_FAIL_HEADER, value);
}

public static void removeCookie(RequestContext context, String name) {
context.addZuulRequestHeader(COOKIE_HEADER,
CookieUtil.removeCookie(
Expand Down
Loading

0 comments on commit 5db1b80

Please sign in to comment.