keycloak-uncached

KEYCLOAK-5635

12/11/2017 6:51:19 PM

Details

diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
index 569a2c0..e367bf2 100755
--- a/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
@@ -19,6 +19,7 @@ package org.keycloak.protocol;
 
 import org.keycloak.events.EventBuilder;
 import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
@@ -81,4 +82,16 @@ public interface LoginProtocol extends Provider {
      */
     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;
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index eb1aede..aa0377e 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -19,7 +19,10 @@ package org.keycloak.protocol.oidc;
 import org.jboss.logging.Logger;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
+import org.keycloak.TokenIdGenerator;
 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.EventBuilder;
 import org.keycloak.events.EventType;
@@ -33,6 +36,7 @@ import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder;
 import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
 import org.keycloak.protocol.oidc.utils.OIDCResponseType;
 import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.adapters.action.PushNotBeforeAction;
 import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.AuthenticationSessionManager;
@@ -41,6 +45,8 @@ import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.sessions.AuthenticationSessionModel;
 import org.keycloak.util.TokenUtil;
 
+import java.io.IOException;
+import java.net.URI;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
@@ -324,6 +330,23 @@ public class OIDCLoginProtocol implements LoginProtocol {
     }
 
     @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
     public void close() {
 
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index dbb0e78..34c85b8 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -30,6 +30,9 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 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.representations.adapters.action.GlobalRequestResult;
 import org.keycloak.representations.adapters.action.LogoutAction;
@@ -286,19 +289,14 @@ public class ResourceAdminManager {
     }
 
     protected 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;
+        String protocol = resource.getProtocol();
+        if (protocol == null) {
+            protocol = OIDCLoginProtocol.LOGIN_PROTOCOL;
         }
+        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) {
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestSamlApplicationResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestSamlApplicationResourceProvider.java
new file mode 100644
index 0000000..02e4214
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestSamlApplicationResourceProvider.java
@@ -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() {
+
+    }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestSamlApplicationResourceProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestSamlApplicationResourceProviderFactory.java
new file mode 100644
index 0000000..c9493e7
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestSamlApplicationResourceProviderFactory.java
@@ -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";
+    }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
index 1958ff0..810bdef 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
@@ -17,4 +17,5 @@
 
 org.keycloak.testsuite.rest.TestingResourceProviderFactory
 org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory
+org.keycloak.testsuite.rest.TestSamlApplicationResourceProviderFactory
 org.keycloak.testsuite.domainextension.rest.ExampleRealmResourceProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
index 57b9afc..09c280b 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
@@ -22,12 +22,12 @@ import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
 import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
 import org.keycloak.testsuite.client.resources.TestApplicationResource;
 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.runonserver.*;
 import org.keycloak.util.JsonSerialization;
 
 import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLSession;
 
 /**
  * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@@ -70,6 +70,8 @@ public class KeycloakTestingClient {
 
     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 Server server() {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestSamlApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestSamlApplicationResource.java
new file mode 100644
index 0000000..e0bf6d7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestSamlApplicationResource.java
@@ -0,0 +1,55 @@
+/*
+ * 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.client.resources;
+
+import org.keycloak.representations.adapters.action.LogoutAction;
+import org.keycloak.representations.adapters.action.PushNotBeforeAction;
+import org.keycloak.representations.adapters.action.TestAvailabilityAction;
+import org.keycloak.utils.MediaType;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Path("/realms/master/saml-app")
+public interface TestSamlApplicationResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/poll-admin-logout")
+    LogoutAction getAdminLogoutAction();
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/poll-admin-not-before")
+    PushNotBeforeAction getAdminPushNotBefore();
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/poll-test-available")
+    TestAvailabilityAction getTestAvailable();
+
+    @POST
+    @Path("/clear-admin-actions")
+    void clearAdminActions();
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
index 9b001ac..7e8be2a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
@@ -27,6 +27,7 @@ import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.AccountRoles;
 import org.keycloak.models.Constants;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
 import org.keycloak.representations.adapters.action.GlobalRequestResult;
 import org.keycloak.representations.adapters.action.PushNotBeforeAction;
@@ -304,6 +305,7 @@ public class ClientTest extends AbstractAdminTest {
         client.setAdminUrl(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/app/admin");
         client.setRedirectUris(Collections.singletonList(redirectUri));
         client.setSecret("secret");
+        client.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
 
         int notBefore = Time.currentTime() - 60;
         client.setNotBefore(notBefore);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
index 7f811c6..dbd8c43 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
@@ -30,6 +30,8 @@ import org.keycloak.common.util.Time;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.Constants;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.saml.SamlProtocol;
 import org.keycloak.representations.adapters.action.GlobalRequestResult;
 import org.keycloak.representations.adapters.action.PushNotBeforeAction;
 import org.keycloak.representations.idm.AdminEventRepresentation;
@@ -49,6 +51,7 @@ import org.keycloak.testsuite.client.KeycloakTestingClient;
 import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
 import org.keycloak.testsuite.runonserver.RunHelpers;
 import org.keycloak.testsuite.util.AdminEventPaths;
+import org.keycloak.testsuite.util.ClientBuilder;
 import org.keycloak.testsuite.util.CredentialBuilder;
 import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse;
 import org.keycloak.testsuite.util.RealmBuilder;
@@ -66,12 +69,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -541,8 +539,7 @@ public class RealmTest extends AbstractAdminTest {
         GlobalRequestResult globalRequestResult = realm.pushRevocation();
         assertAdminEvents.assertEvent(realmId, OperationType.ACTION, "push-revocation", globalRequestResult, ResourceType.REALM);
 
-        assertEquals(1, globalRequestResult.getSuccessRequests().size());
-        assertEquals("http://localhost:8180/auth/realms/master/app/admin", globalRequestResult.getSuccessRequests().get(0));
+        assertThat(globalRequestResult.getSuccessRequests(), Matchers.containsInAnyOrder("http://localhost:8180/auth/realms/master/app/admin"));
         assertNull(globalRequestResult.getFailedRequests());
 
         PushNotBeforeAction adminPushNotBefore = testingClient.testApp().getAdminPushNotBefore();
@@ -550,6 +547,28 @@ public class RealmTest extends AbstractAdminTest {
     }
 
     @Test
+    public void pushNotBeforeWithSamlApp() {
+        setupTestAppAndUser();
+        setupTestSamlApp();
+
+        int time = Time.currentTime() - 60;
+
+        RealmRepresentation rep = realm.toRepresentation();
+        rep.setNotBefore(time);
+        realm.update(rep);
+        assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM);
+
+        GlobalRequestResult globalRequestResult = realm.pushRevocation();
+        assertAdminEvents.assertEvent(realmId, OperationType.ACTION, "push-revocation", globalRequestResult, ResourceType.REALM);
+
+        assertThat(globalRequestResult.getSuccessRequests(), Matchers.containsInAnyOrder("http://localhost:8180/auth/realms/master/app/admin"));
+        assertThat(globalRequestResult.getFailedRequests(), Matchers.containsInAnyOrder("http://localhost:8180/auth/realms/master/saml-app/saml"));
+
+        PushNotBeforeAction adminPushNotBefore = testingClient.testApp().getAdminPushNotBefore();
+        assertEquals(time, adminPushNotBefore.getNotBefore());
+    }
+
+    @Test
     public void logoutAll() {
         setupTestAppAndUser();
 
@@ -628,6 +647,7 @@ public class RealmTest extends AbstractAdminTest {
         client.setClientId("test-app");
         client.setAdminUrl(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/app/admin");
         client.setRedirectUris(Collections.singletonList(redirectUri));
+        client.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
         client.setSecret("secret");
         Response resp = realm.clients().create(client);
         String clientDbId = ApiUtil.getCreatedId(resp);
@@ -651,4 +671,20 @@ public class RealmTest extends AbstractAdminTest {
         testingClient.testApp().clearAdminActions();
     }
 
+    private void setupTestSamlApp() {
+        String redirectUri = oauth.getRedirectUri().replace("/master/", "/" + REALM_NAME + "/");
+        ClientRepresentation client = ClientBuilder.create()
+          .clientId("test-saml-app")
+          .protocol(SamlProtocol.LOGIN_PROTOCOL)
+          .adminUrl(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/saml-app/saml")
+          .addRedirectUri(redirectUri)
+          .secret("secret")
+          .build();
+        Response resp = realm.clients().create(client);
+        String clientDbId = ApiUtil.getCreatedId(resp);
+        getCleanup().addClientUuid(clientDbId);
+        resp.close();
+        assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(clientDbId), client, ResourceType.CLIENT);
+    }
+
 }