keycloak-uncached

KEYCLOAK-2888 KEYCLOAK-3927 Fully migrate kerberos tests

1/6/2017 4:27:46 PM

Changes

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java 398(+0 -398)

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosCredDelegServlet.java 112(+0 -112)

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java 249(+0 -249)

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java 130(+0 -130)

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestConfiguration.java 149(+0 -149)

testsuite/integration/src/test/resources/kerberos/http.keytab 0(+0 -0)

testsuite/integration/src/test/resources/kerberos/kerberos-ldap-connection.properties 34(+0 -34)

testsuite/integration/src/test/resources/kerberos/kerberos-standalone-connection.properties 23(+0 -23)

testsuite/integration/src/test/resources/kerberos/test-krb5.conf 19(+0 -19)

testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif 104(+0 -104)

testsuite/integration/src/test/resources/kerberos-test/kerberos-app-keycloak.json 10(+0 -10)

testsuite/integration/src/test/resources/kerberos-test/kerberosrealm.json 53(+0 -53)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/META-INF/context.xml 20(+0 -20)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/WEB-INF/jetty-web.xml 27(+0 -27)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/WEB-INF/keycloak.json 10(+0 -10)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/WEB-INF/keycloak-relative.json 9(+0 -9)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/WEB-INF/web.xml 57(+0 -57)

Details

diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
index 4689190..d9c3d20 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
@@ -18,7 +18,9 @@
 package org.keycloak.federation.kerberos;
 
 import org.keycloak.common.constants.KerberosConstants;
+import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.representations.idm.ComponentRepresentation;
 
 import java.util.Map;
 
@@ -29,31 +31,39 @@ import java.util.Map;
  */
 public abstract class CommonKerberosConfig {
 
-    protected ComponentModel componentModel;
+    protected MultivaluedHashMap<String, String> userStorageConfig;
 
     public CommonKerberosConfig(ComponentModel componentModel) {
-        this.componentModel = componentModel;
+        this.userStorageConfig = componentModel.getConfig();
+    }
+
+    public CommonKerberosConfig(ComponentRepresentation componentRep) {
+        this.userStorageConfig = componentRep.getConfig();
+    }
+
+    protected MultivaluedHashMap<String, String> getConfig() {
+        return userStorageConfig;
     }
 
     // Should be always true for KerberosFederationProvider
     public boolean isAllowKerberosAuthentication() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION));
     }
 
     public String getKerberosRealm() {
-        return componentModel.getConfig().getFirst(KerberosConstants.KERBEROS_REALM);
+        return getConfig().getFirst(KerberosConstants.KERBEROS_REALM);
     }
 
     public String getServerPrincipal() {
-        return componentModel.getConfig().getFirst(KerberosConstants.SERVER_PRINCIPAL);
+        return getConfig().getFirst(KerberosConstants.SERVER_PRINCIPAL);
     }
 
     public String getKeyTab() {
-        return componentModel.getConfig().getFirst(KerberosConstants.KEYTAB);
+        return getConfig().getFirst(KerberosConstants.KEYTAB);
     }
 
     public boolean isDebug() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.DEBUG));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.DEBUG));
     }
 
 
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java
index 26badf9..010f2c3 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java
@@ -20,6 +20,7 @@ package org.keycloak.federation.kerberos;
 import org.keycloak.common.constants.KerberosConstants;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.models.LDAPConstants;
+import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.storage.UserStorageProvider.EditMode;
 
 /**
@@ -33,8 +34,12 @@ public class KerberosConfig extends CommonKerberosConfig {
         super(component);
     }
 
+    public KerberosConfig(ComponentRepresentation component) {
+        super(component);
+    }
+
     public EditMode getEditMode() {
-        String editModeString = componentModel.getConfig().getFirst(LDAPConstants.EDIT_MODE);
+        String editModeString = getConfig().getFirst(LDAPConstants.EDIT_MODE);
         if (editModeString == null) {
             return EditMode.UNSYNCED;
         } else {
@@ -43,11 +48,11 @@ public class KerberosConfig extends CommonKerberosConfig {
     }
 
     public boolean isAllowPasswordAuthentication() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION));
     }
 
     public boolean isUpdateProfileFirstLogin() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN));
     }
 
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java
index 87bfcaf..80b666b 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java
@@ -20,6 +20,7 @@ package org.keycloak.storage.ldap.kerberos;
 import org.keycloak.common.constants.KerberosConstants;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.storage.ldap.LDAPStorageProvider;
 
 /**
@@ -33,7 +34,11 @@ public class LDAPProviderKerberosConfig extends CommonKerberosConfig {
         super(componentModel);
     }
 
+    public LDAPProviderKerberosConfig(ComponentRepresentation componentRep) {
+        super(componentRep);
+    }
+
     public boolean isUseKerberosForPasswordAuthentication() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION));
     }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestConfiguration.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestConfiguration.java
index b254a1a..f4cbc22 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestConfiguration.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestConfiguration.java
@@ -84,9 +84,9 @@ public class LDAPTestConfiguration {
         DEFAULT_VALUES.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "false");
         DEFAULT_VALUES.put(KerberosConstants.KERBEROS_REALM, "KEYCLOAK.ORG");
         DEFAULT_VALUES.put(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@KEYCLOAK.ORG");
-        URL keytabUrl = LDAPTestConfiguration.class.getResource("/kerberos/http.keytab");
-        String keyTabPath = new File(keytabUrl.getFile()).getAbsolutePath();
-        DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath);
+//        URL keytabUrl = LDAPTestConfiguration.class.getResource("/kerberos/http.keytab");
+//        String keyTabPath = new File(keytabUrl.getFile()).getAbsolutePath();
+//        DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath);
         DEFAULT_VALUES.put(KerberosConstants.DEBUG, "true");
         DEFAULT_VALUES.put(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION, "true");
         DEFAULT_VALUES.put(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "true");
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index 28e685e..cfcef5a 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -82,6 +82,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -656,6 +658,13 @@ public class TestingResourceProvider implements RealmResourceProvider {
         return session.getContext().getRealm().getIdentityProviderByAlias(alias).getConfig();
     }
 
+    @PUT
+    @Path("/set-krb5-conf-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setKrb5ConfFile(@QueryParam("krb5-conf-file") String krb5ConfFile) {
+        System.setProperty("java.security.krb5.conf", krb5ConfFile);
+    }
+
     private RealmModel getRealmByName(String realmName) {
         RealmProvider realmProvider = session.getProvider(RealmProvider.class);
         return realmProvider.getRealmByName(realmName);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
index bed2d32..9bb73d3 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
@@ -256,4 +256,9 @@ public interface TestingResource {
     @Path("/component")
     @Produces(MediaType.APPLICATION_JSON)
     MultivaluedHashMap<String, String> getComponentConfig(@QueryParam("componentId") String componentId);
+
+    @PUT
+    @Path("/set-krb5-conf-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    void setKrb5ConfFile(@QueryParam("krb5-conf-file") String krb5ConfFile);
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPTestConfiguration.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPTestConfiguration.java
index 772ae2d..b148a82 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPTestConfiguration.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPTestConfiguration.java
@@ -28,6 +28,7 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -88,7 +89,7 @@ public class LDAPTestConfiguration {
         DEFAULT_VALUES.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "false");
         DEFAULT_VALUES.put(KerberosConstants.KERBEROS_REALM, "KEYCLOAK.ORG");
         DEFAULT_VALUES.put(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@KEYCLOAK.ORG");
-        String keyTabPath =  getResource("http.keytab");
+        String keyTabPath =  getResource("/kerberos/http.keytab");
         DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath);
         DEFAULT_VALUES.put(KerberosConstants.DEBUG, "true");
         DEFAULT_VALUES.put(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION, "true");
@@ -102,8 +103,10 @@ public class LDAPTestConfiguration {
         return ldapTestConfiguration;
     }
     
-    public static String getResource(String resourceName) {
-        return new File(PROJECT_BUILD_DIRECTORY, "dependency/kerberos/" + resourceName).getAbsolutePath();
+    public static String getResource(String resourcePath) {
+        URL urlPath = LDAPTestConfiguration.class.getResource(resourcePath);
+        String absolutePath = new File(urlPath.getFile()).getAbsolutePath();
+        return absolutePath;
     }
 
     protected void loadConnectionProperties(String connectionPropertiesLocation) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
new file mode 100644
index 0000000..2164ccd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
@@ -0,0 +1,443 @@
+/*
+ * 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.federation.kerberos;
+
+import java.net.URI;
+import java.security.Principal;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.security.sasl.Sasl;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.ietf.jgss.GSSCredential;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.HttpClientBuilder;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.common.constants.KerberosConstants;
+import org.keycloak.common.util.KerberosSerializationUtils;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.events.Details;
+import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.testsuite.AbstractAuthTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.AuthRealm;
+import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.util.OAuthClient;
+import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
+import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractKerberosTest extends AbstractAuthTest {
+
+    protected KeycloakSPNegoSchemeFactory spnegoSchemeFactory;
+
+    protected ResteasyClient client;
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+    @Page
+    protected AccountPasswordPage changePasswordPage;
+
+    protected abstract CommonKerberosConfig getKerberosConfig();
+
+    protected abstract ComponentRepresentation getUserStorageConfiguration();
+
+    protected abstract void setKrb5ConfPath();
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation realmRep = loadJson(getClass().getResourceAsStream("/kerberos/kerberosrealm.json"), RealmRepresentation.class);
+        testRealms.add(realmRep);
+    }
+
+
+    @Before
+    public void beforeAbstractKeycloakTest() throws Exception {
+        super.beforeAbstractKeycloakTest();
+
+        testRealmPage.setAuthRealm(AuthRealm.TEST);
+        changePasswordPage.realm(AuthRealm.TEST);
+
+        setKrb5ConfPath();
+
+        spnegoSchemeFactory = new KeycloakSPNegoSchemeFactory(getKerberosConfig());
+        initHttpClient(true);
+        removeAllUsers();
+
+        oauth.clientId("kerberos-app");
+
+        ComponentRepresentation rep = getUserStorageConfiguration();
+        testRealmResource().components().add(rep);
+    }
+
+    @After
+    public void afterAbstractKeycloakTest() {
+        cleanupApacheHttpClient();
+
+        super.afterAbstractKeycloakTest();
+    }
+
+    private void cleanupApacheHttpClient() {
+        client.close();
+        client = null;
+    }
+
+//    @Test
+//    public void sleepTest() throws Exception {
+//        String kcLoginPageLocation = oauth.getLoginFormUrl();
+//        Thread.sleep(10000000);
+//    }
+
+
+    @Test
+    public void spnegoNotAvailableTest() throws Exception {
+        initHttpClient(false);
+
+        String kcLoginPageLocation = oauth.getLoginFormUrl();
+
+        Response response = client.target(kcLoginPageLocation).request().get();
+        Assert.assertEquals(401, response.getStatus());
+        Assert.assertEquals(KerberosConstants.NEGOTIATE, response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE));
+        String responseText = response.readEntity(String.class);
+        response.close();
+    }
+
+
+    protected OAuthClient.AccessTokenResponse spnegoLoginTestImpl() throws Exception {
+        Response spnegoResponse = spnegoLogin("hnelson", "secret");
+        Assert.assertEquals(302, spnegoResponse.getStatus());
+
+        List<UserRepresentation> users = testRealmResource().users().search("hnelson", 0, 1);
+        String userId = users.get(0).getId();
+        events.expectLogin()
+                .client("kerberos-app")
+                .user(userId)
+                .detail(Details.USERNAME, "hnelson")
+                .assertEvent();
+
+        String codeUrl = spnegoResponse.getLocation().toString();
+
+        return assertAuthenticationSuccess(codeUrl);
+    }
+
+
+    protected abstract boolean isCaseSensitiveLogin();
+
+    // KEYCLOAK-2102
+    @Test
+    public void spnegoCaseInsensitiveTest() throws Exception {
+        Response spnegoResponse = spnegoLogin(isCaseSensitiveLogin() ? "MyDuke" : "myduke", "theduke");
+        Assert.assertEquals(302, spnegoResponse.getStatus());
+        List<UserRepresentation> users = testRealmResource().users().search("myduke", 0, 1);
+        String userId = users.get(0).getId();
+        events.expectLogin()
+                .client("kerberos-app")
+                .user(userId)
+                .detail(Details.USERNAME, "myduke")
+                .assertEvent();
+
+        String codeUrl = spnegoResponse.getLocation().toString();
+
+        assertAuthenticationSuccess(codeUrl);
+    }
+
+    @Test
+    public void usernamePasswordLoginTest() throws Exception {
+        // Change editMode to READ_ONLY
+        updateProviderEditMode(UserStorageProvider.EditMode.READ_ONLY);
+
+        // Login with username/password from kerberos
+        changePasswordPage.open();
+        loginPage.assertCurrent();
+        loginPage.login("jduke", "theduke");
+        changePasswordPage.assertCurrent();
+
+        // Bad existing password
+        changePasswordPage.changePassword("theduke-invalid", "newPass", "newPass");
+        Assert.assertTrue(driver.getPageSource().contains("Invalid existing password."));
+
+        // Change password is not possible as editMode is READ_ONLY
+        changePasswordPage.changePassword("theduke", "newPass", "newPass");
+        Assert.assertTrue(
+                driver.getPageSource().contains("You can't update your password as your account is read only"));
+
+        // Change editMode to UNSYNCED
+        updateProviderEditMode(UserStorageProvider.EditMode.UNSYNCED);
+
+        // Successfully change password now
+        changePasswordPage.changePassword("theduke", "newPass", "newPass");
+        Assert.assertTrue(driver.getPageSource().contains("Your password has been updated."));
+        changePasswordPage.logout();
+
+        // Login with old password doesn't work, but with new password works
+        loginPage.login("jduke", "theduke");
+        loginPage.assertCurrent();
+        loginPage.login("jduke", "newPass");
+        changePasswordPage.assertCurrent();
+        changePasswordPage.logout();
+
+        // Assert SPNEGO login still with the old password as mode is unsynced
+        events.clear();
+        Response spnegoResponse = spnegoLogin("jduke", "theduke");
+        Assert.assertEquals(302, spnegoResponse.getStatus());
+        List<UserRepresentation> users = testRealmResource().users().search("jduke", 0, 1);
+        String userId = users.get(0).getId();
+        events.expectLogin()
+                .client("kerberos-app")
+                .user(userId)
+                .detail(Details.USERNAME, "jduke")
+                .assertEvent();
+
+        String codeUrl = spnegoResponse.getLocation().toString();
+
+        assertAuthenticationSuccess(codeUrl);
+    }
+
+
+    @Test
+    public void credentialDelegationTest() throws Exception {
+        // Add kerberos delegation credential mapper
+        ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+                KerberosConstants.GSS_DELEGATION_CREDENTIAL,
+                KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
+                true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+                true, false);
+        ProtocolMapperRepresentation protocolMapperRep = ModelToRepresentation.toRepresentation(protocolMapper);
+        ClientResource clientResource = findClientByClientId(testRealmResource(), "kerberos-app");
+        Response response = clientResource.getProtocolMappers().createMapper(protocolMapperRep);
+        String protocolMapperId = ApiUtil.getCreatedId(response);
+        response.close();
+
+        // SPNEGO login
+        OAuthClient.AccessTokenResponse tokenResponse = spnegoLoginTestImpl();
+
+        // Assert kerberos ticket in the accessToken can be re-used to authenticate against other 3rd party kerberos service (ApacheDS Server in this case)
+        String accessToken = tokenResponse.getAccessToken();
+        AccessToken token = oauth.verifyToken(accessToken);
+
+        String serializedGssCredential = (String) token.getOtherClaims().get(KerberosConstants.GSS_DELEGATION_CREDENTIAL);
+        Assert.assertNotNull(serializedGssCredential);
+        GSSCredential gssCredential = KerberosSerializationUtils.deserializeCredential(serializedGssCredential);
+        String ldapResponse = invokeLdap(gssCredential, token.getPreferredUsername());
+        Assert.assertEquals("Horatio Nelson", ldapResponse);
+
+        // Logout
+        oauth.openLogout();
+
+        // Remove protocolMapper
+        clientResource.getProtocolMappers().delete(protocolMapperId);
+
+        // Login and assert delegated credential not anymore
+        tokenResponse = spnegoLoginTestImpl();
+        accessToken = tokenResponse.getAccessToken();
+        token = oauth.verifyToken(accessToken);
+        Assert.assertFalse(token.getOtherClaims().containsKey(KerberosConstants.GSS_DELEGATION_CREDENTIAL));
+
+        events.clear();
+    }
+
+    private String invokeLdap(GSSCredential gssCredential, String username) throws NamingException {
+        Hashtable env = new Hashtable(11);
+        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put(Context.PROVIDER_URL, "ldap://localhost:10389");
+
+        if (gssCredential != null) {
+            env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+            env.put(Sasl.CREDENTIALS, gssCredential);
+        }
+
+        DirContext ctx = new InitialDirContext(env);
+        try {
+            Attributes attrs = ctx.getAttributes("uid=" + username + ",ou=People,dc=keycloak,dc=org");
+            String cn = (String) attrs.get("cn").get();
+            String sn = (String) attrs.get("sn").get();
+            return cn + " " + sn;
+        } finally {
+            ctx.close();
+        }
+    }
+
+
+    protected Response spnegoLogin(String username, String password) {
+        String kcLoginPageLocation = oauth.getLoginFormUrl();
+
+        // Request for SPNEGO login sent with Resteasy client
+        spnegoSchemeFactory.setCredentials(username, password);
+        Response response = client.target(kcLoginPageLocation).request().get();
+        if (response.getStatus() == 302) {
+            if (response.getLocation() == null)
+                return response;
+            String uri = response.getLocation().toString();
+            if (uri.contains("login-actions/required-action")) {
+                response = client.target(uri).request().get();
+            }
+        }
+        return response;
+
+    }
+
+
+    protected void initHttpClient(boolean useSpnego) {
+        if (client != null) {
+            cleanupApacheHttpClient();
+        }
+
+        DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder().build();
+        httpClient.getAuthSchemes().register(AuthPolicy.SPNEGO, spnegoSchemeFactory);
+
+        if (useSpnego) {
+            Credentials fake = new Credentials() {
+
+                public String getPassword() {
+                    return null;
+                }
+
+                public Principal getUserPrincipal() {
+                    return null;
+                }
+
+            };
+
+            httpClient.getCredentialsProvider().setCredentials(
+                    new AuthScope(null, -1, null),
+                    fake);
+        }
+        ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
+        client = new ResteasyClientBuilder().httpEngine(engine).build();
+    }
+
+
+    protected void removeAllUsers() {
+        RealmResource realm = testRealmResource();
+        List<UserRepresentation> users = realm.users().search("", 0, Integer.MAX_VALUE);
+        for (UserRepresentation user : users) {
+            if (!user.getUsername().equals(AssertEvents.DEFAULT_USERNAME)) {
+                realm.users().get(user.getId()).remove();
+            }
+        }
+        Assert.assertEquals(1, realm.users().search("", 0, Integer.MAX_VALUE).size());
+    }
+
+
+    protected void assertUser(String expectedUsername, String expectedEmail, String expectedFirstname,
+                              String expectedLastname, boolean updateProfileActionExpected) {
+        try {
+            UserRepresentation user = ApiUtil.findUserByUsername(testRealmResource(), expectedUsername);
+            Assert.assertNotNull(user);
+            Assert.assertEquals(expectedEmail, user.getEmail());
+            Assert.assertEquals(expectedFirstname, user.getFirstName());
+            Assert.assertEquals(expectedLastname, user.getLastName());
+
+            if (updateProfileActionExpected) {
+                Assert.assertEquals(UserModel.RequiredAction.UPDATE_PROFILE.toString(),
+                        user.getRequiredActions().iterator().next());
+            } else {
+                Assert.assertTrue(user.getRequiredActions().isEmpty());
+            }
+        } finally {
+        }
+    }
+
+
+    protected OAuthClient.AccessTokenResponse assertAuthenticationSuccess(String codeUrl) throws Exception {
+        List<NameValuePair> pairs = URLEncodedUtils.parse(new URI(codeUrl), "UTF-8");
+        String code = null;
+        String state = null;
+        for (NameValuePair pair : pairs) {
+            if (pair.getName().equals(OAuth2Constants.CODE)) {
+                code = pair.getValue();
+            } else if (pair.getName().equals(OAuth2Constants.STATE)) {
+                state = pair.getValue();
+            }
+        }
+        Assert.assertNotNull(code);
+        Assert.assertNotNull(state);
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+        Assert.assertNotNull(response.getAccessToken());
+        events.clear();
+        return response;
+    }
+
+
+    protected void updateProviderEditMode(UserStorageProvider.EditMode editMode) {
+        List<ComponentRepresentation> reps = testRealmResource().components().query("test", UserStorageProvider.class.getName());
+        Assert.assertEquals(1, reps.size());
+        ComponentRepresentation kerberosProvider = reps.get(0);
+        kerberosProvider.getConfig().putSingle(LDAPConstants.EDIT_MODE, editMode.toString());
+        testRealmResource().components().component(kerberosProvider.getId()).update(kerberosProvider);
+    }
+
+    public RealmResource testRealmResource() {
+        return adminClient.realm("test");
+    }
+
+
+    // TODO: Use LDAPTestUtils.toComponentConfig once it's migrated to new testsuite
+    public static MultivaluedHashMap<String, String> toComponentConfig(Map<String, String> ldapConfig) {
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        for (Map.Entry<String, String> entry : ldapConfig.entrySet()) {
+            config.add(entry.getKey(), entry.getValue());
+
+        }
+        return config;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
new file mode 100644
index 0000000..9ed0cc9
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.federation.kerberos;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.common.constants.KerberosConstants;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.federation.kerberos.KerberosConfig;
+import org.keycloak.federation.kerberos.KerberosFederationProviderFactory;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.testsuite.util.KerberosRule;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KerberosStandaloneTest extends AbstractKerberosTest {
+
+    private static final String PROVIDER_CONFIG_LOCATION = "classpath:kerberos/kerberos-standalone-connection.properties";
+
+    @ClassRule
+    public static KerberosRule kerberosRule = new KerberosRule(PROVIDER_CONFIG_LOCATION);
+
+    @Override
+    protected CommonKerberosConfig getKerberosConfig() {
+        return new KerberosConfig(getUserStorageConfiguration());
+    }
+
+    @Override
+    protected ComponentRepresentation getUserStorageConfiguration() {
+        Map<String,String> kerberosConfig = kerberosRule.getConfig();
+        MultivaluedHashMap<String, String> config = toComponentConfig(kerberosConfig);
+
+        UserStorageProviderModel model = new UserStorageProviderModel();
+        model.setLastSync(0);
+        model.setChangedSyncPeriod(-1);
+        model.setFullSyncPeriod(-1);
+        model.setName("kerberos-standalone");
+        model.setPriority(0);
+        model.setProviderId(KerberosFederationProviderFactory.PROVIDER_NAME);
+        model.setConfig(config);
+
+        ComponentRepresentation rep = ModelToRepresentation.toRepresentationWithoutConfig(model);
+        return rep;
+    }
+
+
+    @Override
+    protected boolean isCaseSensitiveLogin() {
+        return kerberosRule.isCaseSensitiveLogin();
+    }
+
+
+    @Override
+    protected void setKrb5ConfPath() {
+        kerberosRule.setKrb5ConfPath(testingClient.testing());
+    }
+
+    @Test
+    public void spnegoLoginTest() throws Exception {
+        spnegoLoginTestImpl();
+
+        // Assert user was imported and hasn't any required action on him. Profile info is synced from LDAP
+        assertUser("hnelson", "hnelson@keycloak.org", null, null, false);
+    }
+
+
+    @Test
+    public void updateProfileEnabledTest() throws Exception {
+        // Switch updateProfileOnFirstLogin to on
+        List<ComponentRepresentation> reps = testRealmResource().components().query("test", UserStorageProvider.class.getName());
+        org.keycloak.testsuite.Assert.assertEquals(1, reps.size());
+        ComponentRepresentation kerberosProvider = reps.get(0);
+        kerberosProvider.getConfig().putSingle(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "true");
+        testRealmResource().components().component(kerberosProvider.getId()).update(kerberosProvider);
+
+        // Assert update profile page is displayed
+        Response spnegoResponse = spnegoLogin("hnelson", "secret");
+        Assert.assertEquals(200, spnegoResponse.getStatus());
+        String responseText = spnegoResponse.readEntity(String.class);
+        Assert.assertTrue(responseText.contains("You need to update your user profile to activate your account."));
+        Assert.assertTrue(responseText.contains("hnelson@keycloak.org"));
+        spnegoResponse.close();
+
+        // Assert user was imported and has required action on him
+        assertUser("hnelson", "hnelson@keycloak.org", null, null, true);
+
+        // Switch updateProfileOnFirstLogin to off
+        kerberosProvider.getConfig().putSingle(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "false");
+        testRealmResource().components().component(kerberosProvider.getId()).update(kerberosProvider);
+    }
+
+
+    /**
+     * KEYCLOAK-3451
+     *
+     * Test that if there is no User Storage Provider that can handle kerberos we can still login
+     *
+     * @throws Exception
+     */
+    @Test
+    public void noProvider() throws Exception {
+        List<ComponentRepresentation> reps = testRealmResource().components().query("test", UserStorageProvider.class.getName());
+        org.keycloak.testsuite.Assert.assertEquals(1, reps.size());
+        ComponentRepresentation kerberosProvider = reps.get(0);
+        testRealmResource().components().component(kerberosProvider.getId()).remove();
+
+        /*
+         To do this we do a valid kerberos login.  The authenticator will obtain a valid token, but there will
+         be no user storage provider that can process it.  This means we should be on the login page.
+         We do this through a JAX-RS client request.  We extract the action URL from the login page, and stuff it
+         into selenium then just perform a regular login.
+         */
+        Response spnegoResponse = spnegoLogin("hnelson", "secret");
+        String context = spnegoResponse.readEntity(String.class);
+        spnegoResponse.close();
+        Pattern pattern = Pattern.compile("action=\"([^\"]+)\"");
+        Matcher m = pattern.matcher(context);
+        Assert.assertTrue(m.find());
+        String url = m.group(1);
+        driver.navigate().to(url);
+        Assert.assertTrue(loginPage.isCurrent());
+        loginPage.login("test-user@localhost", "password");
+        String pageSource = driver.getPageSource();
+        assertAuthenticationSuccess(driver.getCurrentUrl());
+
+        events.clear();
+        testRealmResource().components().add(kerberosProvider);
+    }
+
+}