keycloak-aplcache

Merge pull request #3437 from patriot1burke/master disable

10/28/2016 1:33:16 PM

Changes

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index e7661cd..2a4ec62 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -24,6 +24,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -48,6 +49,7 @@ public class UserRepresentation {
     @JsonDeserialize(using = StringListMapDeserializer.class)
     protected Map<String, List<String>> attributes;
     protected List<CredentialRepresentation> credentials;
+    protected Set<String> disableableCredentialTypes;
     protected List<String> requiredActions;
     protected List<FederatedIdentityRepresentation> federatedIdentities;
     protected List<String> realmRoles;
@@ -254,4 +256,12 @@ public class UserRepresentation {
     public void setOrigin(String origin) {
         this.origin = origin;
     }
+
+    public Set<String> getDisableableCredentialTypes() {
+        return disableableCredentialTypes;
+    }
+
+    public void setDisableableCredentialTypes(Set<String> disableableCredentialTypes) {
+        this.disableableCredentialTypes = disableableCredentialTypes;
+    }
 }
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
index 151fa01..77f3083 100644
--- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
@@ -29,7 +29,10 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.cache.CachedUserModel;
 import org.keycloak.models.cache.OnUserCache;
 
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -82,12 +85,24 @@ public class SecretQuestionCredentialProvider implements CredentialProvider, Cre
     @Override
     public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
         if (!SECRET_QUESTION.equals(credentialType)) return;
-        session.userCredentialManager().disableCredential(realm, user, credentialType);
+        session.userCredentialManager().disableCredentialType(realm, user, credentialType);
         session.getUserCache().evict(realm, user);
 
     }
 
     @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        if (!session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION).isEmpty()) {
+            Set<String> set = new HashSet<>();
+            set.add(SECRET_QUESTION);
+            return set;
+        } else {
+            return Collections.EMPTY_SET;
+        }
+
+    }
+
+    @Override
     public boolean supportsCredentialType(String credentialType) {
         return SECRET_QUESTION.equals(credentialType);
     }
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
index 2a0597c..19f9f75 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
@@ -23,7 +23,9 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 
+import java.util.Collections;
 import java.util.Properties;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -90,4 +92,9 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
     public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
 
     }
+
+    @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        return Collections.EMPTY_SET;
+    }
 }
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java
index aac080b..f8262f1 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java
@@ -25,7 +25,9 @@ import org.keycloak.models.UserModel;
 
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Properties;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -108,4 +110,9 @@ public class FilePropertiesFederationProvider extends BasePropertiesFederationPr
     public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
 
     }
+
+    @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        return Collections.EMPTY_SET;
+    }
 }
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
index 8f7f147..837f2b6 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
@@ -44,9 +44,11 @@ import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -200,6 +202,17 @@ public class EjbExampleUserStorageProvider implements UserStorageProvider,
     }
 
     @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        if (getUserAdapter(user).getPassword() != null) {
+            Set<String> set = new HashSet<>();
+            set.add(CredentialModel.PASSWORD);
+            return set;
+        } else {
+            return Collections.emptySet();
+        }
+    }
+
+    @Override
     public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
         return supportsCredentialType(credentialType) && getPassword(user) != null;
     }
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
index 9d82655..d656a0d 100755
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
@@ -165,6 +165,11 @@ public class KerberosFederationProvider implements UserFederationProvider {
     }
 
     @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        return Collections.EMPTY_SET;
+    }
+
+    @Override
     public boolean supportsCredentialType(String credentialType) {
         return credentialType.equals(CredentialModel.KERBEROS) || (kerberosConfig.isAllowPasswordAuthentication() && credentialType.equals(CredentialModel.PASSWORD));
     }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 14c99a6..880f0f7 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -425,6 +425,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
     }
 
     @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        return Collections.EMPTY_SET;
+    }
+
+    @Override
     public boolean supportsCredentialType(String credentialType) {
         return getSupportedCredentialTypes().contains(credentialType);
     }
diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java
index 8e66943..2820947 100755
--- a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java
+++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java
@@ -181,6 +181,11 @@ public class SSSDFederationProvider implements UserFederationProvider {
     }
 
     @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        return Collections.EMPTY_SET;
+    }
+
+    @Override
     public boolean supportsCredentialType(String credentialType) {
         return CredentialModel.PASSWORD.equals(credentialType);
     }
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
index f4e3165..9a0d2b7 100644
--- a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
@@ -19,6 +19,9 @@ package org.keycloak.credential;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 
+import java.util.List;
+import java.util.Set;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
@@ -27,4 +30,14 @@ public interface CredentialInputUpdater {
     boolean supportsCredentialType(String credentialType);
     boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input);
     void disableCredentialType(RealmModel realm, UserModel user, String credentialType);
+
+    /**
+     *
+     * Returns a set of credential types that can be disabled by disableCredentialType() method
+     *
+     * @param realm
+     * @param user
+     * @return
+     */
+    Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user);
 }
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java
index 95e1d1e..0e03bba 100644
--- a/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java
@@ -19,6 +19,8 @@ package org.keycloak.credential;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 
+import java.util.List;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java
index f03ac8f..f5e8aab 100644
--- a/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java
@@ -20,6 +20,7 @@ import org.keycloak.credential.CredentialInput;
 import org.keycloak.credential.UserCredentialStore;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -67,7 +68,16 @@ public interface UserCredentialManager extends UserCredentialStore {
      * @param user
      * @param credentialType
      */
-    void disableCredential(RealmModel realm, UserModel user, String credentialType);
+    void disableCredentialType(RealmModel realm, UserModel user, String credentialType);
+
+    /**
+     * Returns a set of credential types that can be disabled by disableCredentialType() method
+     *
+     * @param realm
+     * @param user
+     * @return
+     */
+    Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user);
 
     /**
      * Checks to see if user has credential type configured.  Looks in UserStorageProvider or UserFederationProvider first,
@@ -82,7 +92,8 @@ public interface UserCredentialManager extends UserCredentialStore {
 
     /**
      * Only loops through each CredentialProvider to see if credential type is configured for the user.
-     * This allows UserStorageProvider and UserFederationProvider to look to abort isValid
+     * This allows UserStorageProvider and UserFederationProvider isValid() implementations to punt to local storage
+     * when validating a credential that has been overriden in Keycloak storage.
      *
      * @param realm
      * @param user
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 5a18a6e..e35da61 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -189,6 +189,7 @@ public class ModelToRepresentation {
         rep.setEnabled(user.isEnabled());
         rep.setEmailVerified(user.isEmailVerified());
         rep.setTotp(session.userCredentialManager().isConfiguredFor(realm, user, CredentialModel.OTP));
+        rep.setDisableableCredentialTypes(session.userCredentialManager().getDisableableCredentialTypes(realm, user));
         rep.setFederationLink(user.getFederationLink());
 
         List<String> reqActions = new ArrayList<String>();
diff --git a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
index f5c973a..f71f84a 100644
--- a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
@@ -29,7 +29,9 @@ import org.keycloak.models.utils.HmacOTP;
 import org.keycloak.models.utils.TimeBasedOTP;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -140,6 +142,19 @@ public class OTPCredentialProvider implements CredentialProvider, CredentialInpu
     }
 
     @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        if (!getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP).isEmpty()
+        || !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP).isEmpty()) {
+            Set<String> set = new HashSet<>();
+            set.add(CredentialModel.OTP);
+            return set;
+        } else {
+            return Collections.EMPTY_SET;
+        }
+    }
+
+
+    @Override
     public boolean supportsCredentialType(String credentialType) {
         return CredentialModel.OTP.equals(credentialType)
                 || CredentialModel.HOTP.equals(credentialType)
diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
index 4fc5856..d0558ab 100644
--- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
@@ -33,8 +33,10 @@ import org.keycloak.policy.PolicyError;
 
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -151,6 +153,17 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
     }
 
     @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        if (!getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD).isEmpty()) {
+            Set<String> set = new HashSet<>();
+            set.add(CredentialModel.PASSWORD);
+            return set;
+        } else {
+            return Collections.EMPTY_SET;
+        }
+    }
+
+    @Override
     public boolean supportsCredentialType(String credentialType) {
         return credentialType.equals(CredentialModel.PASSWORD);
     }
diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
index 9adbae6..a8b4110 100644
--- a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
+++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
@@ -35,9 +35,11 @@ import org.keycloak.storage.UserStorageProvider;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -196,7 +198,7 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
         }
     }
     @Override
-    public void disableCredential(RealmModel realm, UserModel user, String credentialType) {
+    public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
         if (!StorageId.isLocalStorage(user)) {
             String providerId = StorageId.resolveProviderId(user);
             UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
@@ -229,6 +231,38 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
 
 
     }
+
+    @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        Set<String> types = new HashSet<>();
+        if (!StorageId.isLocalStorage(user)) {
+            String providerId = StorageId.resolveProviderId(user);
+            UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+            if (provider instanceof CredentialInputUpdater) {
+                CredentialInputUpdater updater = (CredentialInputUpdater)provider;
+                types.addAll(updater.getDisableableCredentialTypes(realm, user));
+            }
+        } else {
+            UserFederationProvider link = session.users().getFederationLink(realm, user);
+            if (link != null) {
+                types.addAll(link.getDisableableCredentialTypes(realm, user));
+            }
+            else if (user.getFederationLink() != null) {
+                UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, user.getFederationLink());
+                if (provider != null && provider instanceof CredentialInputUpdater) {
+                    types.addAll(((CredentialInputUpdater)provider).getDisableableCredentialTypes(realm, user));
+                }
+            }
+
+        }
+
+        List<CredentialInputUpdater> credentialProviders = getCredentialProviders(realm, CredentialInputUpdater.class);
+        for (CredentialInputUpdater updater : credentialProviders) {
+            types.addAll(updater.getDisableableCredentialTypes(realm, user));
+        }
+        return types;
+    }
+
     @Override
     public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
         if (!StorageId.isLocalStorage(user)) {
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 2684483..7a50644 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -440,7 +440,7 @@ public class AccountService extends AbstractSecuredLocalService {
         csrfCheck(stateChecker);
 
         UserModel user = auth.getUser();
-        session.userCredentialManager().disableCredential(realm, user, CredentialModel.OTP);
+        session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
 
         event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index fdd3faa..f9ae190 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -719,7 +719,32 @@ public class UsersResource {
 
     }
 
-     /**
+    /**
+     * Disable all credentials for a user of a specific type
+     *
+     * @param id
+     * @param credentialTypes
+     */
+    @Path("{id}/disable-credential-types")
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void disableCredentialType(@PathParam("id") String id, List<String> credentialTypes) {
+        auth.requireManage();
+
+        UserModel user = session.users().getUserById(id, realm);
+        if (user == null) {
+            throw new NotFoundException("User not found");
+        }
+        if (credentialTypes == null) return;
+        for (String type : credentialTypes) {
+            session.userCredentialManager().disableCredentialType(realm, user, type);
+
+        }
+
+
+    }
+
+    /**
      * Set up a temporary password for the user
      *
      * User will have to reset the temporary password next time they log in.
@@ -777,7 +802,7 @@ public class UsersResource {
             throw new NotFoundException("User not found");
         }
 
-        session.userCredentialManager().disableCredential(realm, user, CredentialModel.OTP);
+        session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
         adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
index 75e12f7..445b350 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
@@ -33,7 +33,9 @@ import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
 import org.keycloak.storage.user.UserLookupProvider;
 import org.keycloak.storage.user.UserRegistrationProvider;
 
+import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -104,6 +106,11 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider, 
     }
 
     @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        return Collections.EMPTY_SET;
+    }
+
+    @Override
     public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
         return CredentialModel.PASSWORD.equals(credentialType);
     }
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProvider.java
index 58493d0..c0dd8d5 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationProvider.java
@@ -124,6 +124,11 @@ public class DummyUserFederationProvider implements UserFederationProvider {
     }
 
     @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        return Collections.EMPTY_SET;
+    }
+
+    @Override
     public boolean supportsCredentialType(String credentialType) {
         return getSupportedCredentialTypes().contains(credentialType);
     }
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 27cb790..b330a3c 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -1206,4 +1206,16 @@ userStorage.cachePolicy.maxLifespan=Max Lifespan
 userStorage.cachePolicy.maxLifespan.tooltip=Max lifespan of a user cache entry in milliseconds.
 user-origin-link=Storage Origin
 
+disable=Disable
+disableable-credential-types=Disableable Types
+credentials.disableable.tooltip=List of credential types that you can disable
+disable-credential-types=Disable Credential Types
+credentials.disable.tooltip=Click button to disable selected credential types
+credential-types=Credential Types
+manage-user-password=Manage Password
+disable-credentials=Disable Credentials
+credential-reset-actions=Credential Reset
+
+
+
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index 1e7eb29..03addd9 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -501,7 +501,7 @@ module.controller('UserDetailCtrl', function($scope, realm, user, BruteForceUser
     }
 });
 
-module.controller('UserCredentialsCtrl', function($scope, realm, user, RequiredActions, User, UserExecuteActionsEmail, UserCredentials, Notifications, Dialog) {
+module.controller('UserCredentialsCtrl', function($scope, realm, user, $route, RequiredActions, User, UserExecuteActionsEmail, UserCredentials, Notifications, Dialog) {
     console.log('UserCredentialsCtrl');
 
     $scope.realm = realm;
@@ -554,18 +554,19 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, RequiredA
         });
     };
 
-    $scope.removeTotp = function() {
-        Dialog.confirm('Remove totp', 'Are you sure you want to remove the users totp configuration?', function() {
-            UserCredentials.removeTotp({ realm: realm.realm, userId: user.id }, { }, function() {
-                Notifications.success("The users totp configuration has been removed");
-                $scope.user.totp = false;
+    $scope.disableCredentialTypes = function() {
+        Dialog.confirm('Disable credentials', 'Are you sure you want to disable these the users credentials?', function() {
+            UserCredentials.disableCredentialTypes({ realm: realm.realm, userId: user.id }, $scope.disableableCredentialTypes, function() {
+                $route.reload();
+                Notifications.success("Credentials disabled");
             }, function() {
-                Notifications.error("Failed to remove the users totp configuration");
+                Notifications.error("Failed to disable credentials");
             });
         });
     };
 
     $scope.emailActions = [];
+    $scope.disableableCredentialTypes = [];
 
     $scope.sendExecuteActionsEmail = function() {
         if ($scope.changed) {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js
index a291732..5b44382 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -531,6 +531,15 @@ module.factory('UserCredentials', function($resource) {
         }
     }).update;
 
+    credentials.disableCredentialTypes = $resource(authUrl + '/admin/realms/:realm/users/:userId/disable-credential-types', {
+        realm : '@realm',
+        userId : '@userId'
+    }, {
+        update : {
+            method : 'PUT'
+        }
+    }).update;
+
     return credentials;
 });
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html
index 0f1053c..ccfb203 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html
@@ -11,7 +11,8 @@
         <input type="password" readonly value="this is not a login form" style="display: none;">
 
         <fieldset class="border-top">
-                <div class="form-group">
+            <legend><span class="text">{{:: 'manage-user-password' | translate}}</span></legend>
+            <div class="form-group">
                     <label class="col-md-2 control-label" for="password">{{:: 'new-password' | translate}} <span class="required" data-ng-show="create">*</span></label>
                     <div class="col-md-6">
                         <input class="form-control" type="password" id="password" name="password" data-ng-model="password" required>
@@ -40,35 +41,50 @@
                 </div>
             </fieldset>
 
-            <fieldset class="border-top" data-ng-show="user.totp">
-                <div class="form-group" data-ng-show="user.totp">
-                    <label class="col-md-2 control-label">{{:: 'remove-totp' | translate}}</label>
-                    <div class="col-sm-5" data-ng-show="user.totp">
-                        <button class="btn btn-danger"  type="submit" data-ng-click="removeTotp()" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'credentials.remove-totp.tooltip' | translate}}" tooltip-placement="right">{{:: 'remove-totp' | translate}}</button>
-                    </div>
+        <fieldset class="border-top" data-ng-show="user.disableableCredentialTypes && user.disableableCredentialTypes.length > 0">
+            <legend><span class="text">{{:: 'disable-credentials' | translate}}</span></legend>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="credentialTypeList">{{:: 'disableable-credential-types' | translate}}</label>
+
+                <div class="col-md-6">
+                    <select ui-select2 id="credentialTypeList" ng-model="disableableCredentialTypes" data-placeholder="{{:: 'select-a-type.placeholder' | translate}}" multiple>
+                        <option ng-repeat="credType in user.disableableCredentialTypes" value="{{credType}}">{{credType}}</option>
+                    </select>
                 </div>
-            </fieldset >
-            <fieldset class="border-top" data-ng-show="user.email">
-                <div class="form-group clearfix">
-                    <label class="col-md-2 control-label" for="reqActions">{{:: 'reset-actions' | translate}}</label>
+                <kc-tooltip>{{:: 'credentials.disableable.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="disableCredentialTypes">{{:: 'disable-credential-types' | translate}}</label>
 
-                    <div class="col-md-6">
-                        <select ui-select2 id="reqActions" ng-model="emailActions" data-placeholder="{{:: 'select-an-action.placeholder' | translate}}" multiple>
-                            <option ng-repeat="action in userReqActionList" value="{{action.alias}}">{{action.name}}</option>
-                        </select>
-                    </div>
-                    <kc-tooltip>{{:: 'credentials.reset-actions.tooltip' | translate}}</kc-tooltip>
+                <div class="col-md-6">
+                    <button id="disableCredentialTypes" class="btn btn-default" data-ng-click="disableCredentialTypes()">{{:: 'disable' | translate}}</button>
                 </div>
-                <div class="form-group clearfix">
-                    <label class="col-md-2 control-label" for="reqActionsEmail">{{:: 'reset-actions-email' | translate}}</label>
+                <kc-tooltip>{{:: 'credentials.disable.tooltip' | translate}}</kc-tooltip>
+            </div>
+        </fieldset>
+        
+        <fieldset class="border-top" data-ng-show="user.email">
+            <legend><span class="text">{{:: 'credential-reset-actions' | translate}}</span></legend>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="reqActions">{{:: 'reset-actions' | translate}}</label>
 
-                    <div class="col-md-6">
-                        <button id="reqActionsEmail" class="btn btn-default" data-ng-click="sendExecuteActionsEmail()">{{:: 'send-email' | translate}}</button>
-                    </div>
-                    <kc-tooltip>{{:: 'credentials.reset-actions-email.tooltip' | translate}}</kc-tooltip>
+                <div class="col-md-6">
+                    <select ui-select2 id="reqActions" ng-model="emailActions" data-placeholder="{{:: 'select-an-action.placeholder' | translate}}" multiple>
+                        <option ng-repeat="action in userReqActionList" value="{{action.alias}}">{{action.name}}</option>
+                    </select>
                 </div>
-            </fieldset>
-        </form>
+                <kc-tooltip>{{:: 'credentials.reset-actions.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="reqActionsEmail">{{:: 'reset-actions-email' | translate}}</label>
+
+                <div class="col-md-6">
+                    <button id="reqActionsEmail" class="btn btn-default" data-ng-click="sendExecuteActionsEmail()">{{:: 'send-email' | translate}}</button>
+                </div>
+                <kc-tooltip>{{:: 'credentials.reset-actions-email.tooltip' | translate}}</kc-tooltip>
+            </div>
+        </fieldset>
+    </form>
 </div>
 
 <kc-menu></kc-menu>