Skip to content

Commit

Permalink
KEYCLOAK-5635
Browse files Browse the repository at this point in the history
  • Loading branch information
hmlnarik committed Dec 13, 2017
1 parent 6696c0f commit 2a2e6c8
Show file tree
Hide file tree
Showing 10 changed files with 393 additions and 21 deletions.
Expand Up @@ -19,6 +19,7 @@


import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
Expand Down Expand Up @@ -81,4 +82,16 @@ public static enum Error {
*/ */
boolean requireReauthentication(UserSessionModel userSession, AuthenticationSessionModel authSession); boolean requireReauthentication(UserSessionModel userSession, AuthenticationSessionModel authSession);


/**
* Send not-before revocation policy to the given client.
* @param realm
* @param resource
* @param notBefore
* @param managementUrl
* @return {@code true} if revocation policy was successfully updated at the client, {@code false} otherwise.
*/
default boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
return false;
}

} }
Expand Up @@ -19,7 +19,10 @@
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.TokenIdGenerator;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
Expand All @@ -33,6 +36,7 @@
import org.keycloak.protocol.oidc.utils.OIDCResponseMode; import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager; import org.keycloak.services.managers.AuthenticationSessionManager;
Expand All @@ -41,6 +45,8 @@
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;


import java.io.IOException;
import java.net.URI;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
Expand Down Expand Up @@ -323,6 +329,23 @@ protected boolean isAuthTimeExpired(UserSessionModel userSession, Authentication
return false; return false;
} }


@Override
public boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), notBefore);
String token = new TokenManager().encodeToken(session, realm, adminAction);
logger.debugv("pushRevocation resource: {0} url: {1}", resource.getClientId(), managementUrl);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
try {
int status = session.getProvider(HttpClientProvider.class).postText(target.toString(), token);
boolean success = status == 204 || status == 200;
logger.debugf("pushRevocation success for %s: %s", managementUrl, success);
return success;
} catch (IOException e) {
ServicesLogger.LOGGER.failedToSendRevocation(e);
return false;
}
}

@Override @Override
public void close() { public void close() {


Expand Down
Expand Up @@ -30,6 +30,9 @@
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.adapters.action.GlobalRequestResult;
import org.keycloak.representations.adapters.action.LogoutAction; import org.keycloak.representations.adapters.action.LogoutAction;
Expand Down Expand Up @@ -286,19 +289,14 @@ protected GlobalRequestResult pushRevocationPolicy(URI requestUri, RealmModel re
} }


protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) { protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), notBefore); String protocol = resource.getProtocol();
String token = new TokenManager().encodeToken(session, realm, adminAction); if (protocol == null) {
logger.debugv("pushRevocation resource: {0} url: {1}", resource.getClientId(), managementUrl); protocol = OIDCLoginProtocol.LOGIN_PROTOCOL;
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
try {
int status = session.getProvider(HttpClientProvider.class).postText(target.toString(), token);
boolean success = status == 204 || status == 200;
logger.debugf("pushRevocation success for %s: %s", managementUrl, success);
return success;
} catch (IOException e) {
ServicesLogger.LOGGER.failedToSendRevocation(e);
return false;
} }
LoginProtocol loginProtocol = (LoginProtocol) session.getProvider(LoginProtocol.class, protocol);
return loginProtocol == null
? false
: loginProtocol.sendPushRevocationPolicyRequest(realm, resource, notBefore, managementUrl);
} }


public GlobalRequestResult testNodesAvailability(URI requestUri, RealmModel realm, ClientModel client) { public GlobalRequestResult testNodesAvailability(URI requestUri, RealmModel realm, ClientModel client) {
Expand Down
@@ -0,0 +1,178 @@
/*
* 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.rest;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.utils.MediaType;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
* Copied from {@link TestApplicationResourceProvider}
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class TestSamlApplicationResourceProvider implements RealmResourceProvider {

private final KeycloakSession session;

private final BlockingQueue<LogoutAction> adminLogoutActions;
private final BlockingQueue<PushNotBeforeAction> adminPushNotBeforeActions;
private final BlockingQueue<TestAvailabilityAction> adminTestAvailabilityAction;

public TestSamlApplicationResourceProvider(KeycloakSession session, BlockingQueue<LogoutAction> adminLogoutActions,
BlockingQueue<PushNotBeforeAction> adminPushNotBeforeActions,
BlockingQueue<TestAvailabilityAction> adminTestAvailabilityAction) {
this.session = session;
this.adminLogoutActions = adminLogoutActions;
this.adminPushNotBeforeActions = adminPushNotBeforeActions;
this.adminTestAvailabilityAction = adminTestAvailabilityAction;
}

@POST
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
@Path("/saml/k_logout")
public void adminLogout(String data) throws JWSInputException {
adminLogoutActions.add(new JWSInput(data).readJsonContent(LogoutAction.class));
}

@POST
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
@Path("/saml/k_push_not_before")
public void adminPushNotBefore(String data) throws JWSInputException {
adminPushNotBeforeActions.add(new JWSInput(data).readJsonContent(PushNotBeforeAction.class));
}

@POST
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
@Path("/saml/k_test_available")
public void testAvailable(String data) throws JWSInputException {
adminTestAvailabilityAction.add(new JWSInput(data).readJsonContent(TestAvailabilityAction.class));
}

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/poll-admin-logout")
public LogoutAction getAdminLogoutAction() throws InterruptedException {
return adminLogoutActions.poll(10, TimeUnit.SECONDS);
}

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/poll-admin-not-before")
public PushNotBeforeAction getAdminPushNotBefore() throws InterruptedException {
return adminPushNotBeforeActions.poll(10, TimeUnit.SECONDS);
}

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/poll-test-available")
public TestAvailabilityAction getTestAvailable() throws InterruptedException {
return adminTestAvailabilityAction.poll(10, TimeUnit.SECONDS);
}

@POST
@Path("/clear-admin-actions")
public Response clearAdminActions() {
adminLogoutActions.clear();
adminPushNotBeforeActions.clear();
return Response.noContent().build();
}

@POST
@Produces(MediaType.TEXT_HTML_UTF_8)
@Path("/{action}")
public String post(@PathParam("action") String action) {
String title = "APP_REQUEST";
if (action.equals("auth")) {
title = "AUTH_RESPONSE";
} else if (action.equals("logout")) {
title = "LOGOUT_REQUEST";
}

StringBuilder sb = new StringBuilder();
sb.append("<html><head><title>" + title + "</title></head><body>");

sb.append("<b>Form parameters: </b><br>");
HttpRequest request = ResteasyProviderFactory.getContextData(HttpRequest.class);
MultivaluedMap<String, String> formParams = request.getDecodedFormParameters();
for (String paramName : formParams.keySet()) {
sb.append(paramName).append(": ").append("<span id=\"").append(paramName).append("\">").append(formParams.getFirst(paramName)).append("</span><br>");
}
sb.append("<br>");

UriBuilder base = UriBuilder.fromUri("http://localhost:8180/auth");
sb.append("<a href=\"" + RealmsResource.accountUrl(base).build("test").toString() + "\" id=\"account\">account</a>");

sb.append("</body></html>");
return sb.toString();
}

@GET
@Produces(MediaType.TEXT_HTML_UTF_8)
@Path("/{action}")
public String get(@PathParam("action") String action) {
//String requestUri = session.getContext().getUri().getRequestUri().toString();

String title = "APP_REQUEST";
if (action.equals("auth")) {
title = "AUTH_RESPONSE";
} else if (action.equals("logout")) {
title = "LOGOUT_REQUEST";
}

StringBuilder sb = new StringBuilder();
sb.append("<html><head><title>" + title + "</title></head><body>");
UriBuilder base = UriBuilder.fromUri("http://localhost:8180/auth");
sb.append("<a href=\"" + RealmsResource.accountUrl(base).build("test").toString() + "\" id=\"account\">account</a>");

sb.append("</body></html>");
return sb.toString();
}

@Override
public Object getResource() {
return this;
}

@Override
public void close() {

}
}
@@ -0,0 +1,64 @@
/*
* 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.rest;

import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resource.RealmResourceProviderFactory;

import java.security.KeyPair;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class TestSamlApplicationResourceProviderFactory implements RealmResourceProviderFactory {

private final BlockingQueue<LogoutAction> adminLogoutActions = new LinkedBlockingDeque<>();
private final BlockingQueue<PushNotBeforeAction> pushNotBeforeActions = new LinkedBlockingDeque<>();
private final BlockingQueue<TestAvailabilityAction> testAvailabilityActions = new LinkedBlockingDeque<>();

@Override
public RealmResourceProvider create(KeycloakSession session) {
return new TestSamlApplicationResourceProvider(session, adminLogoutActions, pushNotBeforeActions, testAvailabilityActions);
}

@Override
public void init(Scope config) {
}

@Override
public void postInit(KeycloakSessionFactory factory) {
}

@Override
public void close() {
}

@Override
public String getId() {
return "saml-app";
}
}
Expand Up @@ -17,4 +17,5 @@


org.keycloak.testsuite.rest.TestingResourceProviderFactory org.keycloak.testsuite.rest.TestingResourceProviderFactory
org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory
org.keycloak.testsuite.rest.TestSamlApplicationResourceProviderFactory
org.keycloak.testsuite.domainextension.rest.ExampleRealmResourceProviderFactory org.keycloak.testsuite.domainextension.rest.ExampleRealmResourceProviderFactory
Expand Up @@ -22,12 +22,12 @@
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.keycloak.testsuite.client.resources.TestApplicationResource; import org.keycloak.testsuite.client.resources.TestApplicationResource;
import org.keycloak.testsuite.client.resources.TestExampleCompanyResource; import org.keycloak.testsuite.client.resources.TestExampleCompanyResource;
import org.keycloak.testsuite.client.resources.TestSamlApplicationResource;
import org.keycloak.testsuite.client.resources.TestingResource; import org.keycloak.testsuite.client.resources.TestingResource;
import org.keycloak.testsuite.runonserver.*; import org.keycloak.testsuite.runonserver.*;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;


import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;


/** /**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a> * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
Expand Down Expand Up @@ -70,6 +70,8 @@ public TestingResource testing(String realm) {


public TestApplicationResource testApp() { return target.proxy(TestApplicationResource.class); } public TestApplicationResource testApp() { return target.proxy(TestApplicationResource.class); }


public TestSamlApplicationResource testSamlApp() { return target.proxy(TestSamlApplicationResource.class); }

public TestExampleCompanyResource testExampleCompany() { return target.proxy(TestExampleCompanyResource.class); } public TestExampleCompanyResource testExampleCompany() { return target.proxy(TestExampleCompanyResource.class); }


public Server server() { public Server server() {
Expand Down

0 comments on commit 2a2e6c8

Please sign in to comment.