KEYCLOAK-364 Show access denied if social login is cancelled

This commit is contained in:
Stian Thorgersen 2014-03-11 20:03:38 +00:00
parent 6dc156712e
commit 0214827492
12 changed files with 80 additions and 48 deletions

View file

@ -43,6 +43,8 @@ public interface LoginForms {
public LoginForms setClient(ClientModel client);
public LoginForms setQueryParams(MultivaluedMap<String, String> queryParams);
public LoginForms setFormData(MultivaluedMap<String, String> formData);
public LoginForms setStatus(Response.Status status);

View file

@ -51,6 +51,7 @@ public class FreeMarkerLoginForms implements LoginForms {
private Response.Status status = Response.Status.OK;
private List<RoleModel> realmRolesRequested;
private MultivaluedMap<String, RoleModel> resourceRolesRequested;
private MultivaluedMap<String, String> queryParams;
public static enum MessageType {SUCCESS, WARNING, ERROR}
@ -114,7 +115,7 @@ public class FreeMarkerLoginForms implements LoginForms {
}
private Response createResponse(LoginFormsPages page) {
MultivaluedMap<String, String> queryParameterMap = uriInfo.getQueryParameters();
MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : uriInfo.getQueryParameters();
String requestURI = uriInfo.getBaseUri().getPath();
UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
@ -276,4 +277,9 @@ public class FreeMarkerLoginForms implements LoginForms {
return this;
}
@Override
public LoginForms setQueryParams(MultivaluedMap<String, String> queryParams) {
this.queryParams = queryParams;
return this;
}
}

View file

@ -23,7 +23,6 @@ package org.keycloak.services.resources;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@ -32,22 +31,20 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.SocialRequestManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.RequestDetails;
import org.keycloak.social.SocialAccessDeniedException;
import org.keycloak.social.SocialLoader;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
import org.keycloak.services.managers.SocialRequestManager;
import org.keycloak.social.SocialUser;
import javax.ws.rs.GET;
@ -57,6 +54,7 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
@ -141,6 +139,14 @@ public class SocialResource {
SocialUser socialUser;
try {
socialUser = provider.processCallback(config, callback);
} catch (SocialAccessDeniedException e) {
MultivaluedHashMap<String, String> queryParms = new MultivaluedHashMap<String, String>();
queryParms.putSingle("client_id", requestData.getClientAttribute("clientId"));
queryParms.putSingle("state", requestData.getClientAttribute("state"));
queryParms.putSingle("scope", requestData.getClientAttribute("scope"));
queryParms.putSingle("redirect_uri", requestData.getClientAttribute("redirectUri"));
queryParms.putSingle("response_type", requestData.getClientAttribute("responseType"));
return Flows.forms(realm, request, uriInfo).setQueryParams(queryParms).setWarning("Access denied").createLogin();
} catch (SocialProviderException e) {
logger.warn("Failed to process social callback", e);
return oauth.forwardToSecurityFailure("Failed to process social callback");
@ -210,7 +216,7 @@ public class SocialResource {
public Response redirectToProviderAuth(@PathParam("realm") final String realmName,
@QueryParam("provider_id") final String providerId, @QueryParam("client_id") final String clientId,
@QueryParam("scope") final String scope, @QueryParam("state") final String state,
@QueryParam("redirect_uri") String redirectUri) {
@QueryParam("redirect_uri") String redirectUri, @QueryParam("response_type") String responseType) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(realmName);
@ -239,20 +245,24 @@ public class SocialResource {
.putClientAttribute("realm", realmName)
.putClientAttribute("clientId", clientId).putClientAttribute("scope", scope)
.putClientAttribute("state", state).putClientAttribute("redirectUri", redirectUri)
.redirectToSocialProvider();
.putClientAttribute("responseType", responseType).redirectToSocialProvider();
} catch (Throwable t) {
return Flows.forms(realm, request, uriInfo).setError("Failed to redirect to social auth").createErrorPage();
}
}
private RequestDetails getRequestDetails(Map<String, String[]> queryParams) {
for (SocialProvider provider : SocialLoader.load()) {
if (queryParams.containsKey(provider.getRequestIdParamName())) {
String requestId = queryParams.get(provider.getRequestIdParamName())[0];
if (socialRequestManager.isRequestId(requestId)) {
return socialRequestManager.retrieveData(requestId);
}
}
String requestId = null;
if (queryParams.containsKey("state")) {
requestId = queryParams.get("state")[0];
} else if (queryParams.containsKey("oauth_token")) {
requestId = queryParams.get("oauth_token")[0];
} else if (queryParams.containsKey("denied")) {
requestId = queryParams.get("denied")[0];
}
if (requestId != null && socialRequestManager.isRequestId(requestId)) {
return socialRequestManager.retrieveData(requestId);
}
return null;

View file

@ -51,6 +51,15 @@ public abstract class AbstractOAuth2Provider implements SocialProvider {
@Override
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
String error = callback.getQueryParam("error");
if (error != null) {
if (error.equals("access_denied")) {
throw new SocialAccessDeniedException();
} else {
throw new SocialProviderException(error);
}
}
try {
String code = callback.getQueryParam(CODE);
@ -82,9 +91,4 @@ public abstract class AbstractOAuth2Provider implements SocialProvider {
}
}
@Override
public String getRequestIdParamName() {
return STATE;
}
}

View file

@ -0,0 +1,7 @@
package org.keycloak.social;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class SocialAccessDeniedException extends SocialProviderException {
}

View file

@ -31,8 +31,6 @@ public interface SocialProvider {
AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException;
String getRequestIdParamName();
String getName();
SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException;

View file

@ -28,6 +28,9 @@ public class SocialProviderException extends Exception {
private static final long serialVersionUID = 1L;
protected SocialProviderException() {
}
public SocialProviderException(String message) {
super(message);
}

View file

@ -23,6 +23,7 @@ package org.keycloak.social.twitter;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.SocialAccessDeniedException;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
@ -67,6 +68,10 @@ public class TwitterProvider implements SocialProvider {
@Override
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
if (callback.getQueryParam("denied") != null) {
throw new SocialAccessDeniedException();
}
try {
Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(config.getKey(), config.getSecret());
@ -86,9 +91,4 @@ public class TwitterProvider implements SocialProvider {
}
}
@Override
public String getRequestIdParamName() {
return "oauth_token";
}
}

View file

@ -2,6 +2,7 @@ package org.keycloak.testsuite;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.SocialAccessDeniedException;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
@ -26,11 +27,6 @@ public class DummySocial implements SocialProvider {
.setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state).setAttribute("state", state).build();
}
@Override
public String getRequestIdParamName() {
return "state";
}
@Override
public String getName() {
return "Dummy Provider";
@ -38,6 +34,11 @@ public class DummySocial implements SocialProvider {
@Override
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
String error = callback.getQueryParam("error");
if (error != null) {
throw new SocialAccessDeniedException();
}
if (!callback.getQueryParam("state").equals(callback.getAttribute("state"))) {
throw new SocialProviderException("Invalid state");
}

View file

@ -27,8 +27,8 @@ public class DummySocialServlet extends HttpServlet {
pw.print("<label for=\"firstname\">First Name</label><input type=\"text\" id=\"firstname\" name=\"firstname\" />");
pw.print("<label for=\"lastname\">Last Name</label><input type=\"text\" id=\"lastname\" name=\"lastname\" />");
pw.print("<label for=\"email\">Email</label><input type=\"text\" id=\"email\" name=\"email\" />");
pw.print("<input type=\"submit\" id=\"submit\" value=\"login\" />");
pw.print("<input type=\"submit\" id=\"cancel\" value=\"cancel\" />");
pw.print("<input type=\"submit\" id=\"login\" name=\"login\" value=\"login\" />");
pw.print("<input type=\"submit\" id=\"cancel\" name=\"cancel\" value=\"cancel\" />");
pw.print("</form>");
pw.print("</body>");
pw.print("</html>");

View file

@ -62,6 +62,9 @@ public class LoginPage extends AbstractPage {
@FindBy(className = "feedback-error")
private WebElement loginErrorMessage;
@FindBy(className = "feedback-warning")
private WebElement loginWarningMessage;
public void login(String username, String password) {
usernameInput.clear();
usernameInput.sendKeys(username);
@ -80,6 +83,11 @@ public class LoginPage extends AbstractPage {
return loginErrorMessage != null ? loginErrorMessage.getText() : null;
}
public String getWarning() {
return loginWarningMessage != null ? loginWarningMessage.getText() : null;
}
public boolean isCurrent() {
return driver.getTitle().equals("Log in to test");
}

View file

@ -102,7 +102,7 @@ public class SocialLoginTest {
driver.findElement(By.id("firstname")).sendKeys("Bob");
driver.findElement(By.id("lastname")).sendKeys("Builder");
driver.findElement(By.id("email")).sendKeys("bob@builder.com");
driver.findElement(By.id("submit")).click();
driver.findElement(By.id("login")).click();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -128,19 +128,12 @@ public class SocialLoginTest {
driver.findElement(By.id("cancel")).click();
Assert.assertTrue(loginPage.isCurrent());
Assert.assertEquals("Access denied", loginPage.getWarning());
loginPage.login("test-user@localhost", "password");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password");
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals(36, token.getSubject().length());
UserRepresentation profile = keycloakRule.getUserById("test", token.getSubject());
Assert.assertEquals(36, profile.getUsername().length());
Assert.assertEquals("Bob", profile.getFirstName());
Assert.assertEquals("Builder", profile.getLastName());
Assert.assertEquals("bob@builder.com", profile.getEmail());
}
@Test
@ -162,7 +155,7 @@ public class SocialLoginTest {
driver.findElement(By.id("firstname")).sendKeys("Bob");
driver.findElement(By.id("lastname")).sendKeys("Builder");
driver.findElement(By.id("email")).sendKeys("bob@builder.com");
driver.findElement(By.id("submit")).click();
driver.findElement(By.id("login")).click();
profilePage.isCurrent();