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

Add OAuth2redirectTest #1310

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
50afb3d
Test setup
christiangoerdes Oct 17, 2024
7e27c12
implemented simple test (WIP!).
christiangoerdes Oct 17, 2024
cbd9013
test extension (WIP)
christiangoerdes Oct 17, 2024
7ae6c3c
Add OAuth2 redirect test cases and update dependencies
t-burch Oct 23, 2024
354e296
Add URL decoding and response validation in OAuth2RedirectTest
christiangoerdes Oct 23, 2024
de57f31
Merge branch 'master' into OAuth2redirectTest
christiangoerdes Oct 24, 2024
ed0ee30
Improve OAuth2 redirect test functionality and enhance code clarity
t-burch Oct 24, 2024
7b42879
Merge remote-tracking branch 'origin/OAuth2redirectTest' into OAuth2r…
t-burch Oct 24, 2024
4cbea77
Implement OAuth2 flow enhancements and logging configuration
christiangoerdes Oct 24, 2024
a9ff3bd
WIP:
christiangoerdes Oct 24, 2024
8aa3071
.
t-burch Oct 30, 2024
81bf870
switched to relative path
christiangoerdes Oct 30, 2024
3f3c2ec
New consentfile
t-burch Oct 30, 2024
89b2d40
Finished OAuth2RedirectTest
christiangoerdes Oct 30, 2024
cee573f
refactoring
christiangoerdes Oct 30, 2024
f015128
Revert LogHelper tmp changes
christiangoerdes Oct 30, 2024
31f79e8
refactoring and code optimization
christiangoerdes Oct 30, 2024
64152a5
Merge branch 'master' into OAuth2redirectTest
rrayst Oct 31, 2024
a02fed0
Merge branch 'master' into OAuth2redirectTest
t-burch Nov 4, 2024
a96fc71
Merge branch 'master' into OAuth2redirectTest
t-burch Nov 6, 2024
6e4ace7
added check that URL survives encoding
rrayst Nov 7, 2024
ce7e28a
removed unnecessary file
rrayst Nov 7, 2024
b158620
First prototype of request continuation instead of redirection after …
t-burch Nov 8, 2024
55dbc3a
Add redirect handling in OAuth2CallbackRequestHandler and update OAut…
t-burch Nov 11, 2024
62d9808
Implement OAuth2 redirect handling in the doRedirect method
t-burch Nov 11, 2024
e68f5c7
Merge branch 'master' into OAuth2redirectTest
t-burch Nov 11, 2024
afcf054
Call the handler in OAuth2CallbackRequestHandler to process redirecti…
t-burch Nov 11, 2024
998fa57
Refactor OAuth2 redirect test to streamline steps for GET and POST re…
t-burch Nov 11, 2024
e71e139
Add assertion to verify URL encoding in OAuth2RedirectTest
t-burch Nov 11, 2024
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 @@ -165,6 +165,8 @@ private void parseSrc(InputStream resolve) throws IOException {

// without checks
tokenEndpoint = (String) json.get("token_endpoint");
if (tokenEndpoint == null)
throw new RuntimeException("No token_endpoint could be detected.");
userInfoEndpoint = (String) json.get("userinfo_endpoint");
authorizationEndpoint = (String) json.get("authorization_endpoint");
revocationEndpoint = (String) json.get("revocation_endpoint");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@ public class LogHelper {
private LogInterceptor logi;

public LogHelper() {
if (log.isDebugEnabled()) {
if (true) {
christiangoerdes marked this conversation as resolved.
Show resolved Hide resolved
logi = new LogInterceptor();
logi.setHeaderOnly(false);
}
}

public void handleRequest(Exchange e) throws Exception {
if (log.isDebugEnabled()) {
if (true) {
logi.handleRequest(e);
}
}

public void handleResponse(Exchange e) throws Exception {
if (log.isDebugEnabled()) {
if (true) {
logi.handleResponse(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
import org.jetbrains.annotations.NotNull;

import java.math.BigInteger;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;

import static com.predic8.membrane.core.interceptor.session.SessionManager.SESSION_VALUE_SEPARATOR;
import static java.nio.charset.StandardCharsets.UTF_8;

public class StateManager {

Expand All @@ -39,7 +42,7 @@ public static String getSecurityTokenFromState(String state2) {
if (state2 == null)
throw new RuntimeException("No CSRF token.");

Map<String, String> param = URLParamUtil.parseQueryString(state2, URLParamUtil.DuplicateKeyOrInvalidFormStrategy.ERROR);
Map<String, String> param = URLParamUtil.parseQueryString(URLDecoder.decode(state2, UTF_8), URLParamUtil.DuplicateKeyOrInvalidFormStrategy.ERROR);
rrayst marked this conversation as resolved.
Show resolved Hide resolved

if (!param.containsKey("security_token"))
throw new RuntimeException("No CSRF token.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
IllegalCharactersInURLTest.class,
ProxySSLTest.class,
SessionManager.class,
OpenApiRewriteIntegrationTest.class
OpenApiRewriteIntegrationTest.class,
OAuth2RedirectTest.class
})
public class IntegrationTestsWithoutInternet {
}
317 changes: 317 additions & 0 deletions core/src/test/java/com/predic8/membrane/core/OAuth2RedirectTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
package com.predic8.membrane.core;

import com.predic8.membrane.core.exchangestore.ForgetfulExchangeStore;
import com.predic8.membrane.core.interceptor.LogInterceptor;
import com.predic8.membrane.core.interceptor.authentication.session.StaticUserDataProvider;
import com.predic8.membrane.core.interceptor.flow.ConditionalInterceptor;
import com.predic8.membrane.core.interceptor.misc.ReturnInterceptor;
import com.predic8.membrane.core.interceptor.oauth2.ClaimList;
import com.predic8.membrane.core.interceptor.oauth2.Client;
import com.predic8.membrane.core.interceptor.oauth2.OAuth2AuthorizationServerInterceptor;
import com.predic8.membrane.core.interceptor.oauth2.StaticClientList;
import com.predic8.membrane.core.interceptor.oauth2.authorizationservice.MembraneAuthorizationService;
import com.predic8.membrane.core.interceptor.oauth2.tokengenerators.BearerTokenGenerator;
import com.predic8.membrane.core.interceptor.oauth2client.OAuth2Resource2Interceptor;
import com.predic8.membrane.core.interceptor.oauth2client.SessionOriginalExchangeStore;
import com.predic8.membrane.core.interceptor.session.InMemorySessionManager;
import com.predic8.membrane.core.interceptor.templating.StaticInterceptor;
import com.predic8.membrane.core.rules.Rule;
import com.predic8.membrane.core.rules.ServiceProxy;
import com.predic8.membrane.core.rules.ServiceProxyKey;
import com.predic8.membrane.core.transport.http.HttpTransport;
import io.restassured.response.Response;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.predic8.membrane.core.interceptor.LogInterceptor.Level.DEBUG;
import static com.predic8.membrane.core.interceptor.flow.ConditionalInterceptor.LanguageType.SPEL;
import static io.restassured.RestAssured.given;
import static org.apache.http.HttpHeaders.LOCATION;
import static org.hamcrest.text.IsEqualIgnoringCase.equalToIgnoringCase;
import static org.hamcrest.text.MatchesPattern.matchesPattern;

public class OAuth2RedirectTest {

static Router azureRouter;
static Router membraneRouter;
static Router nginxRouter;

@BeforeAll
static void setup() throws Exception {
Rule azureRule = getAzureRule();
christiangoerdes marked this conversation as resolved.
Show resolved Hide resolved
Rule membraneRule = getMembraneRule();
Rule nginxRule = getNginxRule();

azureRouter = startProxyRule(azureRule);
membraneRouter = startProxyRule(membraneRule);
nginxRouter = startProxyRule(nginxRule);
}

private static final String CLIENT_URL = "http://localhost:2000";
christiangoerdes marked this conversation as resolved.
Show resolved Hide resolved

private static final String AUTH_SERVER_URL = "http://localhost:2002";


// @formatter:off
@Test
void testGet() {
Map<String, String> cookies = new HashMap<>();
Map<String, String> memCookies = new HashMap<>();

// Step 1: Initial request to the client
Response clientResponse = step1originalRequest(memCookies);
christiangoerdes marked this conversation as resolved.
Show resolved Hide resolved

// Step 2: Send to authentication at OAuth2 server
Response formRedirect = step2sendAuthToOAuth2Server(cookies, clientResponse);

// Step 3: Open login page
step3openLoginPage(cookies, formRedirect);

// Step 4: Submit login
step4submitLogin(cookies, formRedirect);

// Step 5: Redirect to consent
Response consentRedirect = step5redirectToConsent(cookies);

// Step 6: Open consent dialog
step6openConsentDialog(cookies, consentRedirect);

// Step 7: Submit consent
step7submitConsent(cookies, consentRedirect);

// Step 8: Redirect back to client
Response clientRedirect = step8redirectToClient(cookies);

// Step 9: Exchange Code for Token
step9exchangeCodeForToken(memCookies, clientRedirect);

// Step 10: Make the authenticated POST request
step10makeAuthPostRequest(memCookies);
}

private static void step10makeAuthPostRequest(Map<String,String> memCookies) {
given()
.cookies(memCookies)
.when()
.post(CLIENT_URL)
.then()
.body(equalToIgnoringCase("get"));
}

private static void step9exchangeCodeForToken(Map<String,String> memCookies, Response clientRedirect) {
given()
.redirects().follow(false)
.cookies(memCookies)
.when()
.post(clientRedirect.getHeader(LOCATION))
.then()
.statusCode(307)
.header(LOCATION, "/")
.extract().response();
}

private static Response step8redirectToClient(Map<String,String> cookies) {
return given()
.redirects().follow(false)
.cookies(cookies)
.when()
.post(AUTH_SERVER_URL)
.then()
.statusCode(307)
.header(LOCATION, matchesPattern(CLIENT_URL + ".*"))
.extract().response();
}

private static void step7submitConsent(Map<String,String> cookies, Response consentRedirect) {
given()
.redirects().follow(false)
.cookies(cookies)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Accept-Charset", "UTF-8")
.formParam("consent", "Accept")
.when()
.post(AUTH_SERVER_URL + consentRedirect.getHeader(LOCATION))
.then()
.statusCode(200)
.header(LOCATION, "/")
.extract().response();
}

private static void step6openConsentDialog(Map<String,String> cookies, Response consentRedirect) {
given()
.redirects().follow(false)
.cookies(cookies)
.when()
.get(AUTH_SERVER_URL + consentRedirect.getHeader(LOCATION))
.then()
.statusCode(200)
.extract().response();
}

private static Response step5redirectToConsent(Map<String,String> cookies) {
return given()
.redirects().follow(false)
.cookies(cookies)
.when()
.get(AUTH_SERVER_URL)
.then()
.statusCode(307)
.header(LOCATION, matchesPattern("/login/consent.*"))
.extract().response();
}

private static void step4submitLogin(Map<String,String> cookies, Response formRedirect) {
given()
.redirects().follow(false)
.cookies(cookies)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Accept-Charset", "UTF-8")
.formParam("username", "user")
.formParam("password", "password")
.when()
.post(AUTH_SERVER_URL + formRedirect.getHeader(LOCATION))
.then()
.statusCode(200)
.header(LOCATION, "/")
.extract().response();
}

private static void step3openLoginPage(Map<String,String> cookies, Response formRedirect) {
given()
.redirects().follow(true)
.cookies(cookies)
.when()
.get(AUTH_SERVER_URL + formRedirect.getHeader(LOCATION))
.then()
.statusCode(200)
.extract().response();
}

private static @NotNull Response step2sendAuthToOAuth2Server(Map<String,String> cookies, Response response) {
Response formRedirect =
given()
.redirects().follow(false)
.cookies(cookies)
.urlEncodingEnabled(false)
.when()
.get(response.getHeader(LOCATION))
.then()
.statusCode(307)
.header(LOCATION, matchesPattern("/login.*"))
.extract().response();
cookies.putAll(formRedirect.getCookies());
return formRedirect;
}

private static @NotNull Response step1originalRequest(Map<String,String> memCookies) {
Response response =
given()
.redirects().follow(false)
.when()
.get(CLIENT_URL)
.then()
.statusCode(307)
.header(LOCATION, matchesPattern(AUTH_SERVER_URL + ".*"))
.extract().response();
memCookies.putAll(response.getCookies());
return response;
}

// @formatter:on
private static ConditionalInterceptor createConditionalInterceptorWithReturnMessage(String test, String returnMessage) {
return new ConditionalInterceptor() {{
setLanguage(SPEL);
setTest(test);
setInterceptors(List.of(new StaticInterceptor() {{
setTextTemplate(returnMessage);
}}));
}};
}

@AfterAll
public static void tearDown() throws IOException {
membraneRouter.shutdown();
azureRouter.shutdown();
nginxRouter.shutdown();
}

private static Router startProxyRule(Rule azureRule) throws Exception {
Router router = new Router();
router.setExchangeStore(new ForgetfulExchangeStore());
router.setTransport(new HttpTransport());
router.getRuleManager().addProxyAndOpenPortIfNew(azureRule);
router.init();
return router;
}

private static @NotNull Rule getNginxRule() {
Rule nginxRule = new ServiceProxy(new ServiceProxyKey("localhost", "*", ".*", 2001), "localhost", 80);
nginxRule.getInterceptors().add(createConditionalInterceptorWithReturnMessage("method == 'POST'", "POST"));
nginxRule.getInterceptors().add(createConditionalInterceptorWithReturnMessage("method == 'GET'", "GET"));
nginxRule.getInterceptors().add(new ReturnInterceptor());
return nginxRule;
}

private static @NotNull Rule getMembraneRule() {
Rule membraneRule = new ServiceProxy(new ServiceProxyKey("localhost", "*", ".*", 2000), "localhost", 2001);
membraneRule.getInterceptors().add(new OAuth2Resource2Interceptor() {{
setSessionManager(new InMemorySessionManager());
setAuthService(new MembraneAuthorizationService() {{
setSrc("http://localhost:2002");
setClientId("abc");
setClientSecret("def");
setScope("openid profile");
}});
setOriginalExchangeStore(new SessionOriginalExchangeStore());
}});
return membraneRule;
}

private static @NotNull Rule getAzureRule() {
Rule azureRule = new ServiceProxy(new ServiceProxyKey("localhost", "*", ".*", 2002), "localhost", 80);
azureRule.getInterceptors().add(new LogInterceptor() {{
setLevel(DEBUG);
}});
azureRule.getInterceptors().add(new OAuth2AuthorizationServerInterceptor() {{
setLocation("src/test/resources/openId/dialog");
setConsentFile("src/test/resources/openId/consentFile.json");
setTokenGenerator(new BearerTokenGenerator());
setIssuer("http://localhost:2002");
setUserDataProvider(new StaticUserDataProvider() {{
setUsers(List.of(new User() {{
setUsername("user");
setPassword("password");
}}));
}});
setClientList(new StaticClientList() {{
setClients(List.of(new Client() {{
setClientId("abc");
setClientSecret("def");
setCallbackUrl("http://localhost:2000/oauth2callback");
}}));
}});
setClaimList(new ClaimList() {{
setValue("aud email iss sub username");
setScopes(new ArrayList<>() {{
add(new Scope() {{
setId("username");
setClaims("username");
}});
add(new Scope() {{
setId("profile");
setClaims("username email");
}});
}});
}});
}});
return azureRule;
}
}
Loading
Loading