For installed app urn redirect to a page instead of returning the html, this is to prevent NPE if page is refreshed

This commit is contained in:
Stian Thorgersen 2014-05-21 10:32:03 +01:00
parent fd9317a295
commit eb47d43497
10 changed files with 86 additions and 41 deletions

View file

@ -12,7 +12,7 @@
<p>Please copy this code and paste it into your application:</p> <p>Please copy this code and paste it into your application:</p>
<textarea id="code" class="${properties.kcTextareaClass!}">${code.code}</textarea> <textarea id="code" class="${properties.kcTextareaClass!}">${code.code}</textarea>
<#else> <#else>
<p>${code.error}</p> <p id="error">${code.error}</p>
</#if> </#if>
</div> </div>
</#if> </#if>

View file

@ -560,7 +560,7 @@ public class AccountService {
ApplicationModel application = realm.getApplicationByName(referrer); ApplicationModel application = realm.getApplicationByName(referrer);
if (application != null) { if (application != null) {
if (referrerUri != null) { if (referrerUri != null) {
referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, application); referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, realm, application);
} else { } else {
referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), application.getBaseUrl()); referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), application.getBaseUrl());
} }
@ -571,7 +571,7 @@ public class AccountService {
} else if (referrerUri != null) { } else if (referrerUri != null) {
ClientModel client = realm.getOAuthClient(referrer); ClientModel client = realm.getOAuthClient(referrer);
if (client != null) { if (client != null) {
referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, application); referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, realm, application);
if (referrerUri != null) { if (referrerUri != null) {
return new String[]{referrer, referrerUri}; return new String[]{referrer, referrerUri};

View file

@ -122,6 +122,10 @@ public class SocialResource {
Map<String, String[]> queryParams = getQueryParams(); Map<String, String[]> queryParams = getQueryParams();
RequestDetails requestData = getRequestDetails(queryParams); RequestDetails requestData = getRequestDetails(queryParams);
if (requestData == null) {
Flows.forms(providerSession, null, uriInfo).setError("Unexpected callback").createErrorPage();
}
SocialProvider provider = SocialLoader.load(requestData.getProviderId()); SocialProvider provider = SocialLoader.load(requestData.getProviderId());
String realmName = requestData.getClientAttribute("realm"); String realmName = requestData.getClientAttribute("realm");
@ -296,7 +300,7 @@ public class SocialResource {
logger.warn("Login requester not enabled."); logger.warn("Login requester not enabled.");
return Flows.forms(providerSession, realm, uriInfo).setError("Login requester not enabled.").createErrorPage(); 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) { if (redirectUri == null) {
audit.error(Errors.INVALID_REDIRECT_URI); audit.error(Errors.INVALID_REDIRECT_URI);
return Flows.forms(providerSession, realm, uriInfo).setError("Invalid redirect_uri.").createErrorPage(); return Flows.forms(providerSession, realm, uriInfo).setError("Invalid redirect_uri.").createErrorPage();

View file

@ -18,6 +18,7 @@ import org.keycloak.authentication.AuthenticationProviderException;
import org.keycloak.authentication.AuthenticationProviderManager; import org.keycloak.authentication.AuthenticationProviderManager;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ApplicationModel; import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; 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.messages.Messages;
import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows; import org.keycloak.services.resources.flows.OAuthFlows;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.validation.Validation; import org.keycloak.services.validation.Validation;
import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.Time; import org.keycloak.util.Time;
@ -363,7 +365,7 @@ public class TokenService {
return oauth.forwardToSecurityFailure("Login requester not enabled."); return oauth.forwardToSecurityFailure("Login requester not enabled.");
} }
redirect = verifyRedirectUri(uriInfo, redirect, client); redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) { if (redirect == null) {
audit.error(Errors.INVALID_REDIRECT_URI); audit.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri."); return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
@ -457,7 +459,7 @@ public class TokenService {
return oauth.forwardToSecurityFailure("Login requester not enabled."); return oauth.forwardToSecurityFailure("Login requester not enabled.");
} }
redirect = verifyRedirectUri(uriInfo, redirect, client); redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) { if (redirect == null) {
audit.error(Errors.INVALID_REDIRECT_URI); audit.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri."); return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
@ -754,7 +756,7 @@ public class TokenService {
audit.error(Errors.NOT_ALLOWED); audit.error(Errors.NOT_ALLOWED);
return oauth.forwardToSecurityFailure("Bearer-only applications are not allowed to initiate login"); 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) { if (redirect == null) {
audit.error(Errors.INVALID_REDIRECT_URI); audit.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri."); return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
@ -811,7 +813,7 @@ public class TokenService {
return oauth.forwardToSecurityFailure("Login requester not enabled."); return oauth.forwardToSecurityFailure("Login requester not enabled.");
} }
redirect = verifyRedirectUri(uriInfo, redirect, client); redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) { if (redirect == null) {
audit.error(Errors.INVALID_REDIRECT_URI); audit.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri."); return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
@ -937,6 +939,17 @@ public class TokenService {
return oauth.redirectAccessCode(accessCodeEntry, session, state, redirect); 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) { protected Response redirectAccessDenied(String redirect, String state) {
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied"); UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
if (state != null) if (state != null)
@ -961,7 +974,7 @@ public class TokenService {
return false; 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(); Set<String> validRedirects = client.getRedirectUris();
if (redirectUri == null) { if (redirectUri == null) {
if (validRedirects.size() != 1) return null; if (validRedirects.size() != 1) return null;
@ -970,10 +983,10 @@ public class TokenService {
if (idx > -1) { if (idx > -1) {
validRedirect = validRedirect.substring(0, idx); validRedirect = validRedirect.substring(0, idx);
} }
return validRedirect; redirectUri = validRedirect;
} else if (validRedirects.isEmpty()) { } else if (validRedirects.isEmpty()) {
logger.error("Redirect URI is required for client: " + client.getClientId()); logger.error("Redirect URI is required for client: " + client.getClientId());
return null; redirectUri = null;
} else { } else {
String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri; String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects); Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects);
@ -996,7 +1009,13 @@ public class TokenService {
valid = matchesRedirects(resolveValidRedirects, r); 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;
} }
} }

View file

@ -86,34 +86,25 @@ public class OAuthFlows {
public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel session, String state, String redirect, boolean rememberMe) { public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel session, String state, String redirect, boolean rememberMe) {
String code = accessCode.getCode(); String code = accessCode.getCode();
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code);
if (Constants.INSTALLED_APP_URN.equals(redirect)) { log.debugv("redirectAccessCode: state: {0}", state);
return Flows.forms(providerSession, realm, uriInfo).setAccessCode(accessCode.getId(), code).createCode(); if (state != null)
} else { redirectUri.queryParam(OAuth2Constants.STATE, state);
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code); Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
log.debugv("redirectAccessCode: state: {0}", state); Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
if (state != null) rememberMe = rememberMe || remember != null;
redirectUri.queryParam(OAuth2Constants.STATE, state); // refresh the cookies!
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build()); authManager.createLoginCookie(realm, accessCode.getUser(), session, uriInfo, rememberMe);
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME); if (rememberMe) authManager.createRememberMeCookie(realm, uriInfo);
rememberMe = rememberMe || remember != null; return location.build();
// 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) { public Response redirectError(ClientModel client, String error, String state, String redirect) {
if (Constants.INSTALLED_APP_URN.equals(redirect)) { UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, error);
return Flows.forms(providerSession, realm, uriInfo).setError(error).createCode(); if (state != null) {
} else { redirectUri.queryParam(OAuth2Constants.STATE, state);
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();
} }
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) { public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, String username, boolean rememberMe, String authMethod, Audit audit) {

View file

@ -148,6 +148,10 @@ public class Urls {
return tokenBase(baseUri).path(TokenService.class, "registerPage").build(realmId); 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) { public static URI realmOauthAction(URI baseUri, String realmId) {
return tokenBase(baseUri).path(TokenService.class, "processOAuth").build(realmId); return tokenBase(baseUri).path(TokenService.class, "processOAuth").build(realmId);
} }

View file

@ -1,4 +1,4 @@
package org.keycloak.testsuite.org.keycloak.testsuite.util; package org.keycloak.testsuite;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;

View file

@ -34,7 +34,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient; 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;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
@ -50,8 +50,6 @@ import org.openqa.selenium.WebDriver;
import javax.mail.MessagingException; import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
import java.io.IOException; import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>

View file

@ -36,7 +36,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient; 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;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;

View file

@ -112,6 +112,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 @Test
public void authorizationValidRedirectUri() throws IOException { public void authorizationValidRedirectUri() throws IOException {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() { keycloakRule.configure(new KeycloakRule.KeycloakSetup() {