KEYCLOAK-364 Show access denied if social login is cancelled
This commit is contained in:
parent
6dc156712e
commit
0214827492
12 changed files with 80 additions and 48 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package org.keycloak.social;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class SocialAccessDeniedException extends SocialProviderException {
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -28,6 +28,9 @@ public class SocialProviderException extends Exception {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected SocialProviderException() {
|
||||
}
|
||||
|
||||
public SocialProviderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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>");
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue