Details
diff --git a/forms/common-themes/src/main/resources/theme/login/base/code.ftl b/forms/common-themes/src/main/resources/theme/login/base/code.ftl
index 84a98a5..23ca6a8 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/code.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/code.ftl
@@ -12,7 +12,7 @@
<p>Please copy this code and paste it into your application:</p>
<textarea id="code" class="${properties.kcTextareaClass!}">${code.code}</textarea>
<#else>
- <p>${code.error}</p>
+ <p id="error">${code.error}</p>
</#if>
</div>
</#if>
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 5ff6ca3..47be8e9 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -560,7 +560,7 @@ public class AccountService {
ApplicationModel application = realm.getApplicationByName(referrer);
if (application != null) {
if (referrerUri != null) {
- referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, application);
+ referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, realm, application);
} else {
referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), application.getBaseUrl());
}
@@ -571,7 +571,7 @@ public class AccountService {
} else if (referrerUri != null) {
ClientModel client = realm.getOAuthClient(referrer);
if (client != null) {
- referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, application);
+ referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, realm, application);
if (referrerUri != null) {
return new String[]{referrer, referrerUri};
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index 7bf80fa..65e1bb0 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -86,34 +86,25 @@ public class OAuthFlows {
public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel session, String state, String redirect, boolean rememberMe) {
String code = accessCode.getCode();
-
- if (Constants.INSTALLED_APP_URN.equals(redirect)) {
- return Flows.forms(providerSession, realm, uriInfo).setAccessCode(accessCode.getId(), code).createCode();
- } else {
- UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code);
- log.debugv("redirectAccessCode: state: {0}", state);
- if (state != null)
- redirectUri.queryParam(OAuth2Constants.STATE, state);
- Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
- Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
- rememberMe = rememberMe || remember != null;
- // refresh the cookies!
- authManager.createLoginCookie(realm, accessCode.getUser(), session, uriInfo, rememberMe);
- if (rememberMe) authManager.createRememberMeCookie(realm, uriInfo);
- return location.build();
- }
+ UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code);
+ log.debugv("redirectAccessCode: state: {0}", state);
+ if (state != null)
+ redirectUri.queryParam(OAuth2Constants.STATE, state);
+ Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
+ Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
+ rememberMe = rememberMe || remember != null;
+ // refresh the cookies!
+ authManager.createLoginCookie(realm, accessCode.getUser(), session, uriInfo, rememberMe);
+ if (rememberMe) authManager.createRememberMeCookie(realm, uriInfo);
+ return location.build();
}
public Response redirectError(ClientModel client, String error, String state, String redirect) {
- if (Constants.INSTALLED_APP_URN.equals(redirect)) {
- return Flows.forms(providerSession, realm, uriInfo).setError(error).createCode();
- } else {
- UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, error);
- if (state != null) {
- redirectUri.queryParam(OAuth2Constants.STATE, state);
- }
- return Response.status(302).location(redirectUri.build()).build();
+ UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, error);
+ if (state != null) {
+ redirectUri.queryParam(OAuth2Constants.STATE, state);
}
+ return Response.status(302).location(redirectUri.build()).build();
}
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, String username, boolean rememberMe, String authMethod, Audit audit) {
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index 961fbcb..fa311fa 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -148,6 +148,10 @@ public class Urls {
return tokenBase(baseUri).path(TokenService.class, "registerPage").build(realmId);
}
+ public static URI realmInstalledAppUrnCallback(URI baseUri, String realmId) {
+ return tokenBase(baseUri).path(TokenService.class, "installedAppUrnCallback").build(realmId);
+ }
+
public static URI realmOauthAction(URI baseUri, String realmId) {
return tokenBase(baseUri).path(TokenService.class, "processOAuth").build(realmId);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/SocialResource.java b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
index c7c5d23..8129ff1 100755
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -122,6 +122,10 @@ public class SocialResource {
Map<String, String[]> queryParams = getQueryParams();
RequestDetails requestData = getRequestDetails(queryParams);
+ if (requestData == null) {
+ Flows.forms(providerSession, null, uriInfo).setError("Unexpected callback").createErrorPage();
+ }
+
SocialProvider provider = SocialLoader.load(requestData.getProviderId());
String realmName = requestData.getClientAttribute("realm");
@@ -296,7 +300,7 @@ public class SocialResource {
logger.warn("Login requester not enabled.");
return Flows.forms(providerSession, realm, uriInfo).setError("Login requester not enabled.").createErrorPage();
}
- redirectUri = TokenService.verifyRedirectUri(uriInfo, redirectUri, client);
+ redirectUri = TokenService.verifyRedirectUri(uriInfo, redirectUri, realm, client);
if (redirectUri == null) {
audit.error(Errors.INVALID_REDIRECT_URI);
return Flows.forms(providerSession, realm, uriInfo).setError("Invalid redirect_uri.").createErrorPage();
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index f7feddc..0a5b33c 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -18,6 +18,7 @@ import org.keycloak.authentication.AuthenticationProviderException;
import org.keycloak.authentication.AuthenticationProviderManager;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@@ -42,6 +43,7 @@ import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows;
+import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.validation.Validation;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.Time;
@@ -363,7 +365,7 @@ public class TokenService {
return oauth.forwardToSecurityFailure("Login requester not enabled.");
}
- redirect = verifyRedirectUri(uriInfo, redirect, client);
+ redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) {
audit.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
@@ -457,7 +459,7 @@ public class TokenService {
return oauth.forwardToSecurityFailure("Login requester not enabled.");
}
- redirect = verifyRedirectUri(uriInfo, redirect, client);
+ redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) {
audit.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
@@ -754,7 +756,7 @@ public class TokenService {
audit.error(Errors.NOT_ALLOWED);
return oauth.forwardToSecurityFailure("Bearer-only applications are not allowed to initiate login");
}
- redirect = verifyRedirectUri(uriInfo, redirect, client);
+ redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) {
audit.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
@@ -811,7 +813,7 @@ public class TokenService {
return oauth.forwardToSecurityFailure("Login requester not enabled.");
}
- redirect = verifyRedirectUri(uriInfo, redirect, client);
+ redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) {
audit.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
@@ -937,6 +939,17 @@ public class TokenService {
return oauth.redirectAccessCode(accessCodeEntry, session, state, redirect);
}
+ @Path("oauth/oob")
+ @GET
+ public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) {
+ LoginFormsProvider forms = Flows.forms(providerSession, realm, uriInfo);
+ if (code != null) {
+ return forms.setAccessCode(null, code).createCode();
+ } else {
+ return forms.setError(error).createCode();
+ }
+ }
+
protected Response redirectAccessDenied(String redirect, String state) {
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
if (state != null)
@@ -961,7 +974,7 @@ public class TokenService {
return false;
}
- public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, ClientModel client) {
+ public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
Set<String> validRedirects = client.getRedirectUris();
if (redirectUri == null) {
if (validRedirects.size() != 1) return null;
@@ -970,10 +983,10 @@ public class TokenService {
if (idx > -1) {
validRedirect = validRedirect.substring(0, idx);
}
- return validRedirect;
+ redirectUri = validRedirect;
} else if (validRedirects.isEmpty()) {
logger.error("Redirect URI is required for client: " + client.getClientId());
- return null;
+ redirectUri = null;
} else {
String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects);
@@ -996,7 +1009,13 @@ public class TokenService {
valid = matchesRedirects(resolveValidRedirects, r);
}
- return valid ? redirectUri : null;
+ redirectUri = valid ? redirectUri : null;
+ }
+
+ if (Constants.INSTALLED_APP_URN.equals(redirectUri)) {
+ return Urls.realmInstalledAppUrnCallback(uriInfo.getBaseUri(), realm.getName()).toString();
+ } else {
+ return redirectUri;
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index 73f185f..3d04ffc 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -34,7 +34,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.org.keycloak.testsuite.util.MailUtil;
+import org.keycloak.testsuite.MailUtil;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage;
@@ -50,8 +50,6 @@ import org.openqa.selenium.WebDriver;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 719b98c..ae5cfce 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -36,7 +36,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.org.keycloak.testsuite.util.MailUtil;
+import org.keycloak.testsuite.MailUtil;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index dd11d41..214ff5b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -113,6 +113,35 @@ public class AuthorizationCodeTest {
}
@Test
+ public void authorizationRequestInstalledAppCancel() throws IOException {
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.getApplicationNameMap().get("test-app").addRedirectUri(Constants.INSTALLED_APP_URN);
+ }
+ });
+ oauth.redirectUri(Constants.INSTALLED_APP_URN);
+
+ oauth.openLoginForm();
+ driver.findElement(By.name("cancel")).click();
+
+ String title = driver.getTitle();
+ Assert.assertTrue(title.equals("Error error=access_denied"));
+
+ String error = driver.findElement(By.id(OAuth2Constants.ERROR)).getText();
+ Assert.assertEquals("access_denied", error);
+
+ events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).detail(Details.REDIRECT_URI, Constants.INSTALLED_APP_URN).assertEvent().getDetails().get(Details.CODE_ID);
+
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.getApplicationNameMap().get("test-app").removeRedirectUri(Constants.INSTALLED_APP_URN);
+ }
+ });
+ }
+
+ @Test
public void authorizationValidRedirectUri() throws IOException {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override