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>
<textarea id="code" class="${properties.kcTextareaClass!}">${code.code}</textarea>
<#else>
<p>${code.error}</p>
<p id="error">${code.error}</p>
</#if>
</div>
</#if>

View file

@ -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};

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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);
}

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.Pattern;

View file

@ -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>

View file

@ -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;

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
public void authorizationValidRedirectUri() throws IOException {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {