Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: functionality for error message in HttpBasicPassTicketScheme #2301

Merged
merged 22 commits into from
Apr 21, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
758ddd8
functionality for error message in HttpBasicPassTicketScheme
AmandaDErrico Apr 14, 2022
279f9aa
updated HttpBasicPassTicketSchemeTest to reflect new PassTicketComman…
AmandaDErrico Apr 19, 2022
664ccf6
cleanup with exceptions, error messages
AmandaDErrico Apr 20, 2022
ede3ae5
Merge branch 'master' into apiml/GH2071/enhance_httpPassTickets
AmandaDErrico Apr 20, 2022
9020852
revert back to throwing exception when generating pass ticket
AmandaDErrico Apr 20, 2022
188b35a
only x-zowe-auth-failure before creating command
AmandaDErrico Apr 20, 2022
080c801
updating apply and applyToRequest with authorizationValue=null
AmandaDErrico Apr 20, 2022
3754eaf
Merge branch 'master' into apiml/GH2071/enhance_httpPassTickets
AmandaDErrico Apr 20, 2022
ce06cbe
added back messageService, and message for auth source error
AmandaDErrico Apr 20, 2022
a398c76
add x-zowe-auth header in apply and applyToRequest, error messages in…
AmandaDErrico Apr 20, 2022
7db0ef4
moved logic - add request header with cookie only if authorizationVal…
AmandaDErrico Apr 20, 2022
8112e90
empty commit to trigger job build
AmandaDErrico Apr 20, 2022
ce7b625
removed import in HttpBasicPassTicketSchemeTest
AmandaDErrico Apr 20, 2022
77cdce4
moved logic for applyToRequest - add request header with cookie only …
AmandaDErrico Apr 21, 2022
d53ac41
added error messages when parsed auth source is null and when its use…
AmandaDErrico Apr 21, 2022
d9dcb5e
added try catch block for parse
AmandaDErrico Apr 21, 2022
116a7e4
removing TokenExpireException from try catch block
AmandaDErrico Apr 21, 2022
13a83ec
removing only TokenNotValidException from try catch block
AmandaDErrico Apr 21, 2022
059c490
added back catch block for exception TokenNotValidException
AmandaDErrico Apr 21, 2022
10b7a0d
Fix NPE in HttpBasicPassTicketScheme.isExpired() method
yelyzavetachebanova Apr 21, 2022
13338c3
Use dedicated method to remove cookie
yelyzavetachebanova Apr 21, 2022
d45e14f
Fix JUnit test
yelyzavetachebanova Apr 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,23 @@
import org.apache.http.HttpRequest;
import org.apache.http.message.BasicHeader;
import org.springframework.stereotype.Component;
import org.zowe.apiml.gateway.security.service.PassTicketException;
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;
import org.zowe.apiml.auth.AuthenticationScheme;
import org.zowe.apiml.security.common.config.AuthConfigurationProperties;
import org.zowe.apiml.security.common.token.TokenExpireException;
import org.zowe.apiml.security.common.token.TokenNotValidException;
import org.zowe.apiml.util.CookieUtil;

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 @@ -42,17 +46,20 @@ 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
AuthConfigurationProperties authConfigurationProperties,
MessageService messageService
) {
this.passTicketService = passTicketService;
this.authSourceService = authSourceService;
this.authConfigurationProperties = authConfigurationProperties;
cookieName = authConfigurationProperties.getCookieProperties().getCookieName();
this.messageService = messageService;
}

@Override
Expand All @@ -62,12 +69,40 @@ 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();

final AuthSource.Parsed parsedAuthSource = authSourceService.parse(authSource);

if (authSource == null || parsedAuthSource == null) {
return AuthenticationCommand.EMPTY;
AuthSource.Parsed parsedAuthSource;
String error;
try {
parsedAuthSource = authSourceService.parse(authSource);
} catch (TokenNotValidException e) {
error = this.messageService.createMessage("org.zowe.apiml.gateway.security.invalidToken").mapToLogMessage();
return new PassTicketCommand(null, cookieName, null, error);
} 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);
}

final String applId = authentication.getApplid();
Expand All @@ -76,9 +111,8 @@ public AuthenticationCommand createCommand(Authentication authentication, AuthSo
try {
passTicket = passTicketService.generate(userId, applId);
} catch (IRRPassTicketGenerationException e) {
throw new PassTicketException(
String.format("Could not generate PassTicket for user ID %s and APPLID %s", userId, applId), e
);
error = String.format("Could not generate PassTicket for user ID %s and APPLID %s", userId, applId);
return new PassTicketCommand(null, cookieName, null, error);
}
final String encoded = Base64.getEncoder()
.encodeToString((userId + ":" + passTicket).getBytes(StandardCharsets.UTF_8));
Expand All @@ -87,7 +121,7 @@ public AuthenticationCommand createCommand(Authentication authentication, AuthSo
final long expiredAt = Math.min(before + authConfigurationProperties.getPassTicket().getTimeout() * 1000,
parsedAuthSource.getExpiration().getTime());

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

@Override
Expand All @@ -105,38 +139,51 @@ public static class PassTicketCommand extends AuthenticationCommand {

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

@Override
public void apply(InstanceInfo instanceInfo) {
final RequestContext context = RequestContext.getCurrentContext();
context.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, authorizationValue);
context.addZuulRequestHeader(COOKIE_HEADER,
CookieUtil.removeCookie(
context.getZuulRequestHeaders().get(COOKIE_HEADER),
cookieName
)
);
if (authorizationValue != null) {
context.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, authorizationValue);
context.addZuulRequestHeader(COOKIE_HEADER,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use JwtCommand.removeCookie() ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

CookieUtil.removeCookie(
context.getZuulRequestHeaders().get(COOKIE_HEADER),
cookieName
)
);
}
else {
JwtCommand.setErrorHeader(context, errorValue);
}
}

@Override
public void applyToRequest(HttpRequest request) {
request.setHeader(
new BasicHeader(HttpHeaders.AUTHORIZATION, authorizationValue)
);
Header header = request.getFirstHeader(COOKIE_HEADER);
if (header != null) {
request.setHeader(COOKIE_HEADER,
CookieUtil.removeCookie(
header.getValue(),
cookieName
)
if (authorizationValue != null) {
request.setHeader(
new BasicHeader(HttpHeaders.AUTHORIZATION, authorizationValue)
);
Header header = request.getFirstHeader(COOKIE_HEADER);
if (header != null) {
request.setHeader(COOKIE_HEADER,
CookieUtil.removeCookie(
header.getValue(),
cookieName
)
);
}
}
else {
request.addHeader(AUTH_FAIL_HEADER, errorValue);
}
}

@Override
public boolean isExpired() {
if (expireAt == null) return false;

return System.currentTimeMillis() > expireAt;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,22 @@
import org.apache.http.HttpRequest;
import org.apache.http.client.methods.HttpGet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.util.ReflectionTestUtils;
import org.zowe.apiml.gateway.security.service.PassTicketException;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSource;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService;
import org.zowe.apiml.gateway.security.service.schema.source.DefaultAuthSourceService;
import org.zowe.apiml.gateway.security.service.schema.source.JwtAuthSource;
import org.zowe.apiml.gateway.security.service.schema.source.JwtAuthSourceService;
import org.zowe.apiml.gateway.security.service.schema.source.X509AuthSourceService;
import org.zowe.apiml.gateway.utils.CleanCurrentRequestContextTest;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.message.yaml.YamlMessageService;
import org.zowe.apiml.passticket.IRRPassTicketGenerationException;
import org.zowe.apiml.passticket.PassTicketService;
import org.zowe.apiml.auth.Authentication;
Expand All @@ -55,6 +57,14 @@ class HttpBasicPassTicketSchemeTest extends CleanCurrentRequestContextTest {
private static final String USERNAME = "USERNAME";
private final AuthConfigurationProperties authConfigurationProperties = new AuthConfigurationProperties();
private HttpBasicPassTicketScheme httpBasicPassTicketScheme;
private static MessageService messageService;


@BeforeAll
static void setForAll() {
messageService = new YamlMessageService();
messageService.loadMessages("/gateway-messages.yml");
}

@BeforeEach
void init() {
Expand All @@ -63,7 +73,7 @@ void init() {

PassTicketService passTicketService = new PassTicketService();
AuthSourceService authSourceService = new DefaultAuthSourceService(jwtAuthSourceService, x509MFAuthSourceService);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties, messageService);
}

@AfterEach
Expand All @@ -75,7 +85,7 @@ void tearEverythingDown() {
void testCreateCommand() {
PassTicketService passTicketService = new PassTicketService();
AuthSourceService authSourceService = mock(AuthSourceService.class);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties, messageService);

Calendar calendar = Calendar.getInstance();
Authentication authentication = new Authentication(AuthenticationScheme.HTTP_BASIC_PASSTICKET, "APPLID");
Expand Down Expand Up @@ -126,7 +136,7 @@ void testCreateCommand() {
void testGetAuthSource() {
PassTicketService passTicketService = mock(PassTicketService.class);
AuthSourceService authSourceService = mock(AuthSourceService.class);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties, messageService);

doReturn(Optional.empty()).when(authSourceService).getAuthSourceFromRequest();

Expand All @@ -138,7 +148,7 @@ void testGetAuthSource() {
void givenRequest_whenApplyToRequest_thenSetsAuthorizationBasic() throws IRRPassTicketGenerationException {
PassTicketService passTicketService = mock(PassTicketService.class);
AuthSourceService authSourceService = mock(AuthSourceService.class);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties, messageService);

Calendar calendar = Calendar.getInstance();
Authentication authentication = new Authentication(AuthenticationScheme.HTTP_BASIC_PASSTICKET, "APPLID");
Expand Down Expand Up @@ -169,24 +179,24 @@ void getExceptionWhenUserIdNotValid() {
String applId = "APPLID";
PassTicketService passTicketService = new PassTicketService();
AuthSourceService authSourceService = mock(AuthSourceService.class);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties, messageService);

Calendar calendar = Calendar.getInstance();
Authentication authentication = new Authentication(AuthenticationScheme.HTTP_BASIC_PASSTICKET, applId);
AuthSource.Parsed parsedSource = new JwtAuthSource.Parsed(UNKNOWN_USER, calendar.getTime(), calendar.getTime(), AuthSource.Origin.ZOWE);
when(authSourceService.parse(new JwtAuthSource("token"))).thenReturn(parsedSource);
AuthSource authSource = new JwtAuthSource("token");
Exception exception = assertThrows(PassTicketException.class,
() -> httpBasicPassTicketScheme.createCommand(authentication, authSource),
"Expected exception is not AuthenticationException");
assertEquals((String.format("Could not generate PassTicket for user ID %s and APPLID %s", UNKNOWN_USER, applId)), exception.getMessage());
String errorMsg = String.format("Could not generate PassTicket for user ID %s and APPLID %s", UNKNOWN_USER, applId);
AuthenticationCommand expected = new HttpBasicPassTicketScheme.PassTicketCommand(null, authConfigurationProperties.getCookieProperties().getCookieName(), null, errorMsg);
AuthenticationCommand actual = httpBasicPassTicketScheme.createCommand(authentication, authSource);
assertEquals(expected, actual);
}

@Test
void testIsRequiredValidJwt() {
PassTicketService passTicketService = new PassTicketService();
AuthSourceService authSourceService = mock(AuthSourceService.class);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties, messageService);

Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 1);
Expand All @@ -198,16 +208,19 @@ void testIsRequiredValidJwt() {
}

@Test
void whenCallWithoutJwt_thenDoNothing() {
void whenCallWithoutJwt_thenNoPassTicketGenerates() {
Authentication authentication = new Authentication(AuthenticationScheme.HTTP_BASIC_PASSTICKET, "applid");
AuthenticationCommand ac = httpBasicPassTicketScheme.createCommand(authentication, null);
assertSame(AuthenticationCommand.EMPTY, ac);
String errorMsg = messageService.createMessage("org.zowe.apiml.gateway.security.schema.missingAuthentication").mapToLogMessage();
AuthenticationCommand expected = new HttpBasicPassTicketScheme.PassTicketCommand(null, authConfigurationProperties.getCookieProperties().getCookieName(), null, errorMsg);

assertEquals(expected, ac);
}

private HttpBasicPassTicketScheme.PassTicketCommand getPassTicketCommand() {
PassTicketService passTicketService = new PassTicketService();
AuthSourceService authSourceService = mock(AuthSourceService.class);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties);
httpBasicPassTicketScheme = new HttpBasicPassTicketScheme(passTicketService, authSourceService, authConfigurationProperties, messageService);

Calendar c = Calendar.getInstance();
c.add(Calendar.YEAR, 1);
Expand All @@ -226,7 +239,7 @@ void givenJwtInCookie_whenApply_thenJwtIsRemoved() {
RequestContext requestContext = new RequestContext();
requestContext.addZuulRequestHeader("cookie",
authConfigurationProperties.getCookieProperties().getCookieName() + "=jwt;" +
"abc=def"
"abc=def"
);
RequestContext.testSetCurrentContext(requestContext);

Expand All @@ -252,7 +265,7 @@ void givenJwtInCookie_whenApplyToRequest_thenJwtIsRemoved() {
HttpRequest httpRequest = new HttpGet();
httpRequest.setHeader("cookie",
authConfigurationProperties.getCookieProperties().getCookieName() + "=jwt;" +
"abc=def"
"abc=def"
);

command.applyToRequest(httpRequest);
Expand Down