mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-25 16:42:34 +00:00
KEYCLOAK-4501
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.common.util;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Helper class to do a browser redirect via a POST.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class HttpPostRedirect {
|
||||
|
||||
/**
|
||||
* Generate an HTML page that does a browser redirect via a POST. The HTML document uses Javascript to automatically
|
||||
* submit a FORM post when loaded.
|
||||
*
|
||||
* This is similar to what the SAML Post Binding does.
|
||||
*
|
||||
* Here's an example
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* <HTML>
|
||||
* <HEAD>
|
||||
* <TITLE>title</TITLE>
|
||||
* </HEAD>
|
||||
* <BODY Onload="document.forms[0].submit()">
|
||||
* <FORM METHOD="POST" ACTION="actionUrl">
|
||||
* <INPUT TYPE="HIDDEN" NAME="param" VALUE="value"/>
|
||||
* <NOSCRIPT>
|
||||
* <P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.</P>
|
||||
* <INPUT TYPE="SUBMIT" VALUE="CONTINUE"/>
|
||||
* </NOSCRIPT>
|
||||
* </FORM>
|
||||
* </BODY>
|
||||
* </HTML>
|
||||
* }
|
||||
* </pre>
|
||||
|
||||
*
|
||||
* @param title may be null. Just the title of the HTML document
|
||||
* @param actionUrl URL to redirect to
|
||||
* @param params must be encoded so that they can be placed in an HTML form hidden INPUT field value
|
||||
* @return
|
||||
*/
|
||||
public String buildHtml(String title, String actionUrl, Map<String, String> params) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("<HTML>")
|
||||
.append("<HEAD>");
|
||||
if (title != null) {
|
||||
builder.append("<TITLE>SAML HTTP Post Binding</TITLE>");
|
||||
}
|
||||
builder.append("</HEAD>")
|
||||
.append("<BODY Onload=\"document.forms[0].submit()\">")
|
||||
|
||||
.append("<FORM METHOD=\"POST\" ACTION=\"").append(actionUrl).append("\">");
|
||||
for (Map.Entry<String, String> param : params.entrySet()) {
|
||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"").append(param.getKey()).append("\"").append(" VALUE=\"").append(param.getValue()).append("\"/>");
|
||||
}
|
||||
|
||||
|
||||
builder.append("<NOSCRIPT>")
|
||||
.append("<P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.</P>")
|
||||
.append("<INPUT TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />")
|
||||
.append("</NOSCRIPT>")
|
||||
|
||||
.append("</FORM></BODY></HTML>");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,4 +75,7 @@ public interface Errors {
|
||||
String PASSWORD_CONFIRM_ERROR = "password_confirm_error";
|
||||
String PASSWORD_MISSING = "password_missing";
|
||||
String PASSWORD_REJECTED = "password_rejected";
|
||||
String NOT_LOGGED_IN = "not_logged_in";
|
||||
String UNKNOWN_IDENTITY_PROVIDER = "unknown_identity_provider";
|
||||
String ILLEGAL_ORIGIN = "illegal_origin";
|
||||
}
|
||||
|
||||
@@ -113,7 +113,10 @@ public enum EventType {
|
||||
CLIENT_UPDATE(true),
|
||||
CLIENT_UPDATE_ERROR(true),
|
||||
CLIENT_DELETE(true),
|
||||
CLIENT_DELETE_ERROR(true);
|
||||
CLIENT_DELETE_ERROR(true),
|
||||
|
||||
CLIENT_INITIATED_ACCOUNT_LINKING(true),
|
||||
CLIENT_INITIATED_ACCOUNT_LINKING_ERROR(true);
|
||||
|
||||
private boolean saveByDefault;
|
||||
|
||||
|
||||
@@ -216,8 +216,10 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||
if (error != null) {
|
||||
//logger.error("Failed " + getConfig().getAlias() + " broker login: " + error);
|
||||
if (error.equals(ACCESS_DENIED)) {
|
||||
logger.error(ACCESS_DENIED + " for broker login " + getConfig().getProviderId());
|
||||
return callback.cancelled(state);
|
||||
} else {
|
||||
logger.error(error + " for broker login " + getConfig().getProviderId());
|
||||
return callback.error(state, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,7 +699,7 @@ public class AuthenticationManager {
|
||||
}
|
||||
|
||||
|
||||
protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
|
||||
public static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
|
||||
boolean isCookie, String tokenString, HttpHeaders headers) {
|
||||
try {
|
||||
TokenVerifier verifier = TokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.keycloak.services.resources;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
|
||||
@@ -34,8 +35,10 @@ import org.keycloak.broker.provider.IdentityProviderMapper;
|
||||
import org.keycloak.broker.saml.SAMLEndpoint;
|
||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
@@ -55,17 +58,20 @@ import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
import org.keycloak.protocol.saml.SamlService;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.ErrorPageException;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager.AuthResult;
|
||||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
@@ -88,6 +94,9 @@ import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -95,6 +104,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
|
||||
import static org.keycloak.models.ClientSessionModel.Action.AUTHENTICATE;
|
||||
@@ -140,6 +150,162 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||
this.event = new EventBuilder(realmModel, session, clientConnection).event(EventType.IDENTITY_PROVIDER_LOGIN);
|
||||
}
|
||||
|
||||
private void checkRealm() {
|
||||
if (!realmModel.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
throw new ErrorPageException(session, Messages.REALM_NOT_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
private ClientModel checkClient(String clientId) {
|
||||
if (clientId == null) {
|
||||
event.error(Errors.INVALID_REQUEST);
|
||||
throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
|
||||
}
|
||||
|
||||
event.client(clientId);
|
||||
|
||||
ClientModel client = realmModel.getClientByClientId(clientId);
|
||||
if (client == null) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
event.error(Errors.CLIENT_DISABLED);
|
||||
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
|
||||
}
|
||||
return client;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes off CORS preflight requests for account linking
|
||||
*
|
||||
* @param providerId
|
||||
* @return
|
||||
*/
|
||||
@OPTIONS
|
||||
@Path("/{provider_id}/link")
|
||||
public Response clientIntiatedAccountLinkingPreflight(@PathParam("provider_id") String providerId) {
|
||||
return Response.status(403).build(); // don't allow preflight
|
||||
}
|
||||
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("/{provider_id}/link")
|
||||
public Response clientInitiatedAccountLinking(@PathParam("provider_id") String providerId,
|
||||
@QueryParam("redirect_uri") String redirectUri,
|
||||
@QueryParam("client_id") String clientId,
|
||||
@QueryParam("nonce") String nonce,
|
||||
@QueryParam("hash") String hash
|
||||
) {
|
||||
this.event.event(EventType.CLIENT_INITIATED_ACCOUNT_LINKING);
|
||||
checkRealm();
|
||||
ClientModel client = checkClient(clientId);
|
||||
AuthenticationManager authenticationManager = new AuthenticationManager();
|
||||
redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realmModel, client);
|
||||
if (redirectUri == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
if (nonce == null || hash == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
|
||||
|
||||
}
|
||||
|
||||
// only allow origins from client. Not sure we need this as I don't believe cookies can be
|
||||
// sent if CORS preflight requests can't execute.
|
||||
String origin = headers.getRequestHeaders().getFirst("Origin");
|
||||
if (origin != null) {
|
||||
String redirectOrigin = UriUtils.getOrigin(redirectUri);
|
||||
if (!redirectOrigin.equals(origin)) {
|
||||
event.error(Errors.ILLEGAL_ORIGIN);
|
||||
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
AuthResult cookieResult = authenticationManager.authenticateIdentityCookie(session, realmModel, true);
|
||||
if (cookieResult == null) {
|
||||
event.error(Errors.NOT_LOGGED_IN);
|
||||
UriBuilder builder = UriBuilder.fromUri(redirectUri)
|
||||
.queryParam("error", Errors.NOT_LOGGED_IN)
|
||||
.queryParam("nonce", nonce);
|
||||
|
||||
return Response.status(302).location(builder.build()).build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
ClientSessionModel clientSession = null;
|
||||
for (ClientSessionModel cs : cookieResult.getSession().getClientSessions()) {
|
||||
if (cs.getClient().getClientId().equals(clientId)) {
|
||||
byte[] decoded = Base64Url.decode(hash);
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new ErrorPageException(session, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST);
|
||||
}
|
||||
String input = nonce + cookieResult.getSession().getId() + cs.getId() + providerId;
|
||||
byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||
if (MessageDigest.isEqual(decoded, check)) {
|
||||
clientSession = cs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (clientSession == null) {
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
IdentityProviderModel identityProviderModel = realmModel.getIdentityProviderByAlias(providerId);
|
||||
if (identityProviderModel == null) {
|
||||
event.error(Errors.UNKNOWN_IDENTITY_PROVIDER);
|
||||
UriBuilder builder = UriBuilder.fromUri(redirectUri)
|
||||
.queryParam("error", Errors.UNKNOWN_IDENTITY_PROVIDER)
|
||||
.queryParam("nonce", nonce);
|
||||
return Response.status(302).location(builder.build()).build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
ClientSessionCode clientSessionCode = new ClientSessionCode(session, realmModel, clientSession);
|
||||
clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||
clientSessionCode.getCode();
|
||||
clientSession.setRedirectUri(redirectUri);
|
||||
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
|
||||
|
||||
event.success();
|
||||
|
||||
|
||||
try {
|
||||
IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
|
||||
Response response = identityProvider.performLogin(createAuthenticationRequest(providerId, clientSessionCode));
|
||||
|
||||
if (response != null) {
|
||||
if (isDebugEnabled()) {
|
||||
logger.debugf("Identity provider [%s] is going to send a request [%s].", identityProvider, response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
} catch (IdentityBrokerException e) {
|
||||
return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId);
|
||||
} catch (Exception e) {
|
||||
return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId);
|
||||
}
|
||||
|
||||
return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@POST
|
||||
@Path("/{provider_id}/login")
|
||||
public Response performPostLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
|
||||
@@ -147,6 +313,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||
}
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("/{provider_id}/login")
|
||||
public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
|
||||
this.event.detail(Details.IDENTITY_PROVIDER, providerId);
|
||||
@@ -198,11 +365,18 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||
}
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("{provider_id}/token")
|
||||
public Response retrieveToken(@PathParam("provider_id") String providerId) {
|
||||
return getToken(providerId, false);
|
||||
}
|
||||
|
||||
private boolean canReadBrokerToken(AccessToken token) {
|
||||
Map<String, AccessToken.Access> resourceAccess = token.getResourceAccess();
|
||||
AccessToken.Access brokerRoles = resourceAccess == null ? null : resourceAccess.get(Constants.BROKER_SERVICE_CLIENT_ID);
|
||||
return brokerRoles != null && brokerRoles.isUserInRole(Constants.READ_TOKEN_ROLE);
|
||||
}
|
||||
|
||||
private Response getToken(String providerId, boolean forceRetrieval) {
|
||||
this.event.event(EventType.IDENTITY_PROVIDER_RETRIEVE_TOKEN);
|
||||
|
||||
@@ -226,9 +400,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||
return corsResponse(forbidden("Realm has not migrated to support the broker token exchange service"), clientModel);
|
||||
|
||||
}
|
||||
Map<String, AccessToken.Access> resourceAccess = token.getResourceAccess();
|
||||
AccessToken.Access brokerRoles = resourceAccess == null ? null : resourceAccess.get(Constants.BROKER_SERVICE_CLIENT_ID);
|
||||
if (brokerRoles == null || !brokerRoles.isUserInRole(Constants.READ_TOKEN_ROLE)) {
|
||||
if (!canReadBrokerToken(token)) {
|
||||
return corsResponse(forbidden("Client [" + clientModel.getClientId() + "] not authorized to retrieve tokens from identity provider [" + providerId + "]."), clientModel);
|
||||
|
||||
}
|
||||
@@ -366,6 +538,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||
|
||||
// Callback from LoginActionsService after first login with broker was done and Keycloak account is successfully linked/created
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("/after-first-broker-login")
|
||||
public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
|
||||
ParsedCodeContext parsedCode = parseClientSessionCode(code);
|
||||
@@ -487,6 +660,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||
|
||||
// Callback from LoginActionsService after postBrokerLogin flow is finished
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("/after-post-broker-login")
|
||||
public Response afterPostBrokerLoginFlow(@QueryParam("code") String code) {
|
||||
ParsedCodeContext parsedCode = parseClientSessionCode(code);
|
||||
@@ -613,7 +787,19 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||
this.event.event(EventType.FEDERATED_IDENTITY_LINK);
|
||||
|
||||
if (federatedUser != null) {
|
||||
return redirectToAccountErrorPage(clientSession, Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias());
|
||||
// refresh the token
|
||||
if (context.getIdpConfig().isStoreToken()) {
|
||||
federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
|
||||
if (!ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) {
|
||||
|
||||
this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
|
||||
}
|
||||
|
||||
UserModel authenticatedUser = clientSession.getUserSession().getUser();
|
||||
@@ -645,15 +831,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||
FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
|
||||
|
||||
// Skip DB write if tokens are null or equal
|
||||
if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) {
|
||||
federatedIdentityModel.setToken(context.getToken());
|
||||
|
||||
this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias());
|
||||
}
|
||||
}
|
||||
updateToken(context, federatedUser, federatedIdentityModel);
|
||||
context.getIdp().updateBrokeredUser(session, realmModel, federatedUser, context);
|
||||
Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias());
|
||||
if (mappers != null) {
|
||||
@@ -666,6 +844,18 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||
|
||||
}
|
||||
|
||||
private void updateToken(BrokeredIdentityContext context, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
|
||||
if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) {
|
||||
federatedIdentityModel.setToken(context.getToken());
|
||||
|
||||
this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ParsedCodeContext parseClientSessionCode(String code) {
|
||||
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.adapter.servlet;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ClientInitiatedAccountLinkServlet extends HttpServlet {
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
|
||||
if (request.getRequestURI().endsWith("/link") && request.getParameter("response") == null) {
|
||||
String provider = request.getParameter("provider");
|
||||
String realm = request.getParameter("realm");
|
||||
KeycloakSecurityContext session = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
|
||||
AccessToken token = session.getToken();
|
||||
String clientSessionId = token.getClientSession();
|
||||
String nonce = UUID.randomUUID().toString();
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
String input = nonce + token.getSessionState() + clientSessionId + provider;
|
||||
byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||
String hash = Base64Url.encode(check);
|
||||
request.getSession().setAttribute("hash", hash);
|
||||
String redirectUri = KeycloakUriBuilder.fromUri(request.getRequestURL().toString())
|
||||
.replaceQuery(null)
|
||||
.queryParam("response", "true").build().toString();
|
||||
String accountLinkUrl = KeycloakUriBuilder.fromUri(ServletTestUtils.getAuthServerUrlBase())
|
||||
.path("/auth/realms/{realm}/broker/{provider}/link")
|
||||
.queryParam("nonce", nonce)
|
||||
.queryParam("hash", hash)
|
||||
.queryParam("client_id", token.getIssuedFor())
|
||||
.queryParam("redirect_uri", redirectUri).build(realm, provider).toString();
|
||||
resp.setStatus(302);
|
||||
resp.setHeader("Location", accountLinkUrl);
|
||||
} else if (request.getRequestURI().endsWith("/link") && request.getParameter("response") != null) {
|
||||
String hash = request.getSession().getAttribute("hash").toString();
|
||||
String hashParam = request.getParameter("hash");
|
||||
resp.setStatus(200);
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
pw.printf("<html><head><title>%s</title></head><body>", "Client Linking");
|
||||
String error = request.getParameter("error");
|
||||
if (error != null) {
|
||||
pw.println("Link error: " + error);
|
||||
} else {
|
||||
pw.println("Account Linked");
|
||||
}
|
||||
pw.print("</body></html>");
|
||||
pw.flush();
|
||||
} else {
|
||||
resp.setStatus(200);
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
pw.printf("<html><head><title>%s</title></head><body>", "Client Linking");
|
||||
pw.println("Unknown request: " + request.getRequestURL().toString());
|
||||
pw.print("</body></html>");
|
||||
pw.flush();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,6 @@ public class ServletTestUtils {
|
||||
return System.getProperty("auth.server.ssl.base.url", "https://localhost:8543");
|
||||
}
|
||||
|
||||
return System.getProperty("auth.server.base.url");
|
||||
return System.getProperty("auth.server.base.url", "http://localhost:8180");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,12 @@ public class LoginPage extends AbstractPage {
|
||||
|
||||
|
||||
public boolean isCurrent() {
|
||||
return driver.getTitle().equals("Log in to test") || driver.getTitle().equals("Anmeldung bei test");
|
||||
String realm = "test";
|
||||
return isCurrent(realm);
|
||||
}
|
||||
|
||||
public boolean isCurrent(String realm) {
|
||||
return driver.getTitle().equals("Log in to " + realm) || driver.getTitle().equals("Anmeldung bei " + realm);
|
||||
}
|
||||
|
||||
public void clickRegister() {
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.broker;
|
||||
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.adapter.page.AppServerContextRoot;
|
||||
import org.keycloak.testsuite.adapter.servlet.ClientInitiatedAccountLinkServlet;
|
||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||
import org.keycloak.testsuite.federation.PassThroughFederatedUserStorageProvider;
|
||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
||||
import org.keycloak.testsuite.pages.AccountFederatedIdentityPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.UpdateAccountInformationPage;
|
||||
import org.keycloak.testsuite.util.AdapterServletDeployment;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.net.URL;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@AppServerContainer("auth-server-undertow")
|
||||
public class ClientInitiatedAccountLinkTest extends AbstractKeycloakTest {
|
||||
public static final String CHILD_IDP = "child";
|
||||
public static final String PARENT_IDP = "parent-idp";
|
||||
public static final String PARENT_USERNAME = "parent";
|
||||
|
||||
@Page
|
||||
protected UpdateAccountInformationPage profilePage;
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Page
|
||||
protected AppServerContextRoot appServerContextRootPage;
|
||||
|
||||
public boolean isRelative() {
|
||||
return testContext.isRelativeAdapterTest();
|
||||
}
|
||||
|
||||
public static class ClientApp extends AbstractPageWithInjectedUrl {
|
||||
|
||||
public static final String DEPLOYMENT_NAME = "client-linking";
|
||||
|
||||
@ArquillianResource
|
||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
||||
private URL url;
|
||||
|
||||
@Override
|
||||
public URL getInjectedUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Page
|
||||
protected ClientApp appPage;
|
||||
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = new RealmRepresentation();
|
||||
realm.setRealm(CHILD_IDP);
|
||||
realm.setEnabled(true);
|
||||
ClientRepresentation servlet = new ClientRepresentation();
|
||||
servlet.setClientId("client-linking");
|
||||
servlet.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
String uri = "/client-linking";
|
||||
if (!isRelative()) {
|
||||
uri = appServerContextRootPage.toString() + uri;
|
||||
}
|
||||
servlet.setAdminUrl(uri);
|
||||
servlet.setBaseUrl(uri);
|
||||
servlet.setRedirectUris(new LinkedList<>());
|
||||
servlet.getRedirectUris().add(uri + "/*");
|
||||
servlet.setSecret("password");
|
||||
servlet.setFullScopeAllowed(true);
|
||||
realm.setClients(new LinkedList<>());
|
||||
realm.getClients().add(servlet);
|
||||
testRealms.add(realm);
|
||||
|
||||
|
||||
realm = new RealmRepresentation();
|
||||
realm.setRealm(PARENT_IDP);
|
||||
realm.setEnabled(true);
|
||||
|
||||
testRealms.add(realm);
|
||||
|
||||
}
|
||||
|
||||
@Deployment(name = "client-linking")
|
||||
public static WebArchive customerPortal() {
|
||||
return AdapterServletDeployment.oidcDeployment("client-linking", "/account-link-test", ClientInitiatedAccountLinkServlet.class);
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void addIdpUser() {
|
||||
RealmResource realm = adminClient.realms().realm(PARENT_IDP);
|
||||
UserRepresentation user = new UserRepresentation();
|
||||
user.setUsername(PARENT_USERNAME);
|
||||
user.setEnabled(true);
|
||||
String userId = createUserAndResetPasswordWithAdminClient(realm, user, "password");
|
||||
|
||||
}
|
||||
|
||||
private String childUserId = null;
|
||||
|
||||
@Before
|
||||
public void addChildUser() {
|
||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
||||
UserRepresentation user = new UserRepresentation();
|
||||
user.setUsername("child");
|
||||
user.setEnabled(true);
|
||||
childUserId = createUserAndResetPasswordWithAdminClient(realm, user, "password");
|
||||
|
||||
// have to add a role as stupid undertow auth manager doesn't like "*"
|
||||
realm.roles().create(new RoleRepresentation("user", null, false));
|
||||
RoleRepresentation role = realm.roles().get("user").toRepresentation();
|
||||
List<RoleRepresentation> roles = new LinkedList<>();
|
||||
roles.add(role);
|
||||
realm.users().get(childUserId).roles().realmLevel().add(roles);
|
||||
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createBroker() {
|
||||
createParentChild();
|
||||
}
|
||||
|
||||
public void createParentChild() {
|
||||
BrokerTestTools.createKcOidcBroker(adminClient, CHILD_IDP, PARENT_IDP, suiteContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorConditions() {
|
||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
Assert.assertTrue(links.isEmpty());
|
||||
|
||||
UriBuilder redirectUri = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
|
||||
.path("link")
|
||||
.queryParam("response", "true");
|
||||
|
||||
UriBuilder directLinking = UriBuilder.fromUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth")
|
||||
.path("realms/child/broker/{provider}/link")
|
||||
.queryParam("client_id", "client-linking")
|
||||
.queryParam("redirect_uri", redirectUri.build())
|
||||
.queryParam("hash", Base64Url.encode("crap".getBytes()))
|
||||
.queryParam("nonce", UUID.randomUUID().toString());
|
||||
|
||||
String linkUrl = directLinking
|
||||
.build(PARENT_IDP).toString();
|
||||
// test not logged in
|
||||
|
||||
|
||||
driver.navigate().to(linkUrl);
|
||||
|
||||
Assert.assertTrue(driver.getCurrentUrl().contains("error=not_logged_in"));
|
||||
|
||||
|
||||
// now log in
|
||||
|
||||
driver.navigate().to( appPage.getInjectedUrl() + "/hello");
|
||||
loginPage.login("child", "password");
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(appPage.getInjectedUrl() + "/hello"));
|
||||
|
||||
// now test CSRF with bad hash.
|
||||
|
||||
driver.navigate().to(linkUrl);
|
||||
|
||||
Assert.assertTrue(driver.getPageSource().contains("We're sorry..."));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountLink() {
|
||||
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
|
||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
Assert.assertTrue(links.isEmpty());
|
||||
|
||||
UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
|
||||
.path("link");
|
||||
String linkUrl = linkBuilder.clone()
|
||||
.queryParam("realm", CHILD_IDP)
|
||||
.queryParam("provider", PARENT_IDP).build().toString();
|
||||
driver.navigate().to(linkUrl);
|
||||
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
|
||||
loginPage.login("child", "password");
|
||||
Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
|
||||
loginPage.login(PARENT_USERNAME, "password");
|
||||
System.out.println("After linking: " + driver.getCurrentUrl());
|
||||
System.out.println(driver.getPageSource());
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
|
||||
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
|
||||
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
Assert.assertFalse(links.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.util;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.jboss.shrinkwrap.api.Archive;
|
||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||
import org.jboss.shrinkwrap.api.asset.StringAsset;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Expects a structure like adapter-test directory
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AdapterServletDeployment {
|
||||
|
||||
public static final String JBOSS_DEPLOYMENT_STRUCTURE_XML = "jboss-deployment-structure.xml";
|
||||
public static final String TOMCAT_CONTEXT_XML = "context.xml";
|
||||
|
||||
// hardcoded for now
|
||||
public static final URL tomcatContext = AdapterServletDeployment.class
|
||||
.getResource("/adapter-test/" + TOMCAT_CONTEXT_XML);
|
||||
|
||||
public static WebArchive oidcDeployment(String name, String configRoot, Class... servletClasses) {
|
||||
return oidcDeployment(name, configRoot, "keycloak.json");
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static WebArchive oidcDeployment(String name, String configRoot, String adapterConfigFilename, Class... servletClasses) {
|
||||
String configPath = configRoot + "/" + name;
|
||||
String webInfPath = configPath + "/WEB-INF/";
|
||||
|
||||
URL keycloakJSON = AdapterServletDeployment.class.getResource(webInfPath + adapterConfigFilename);
|
||||
URL webXML = AdapterServletDeployment.class.getResource(webInfPath + "web.xml");
|
||||
|
||||
WebArchive deployment = ShrinkWrap.create(WebArchive.class, name + ".war")
|
||||
.addClasses(servletClasses)
|
||||
.addAsWebInfResource(webXML, "web.xml");
|
||||
|
||||
URL keystore = AdapterServletDeployment.class.getResource(webInfPath + "keystore.jks");
|
||||
if (keystore != null) {
|
||||
deployment.addAsWebInfResource(keystore, "classes/keystore.jks");
|
||||
}
|
||||
|
||||
if (keycloakJSON != null) {
|
||||
deployment.addAsWebInfResource(keycloakJSON, "keycloak.json");
|
||||
}
|
||||
|
||||
URL jbossDeploymentStructure = AdapterServletDeployment.class.getResource(webInfPath + JBOSS_DEPLOYMENT_STRUCTURE_XML);
|
||||
if (jbossDeploymentStructure == null) {
|
||||
jbossDeploymentStructure = AdapterServletDeployment.class.getResource(configRoot + "/" + JBOSS_DEPLOYMENT_STRUCTURE_XML);
|
||||
}
|
||||
if (jbossDeploymentStructure != null) deployment.addAsWebInfResource(jbossDeploymentStructure, JBOSS_DEPLOYMENT_STRUCTURE_XML);
|
||||
|
||||
addContextXml(deployment, name);
|
||||
|
||||
return deployment;
|
||||
}
|
||||
|
||||
|
||||
public static void addContextXml(Archive archive, String contextPath) {
|
||||
// hardcoded for now
|
||||
try {
|
||||
String contextXmlContent = IOUtils.toString(tomcatContext.openStream())
|
||||
.replace("%CONTEXT_PATH%", contextPath);
|
||||
archive.add(new StringAsset(contextXmlContent), "/META-INF/context.xml");
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "child",
|
||||
"realm": "child",
|
||||
"enabled": true,
|
||||
"accessTokenLifespan": 600,
|
||||
"accessCodeLifespan": 10,
|
||||
"accessCodeLifespanUserAction": 6000,
|
||||
"sslRequired": "external",
|
||||
"registrationAllowed": false,
|
||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"requiredCredentials": [ "password" ],
|
||||
"users" : [
|
||||
{
|
||||
"username" : "bburke@redhat.com",
|
||||
"enabled": true,
|
||||
"email" : "bburke@redhat.com",
|
||||
"firstName": "Bill",
|
||||
"lastName": "Burke",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"clientId": "client-linking",
|
||||
"enabled": true,
|
||||
"adminUrl": "/client-linking",
|
||||
"baseUrl": "/client-linking",
|
||||
"redirectUris": [
|
||||
"/client-linking/*"
|
||||
],
|
||||
"secret": "password"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<Context path="/customer-portal">
|
||||
<Valve className="org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve"/>
|
||||
</Context>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
|
||||
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
|
||||
<Get name="securityHandler">
|
||||
<Set name="authenticator">
|
||||
<New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
|
||||
<!--
|
||||
<Set name="adapterConfig">
|
||||
<New class="org.keycloak.representations.adapters.config.AdapterConfig">
|
||||
<Set name="realm">tomcat</Set>
|
||||
<Set name="resource">customer-portal</Set>
|
||||
<Set name="authServerUrl">http://localhost:8180/auth</Set>
|
||||
<Set name="sslRequired">external</Set>
|
||||
<Set name="credentials">
|
||||
<Map>
|
||||
<Entry>
|
||||
<Item>secret</Item>
|
||||
<Item>password</Item>
|
||||
</Entry>
|
||||
</Map>
|
||||
</Set>
|
||||
<Set name="realmKey">MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</Set>
|
||||
</New>
|
||||
</Set>
|
||||
-->
|
||||
</New>
|
||||
</Set>
|
||||
</Get>
|
||||
</Configure>
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"realm" : "child",
|
||||
"resource" : "client-linking",
|
||||
"auth-server-url" : "http://localhost:8180/auth",
|
||||
"ssl-required" : "external",
|
||||
"credentials" : {
|
||||
"secret": "password"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<module-name>client-linking</module-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Servlet</servlet-name>
|
||||
<servlet-class>org.keycloak.testsuite.adapter.servlet.ClientInitiatedAccountLinkServlet</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Servlet</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Users</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>user</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<login-config>
|
||||
<auth-method>KEYCLOAK</auth-method>
|
||||
<realm-name>child</realm-name>
|
||||
</login-config>
|
||||
|
||||
<security-role>
|
||||
<role-name>user</role-name>
|
||||
</security-role>
|
||||
</web-app>
|
||||
@@ -104,21 +104,6 @@ public class AdapterTest {
|
||||
Thread.sleep(1000000000);
|
||||
}
|
||||
|
||||
public static class MySuper {
|
||||
|
||||
}
|
||||
|
||||
public static class Base extends MySuper {
|
||||
public Class superClass() {
|
||||
return super.getClass();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase() {
|
||||
System.out.println(new Base().superClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginSSOAndLogout() throws Exception {
|
||||
testStrategy.testLoginSSOMax();
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<module>tomcat8</module>
|
||||
<module>jetty</module>
|
||||
<module>integration-arquillian</module>
|
||||
<module>stress</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
Reference in New Issue
Block a user