/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.oauth;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.jboss.arquillian.graphene.page.Page;
import org.jgroups.protocols.TP;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder;
import org.openqa.selenium.By;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.util.OAuthClient.APP_ROOT;
/**
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
*/
public class OAuthRedirectUriTest extends AbstractKeycloakTest {
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
protected ErrorPage errorPage;
@Page
protected LoginPage loginPage;
private HttpServer server;
@Override
public void beforeAbstractKeycloakTest() throws Exception {
super.beforeAbstractKeycloakTest();
server = HttpServer.create(new InetSocketAddress(8280), 0);
server.createContext("/", new MyHandler());
server.setExecutor(null); // creates a default executor
server.start();
}
@Override
public void afterAbstractKeycloakTest() {
super.afterAbstractKeycloakTest();
server.stop(0);
}
static class MyHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
String response = "Hello";
t.sendResponseHeaders(200, response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
RealmBuilder realm = RealmBuilder.edit(realmRepresentation).testEventListener();
ClientBuilder installedApp = ClientBuilder.create().id("test-installed").name("test-installed")
.redirectUris(Constants.INSTALLED_APP_URN, Constants.INSTALLED_APP_URL)
.secret("password");
realm.client(installedApp);
ClientBuilder installedApp2 = ClientBuilder.create().id("test-installed2").name("test-installed2")
.redirectUris(Constants.INSTALLED_APP_URL + "/myapp")
.secret("password");
realm.client(installedApp2);
ClientBuilder installedApp3 = ClientBuilder.create().id("test-wildcard").name("test-wildcard")
.redirectUris("http://example.com/foo/*", "http://with-dash.example.local/foo/*", "http://localhost:8280/foo/*")
.secret("password");
realm.client(installedApp3);
ClientBuilder installedApp4 = ClientBuilder.create().id("test-dash").name("test-dash")
.redirectUris("http://with-dash.example.local", "http://with-dash.example.local/foo")
.secret("password");
realm.client(installedApp4);
ClientBuilder installedApp5 = ClientBuilder.create().id("test-root-url").name("test-root-url")
.rootUrl("http://with-dash.example.local")
.redirectUris("/foo")
.secret("password");
realm.client(installedApp5);
ClientBuilder installedApp6 = ClientBuilder.create().id("test-relative-url").name("test-relative-url")
.rootUrl("")
.redirectUris("/auth")
.secret("password");
realm.client(installedApp6);
ClientBuilder installedApp7 = ClientBuilder.create().id("test-query-component").name("test-query-component")
.redirectUris("http://localhost?foo=bar", "http://localhost?foo=bar*")
.secret("password");
realm.client(installedApp7);
testRealms.add(realm.build());
}
@Test
public void testNoParam() throws IOException {
oauth.redirectUri(null);
oauth.openLoginForm();
Assert.assertTrue(errorPage.isCurrent());
Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
}
@Test
public void testNoParamMultipleValidUris() throws IOException {
ClientManager.realm(adminClient.realm("test")).clientId("test-app").addRedirectUris("http://localhost:8180/app2");
try {
oauth.redirectUri(null);
oauth.openLoginForm();
Assert.assertTrue(errorPage.isCurrent());
Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
} finally {
ClientManager.realm(adminClient.realm("test")).clientId("test-app").removeRedirectUris("http://localhost:8180/app2");
}
}
@Test
public void testNoParamNoValidUris() throws IOException {
ClientManager.realm(adminClient.realm("test")).clientId("test-app")
.removeRedirectUris("http://localhost:8180/auth/realms/master/app/auth/*");
try {
oauth.redirectUri(null);
oauth.openLoginForm();
Assert.assertTrue(errorPage.isCurrent());
assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
} finally {
ClientManager.realm(adminClient.realm("test")).clientId("test-app").addRedirectUris("http://localhost:8180/auth/realms/master/app/auth/*");
}
}
@Test
public void testNoValidUris() throws IOException {
ClientManager.realm(adminClient.realm("test")).clientId("test-app").removeRedirectUris("http://localhost:8180/auth/realms/master/app/auth/*");
try {
oauth.redirectUri(null);
oauth.openLoginForm();
Assert.assertTrue(errorPage.isCurrent());
assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
} finally {
ClientManager.realm(adminClient.realm("test")).clientId("test-app").addRedirectUris("http://localhost:8180/auth/realms/master/app/auth/*");
}
}
@Test
public void testValid() throws IOException {
oauth.redirectUri(APP_ROOT + "/auth");
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
Assert.assertNotNull(response.getCode());
URL url = new URL(driver.getCurrentUrl());
Assert.assertTrue(url.toString().startsWith(APP_ROOT));
Assert.assertTrue(url.getQuery().contains("code="));
Assert.assertTrue(url.getQuery().contains("state="));
}
@Test
public void testInvalid() throws IOException {
oauth.redirectUri("http://localhost:8180/app2");
oauth.openLoginForm();
Assert.assertTrue(errorPage.isCurrent());
assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
}
@Test
public void testWithParams() throws IOException {
oauth.redirectUri(APP_ROOT + "/auth?key=value");
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
Assert.assertNotNull(response.getCode());
URL url = new URL(driver.getCurrentUrl());
Assert.assertTrue(url.toString().startsWith(APP_ROOT));
Assert.assertTrue(url.getQuery().contains("key=value"));
Assert.assertTrue(url.getQuery().contains("state="));
Assert.assertTrue(url.getQuery().contains("code="));
}
@Test
public void testQueryComponents() throws IOException {
// KEYCLOAK-3420
oauth.clientId("test-query-component");
checkRedirectUri("http://localhost?foo=bar", true);
checkRedirectUri("http://localhost?foo=bara", false);
checkRedirectUri("http://localhost?foo=bar/", false);
checkRedirectUri("http://localhost?foo2=bar2&foo=bar", false);
checkRedirectUri("http://localhost?foo=b", false);
checkRedirectUri("http://localhost?foo", false);
checkRedirectUri("http://localhost?foo=bar&bar=foo", false);
checkRedirectUri("http://localhost?foo&bar=foo", false);
checkRedirectUri("http://localhost?foo&bar", false);
checkRedirectUri("http://localhost", false);
// KEYCLOAK-3418
oauth.clientId("test-installed");
checkRedirectUri("http://localhost?foo=bar", false);
}
@Test
public void testWildcard() throws IOException {
oauth.clientId("test-wildcard");
checkRedirectUri("http://example.com", false);
checkRedirectUri("http://localhost:8080", false, true);
checkRedirectUri("http://example.com/foo", true);
checkRedirectUri("http://example.com/foo/bar", true);
checkRedirectUri("http://localhost:8280/foo", true, true);
checkRedirectUri("http://localhost:8280/foo/bar", true, true);
checkRedirectUri("http://example.com/foobar", false);
checkRedirectUri("http://localhost:8280/foobar", false, true);
}
@Test
public void testDash() throws IOException {
oauth.clientId("test-dash");
checkRedirectUri("http://with-dash.example.local/foo", true);
}
@Test
public void testDifferentCaseInHostname() throws IOException {
oauth.clientId("test-dash");
checkRedirectUri("http://with-dash.example.local", true);
checkRedirectUri("http://wiTh-dAsh.example.local", true);
checkRedirectUri("http://with-dash.example.local/foo", true);
checkRedirectUri("http://wiTh-dAsh.example.local/foo", true);
checkRedirectUri("http://with-dash.example.local/foo", true);
checkRedirectUri("http://wiTh-dAsh.example.local/foo", true);
checkRedirectUri("http://wiTh-dAsh.example.local/Foo", false);
checkRedirectUri("http://wiTh-dAsh.example.local/foO", false);
}
@Test
public void testDifferentCaseInScheme() throws IOException {
oauth.clientId("test-dash");
checkRedirectUri("HTTP://with-dash.example.local", true);
checkRedirectUri("Http://wiTh-dAsh.example.local", true);
}
@Test
public void testRelativeWithRoot() throws IOException {
oauth.clientId("test-root-url");
checkRedirectUri("http://with-dash.example.local/foo", true);
checkRedirectUri("http://localhost:8180/foo", false);
}
@Test
public void testRelative() throws IOException {
oauth.clientId("test-relative-url");
checkRedirectUri("http://with-dash.example.local/foo", false);
checkRedirectUri("http://localhost:8180/auth", true);
}
@Test
public void testLocalhost() throws IOException {
oauth.clientId("test-installed");
checkRedirectUri("urn:ietf:wg:oauth:2.0:oob", true, true);
checkRedirectUri("http://localhost", true);
checkRedirectUri("http://localhost:8280", true, true);
checkRedirectUri("http://localhosts", false);
checkRedirectUri("http://localhost/myapp", false);
checkRedirectUri("http://localhost:8180/myapp", false, true);
oauth.clientId("test-installed2");
checkRedirectUri("http://localhost/myapp", true);
checkRedirectUri("http://localhost:8280/myapp", true, true);
checkRedirectUri("http://localhosts/myapp", false);
checkRedirectUri("http://localhost", false);
checkRedirectUri("http://localhost/myapp2", false);
}
private void checkRedirectUri(String redirectUri, boolean expectValid) throws IOException {
checkRedirectUri(redirectUri, expectValid, false);
}
private void checkRedirectUri(String redirectUri, boolean expectValid, boolean checkCodeToToken) throws IOException {
oauth.redirectUri(redirectUri);
if (!expectValid) {
oauth.openLoginForm();
Assert.assertTrue(errorPage.isCurrent());
Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
} else {
if (!checkCodeToToken) {
oauth.openLoginForm();
Assert.assertTrue(loginPage.isCurrent());
} else {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
Assert.assertNotNull(code);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
Assert.assertEquals("Expected success, but got error: " + tokenResponse.getError(), 200, tokenResponse.getStatusCode());
oauth.doLogout(tokenResponse.getRefreshToken(), "password");
}
}
}
}