keycloak-aplcache

creds

8/18/2016 12:52:00 PM

Changes

Details

diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 8a62c33..3d966d1 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -20,6 +20,7 @@ package org.keycloak.models.cache.infinispan;
 import org.jboss.logging.Logger;
 import org.keycloak.common.constants.ServiceAccountConstants;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
@@ -669,4 +670,25 @@ public class UserCacheSession implements CacheUserProvider {
         getDelegate().preRemove(realm, component);
 
     }
+
+    @Override
+    public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
+        return getDelegate().isValid(realm, user, inputs);
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+        getDelegate().updateCredential(realm, user, input);
+    }
+
+    @Override
+    public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
+        return getDelegate().isConfiguredFor(realm, user, type);
+    }
+
+    @Override
+    public Set<String> requiredActionsFor(RealmModel realm, UserModel user, String type) {
+        return getDelegate().requiredActionsFor(realm, user, type);
+    }
+
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java
new file mode 100755
index 0000000..f4ceb94
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java
@@ -0,0 +1,111 @@
+/*
+ * 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.models.jpa.entities;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="getCredentialAttribute", query="select attr from CredentialAttributeEntity attr where attr.credential = :credential"),
+        @NamedQuery(name="deleteCredentialAttributeByCredential", query="delete from  CredentialAttributeEntity attr where attr.credential = :credential"),
+        @NamedQuery(name="deleteCredentialAttributeByRealm", query="delete from  CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId))"),
+        @NamedQuery(name="deleteCredentialAttributeByRealmAndLink", query="delete from  CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
+        @NamedQuery(name="deleteCredentialAttributeByUser", query="delete from  CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user = :user)"),
+})
+@Table(name="CREDENTIAL_ATTRIBUTE")
+@Entity
+public class CredentialAttributeEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
+    protected String id;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name = "CREDENTIAL_ID")
+    protected CredentialEntity credential;
+
+    @Column(name = "NAME")
+    protected String name;
+    @Column(name = "VALUE")
+    protected String value;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public CredentialEntity getCredential() {
+        return credential;
+    }
+
+    public void setCredential(CredentialEntity credential) {
+        this.credential = credential;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof CredentialAttributeEntity)) return false;
+
+        CredentialAttributeEntity that = (CredentialAttributeEntity) o;
+
+        if (!id.equals(that.getId())) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
index ceb284c..2d048ee 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
@@ -19,6 +19,7 @@ package org.keycloak.models.jpa.entities;
 
 import javax.persistence.Access;
 import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -27,7 +28,10 @@ import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
 import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.Collection;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -74,6 +78,8 @@ public class CredentialEntity {
     @Column(name="PERIOD")
     protected int period;
 
+    @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
+    protected Collection<CredentialAttributeEntity> credentialAttributes = new ArrayList<>();
 
     public String getId() {
         return id;
@@ -171,6 +177,14 @@ public class CredentialEntity {
         this.period = period;
     }
 
+    public Collection<CredentialAttributeEntity> getCredentialAttributes() {
+        return credentialAttributes;
+    }
+
+    public void setCredentialAttributes(Collection<CredentialAttributeEntity> credentialAttributes) {
+        this.credentialAttributes = credentialAttributes;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 0d938d6..30752d3 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -18,6 +18,7 @@
 package org.keycloak.models.jpa;
 
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
@@ -48,6 +49,7 @@ import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -367,6 +369,8 @@ public class JpaUserProvider implements UserProvider {
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteFederatedIdentityByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteCredentialAttributeByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteCredentialsByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteUserAttributesByRealm")
@@ -391,6 +395,10 @@ public class JpaUserProvider implements UserProvider {
                 .setParameter("realmId", realm.getId())
                 .setParameter("link", link.getId())
                 .executeUpdate();
+        num = em.createNamedQuery("deleteCredentialAttributeByRealmAndLink")
+                .setParameter("realmId", realm.getId())
+                .setParameter("link", link.getId())
+                .executeUpdate();
         num = em.createNamedQuery("deleteCredentialsByRealmAndLink")
                 .setParameter("realmId", realm.getId())
                 .setParameter("link", link.getId())
@@ -716,4 +724,24 @@ public class JpaUserProvider implements UserProvider {
     public void preRemove(RealmModel realm, ComponentModel component) {
 
     }
+    @Override
+    public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
+        return false;
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+
+    }
+
+    @Override
+    public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
+        return false;
+    }
+
+    @Override
+    public Set<String> requiredActionsFor(RealmModel realm, UserModel user, String type) {
+        return Collections.EMPTY_SET;
+    }
+
 }
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java
new file mode 100755
index 0000000..c6a15b3
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java
@@ -0,0 +1,113 @@
+/*
+ * 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.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.CredentialEntity;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="deleteFederatedCredentialAttributeByCredential", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential = :credential"),
+        @NamedQuery(name="deleteFederatedCredentialAttributeByStorageProvider", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.storageProviderId=:storageProviderId)"),
+        @NamedQuery(name="deleteFederatedCredentialAttributeByRealm", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.realmId=:realmId)"),
+        @NamedQuery(name="deleteFederatedCredentialAttributeByRealmAndLink", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
+        @NamedQuery(name="deleteFederatedCredentialAttributeByUser", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId)"),
+})
+@Table(name="FED_CREDENTIAL_ATTRIBUTE")
+@Entity
+public class FederatedUserCredentialAttributeEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
+    protected String id;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name = "CREDENTIAL_ID")
+    protected FederatedUserCredentialEntity credential;
+
+    @Column(name = "NAME")
+    protected String name;
+    @Column(name = "VALUE")
+    protected String value;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public FederatedUserCredentialEntity getCredential() {
+        return credential;
+    }
+
+    public void setCredential(FederatedUserCredentialEntity credential) {
+        this.credential = credential;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserCredentialAttributeEntity)) return false;
+
+        FederatedUserCredentialAttributeEntity that = (FederatedUserCredentialAttributeEntity) o;
+
+        if (!id.equals(that.getId())) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
index 996608b..da305b4 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
@@ -17,10 +17,12 @@
 
 package org.keycloak.storage.jpa.entity;
 
+import org.keycloak.models.jpa.entities.CredentialEntity;
 import org.keycloak.models.jpa.entities.UserEntity;
 
 import javax.persistence.Access;
 import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -29,7 +31,10 @@ import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
 import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.Collection;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -87,6 +92,8 @@ public class FederatedUserCredentialEntity {
     protected int digits;
     @Column(name="PERIOD")
     protected int period;
+    @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
+    protected Collection<FederatedUserCredentialAttributeEntity> credentialAttributes = new ArrayList<>();
 
 
     public String getId() {
@@ -201,6 +208,14 @@ public class FederatedUserCredentialEntity {
         this.period = period;
     }
 
+    public Collection<FederatedUserCredentialAttributeEntity> getCredentialAttributes() {
+        return credentialAttributes;
+    }
+
+    public void setCredentialAttributes(Collection<FederatedUserCredentialAttributeEntity> credentialAttributes) {
+        this.credentialAttributes = credentialAttributes;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
index f6710dd..d9d7de9 100644
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
@@ -18,6 +18,7 @@ package org.keycloak.storage.jpa;
 
 import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.GroupModel;
@@ -607,6 +608,46 @@ public class JpaUserFederatedStorageProvider implements
     }
 
     @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+
+    }
+
+    @Override
+    public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        return null;
+    }
+
+    @Override
+    public boolean removeCredential(RealmModel realm, String id) {
+        return false;
+    }
+
+    @Override
+    public CredentialModel getCredentialById(String id) {
+        return null;
+    }
+
+    @Override
+    public List<CredentialModel> getCredentials(RealmModel realm) {
+        return null;
+    }
+
+    @Override
+    public List<CredentialModel> getUserCredentials(RealmModel realm, UserModel user) {
+        return null;
+    }
+
+    @Override
+    public List<CredentialModel> getCredentialsByType(RealmModel realm, UserModel user, String type) {
+        return null;
+    }
+
+    @Override
+    public CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+        return null;
+    }
+
+    @Override
     public void preRemove(RealmModel realm) {
         int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
@@ -620,6 +661,8 @@ public class JpaUserFederatedStorageProvider implements
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteBrokerLinkByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteUserFederatedAttributesByRealm")
@@ -642,6 +685,10 @@ public class JpaUserFederatedStorageProvider implements
                 .setParameter("realmId", realm.getId())
                 .setParameter("link", link.getId())
                 .executeUpdate();
+        num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealmAndLink")
+                .setParameter("realmId", realm.getId())
+                .setParameter("link", link.getId())
+                .executeUpdate();
         num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink")
                 .setParameter("realmId", realm.getId())
                 .setParameter("link", link.getId())
@@ -699,6 +746,10 @@ public class JpaUserFederatedStorageProvider implements
                 .setParameter("userId", user.getId())
                 .setParameter("realmId", realm.getId())
                 .executeUpdate();
+        em.createNamedQuery("deleteFederatedCredentialAttributeByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
         em.createNamedQuery("deleteFederatedUserCredentialByUser")
                 .setParameter("userId", user.getId())
                 .setParameter("realmId", realm.getId())
@@ -737,6 +788,9 @@ public class JpaUserFederatedStorageProvider implements
         em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider")
                 .setParameter("storageProviderId", model.getId())
                 .executeUpdate();
+        em.createNamedQuery("deleteFederatedCredentialAttributeByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
         em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider")
                 .setParameter("storageProviderId", model.getId())
                 .executeUpdate();
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml
index 63afbb2..a9b6078 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml
@@ -24,4 +24,39 @@
         </addColumn>
     </changeSet>
 
+    <changeSet author="bburke@redhat.com" id="2.2.0">
+        <createTable tableName="CREDENTIAL_ATTRIBUTE">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="CREDENTIAL_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="VARCHAR(4000)"/>
+        </createTable>
+
+        <createTable tableName="FED_CREDENTIAL_ATTRIBUTE">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="CREDENTIAL_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="VARCHAR(4000)"/>
+        </createTable>
+        <modifyDataType tableName="CREDENTIAL" columnName="VALUE" newDataType="VARCHAR(4000)"/>
+
+        <addForeignKeyConstraint baseColumnNames="CREDENTIAL_ID" baseTableName="FED_CREDENTIAL_ATTRIBUTE" constraintName="FK_FED_CRED_ATTR" referencedColumnNames="ID" referencedTableName="FED_USER_CREDENTIAL"/>
+        <addForeignKeyConstraint baseColumnNames="CREDENTIAL_ID" baseTableName="CREDENTIAL_ATTRIBUTE" constraintName="FK_CRED_ATTR" referencedColumnNames="ID" referencedTableName="CREDENTIAL"/>
+
+
+    </changeSet>
+
+
 </databaseChangeLog>
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index 0b2ff23..6288fe5 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -22,6 +22,7 @@
     <persistence-unit name="keycloak-default" transaction-type="RESOURCE_LOCAL">
         <class>org.keycloak.models.jpa.entities.ClientEntity</class>
         <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
+        <class>org.keycloak.models.jpa.entities.CredentialAttributeEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
         <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
@@ -74,6 +75,7 @@
         <class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity</class>
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index c0537ba..3888c7a 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -24,6 +24,7 @@ import com.mongodb.QueryBuilder;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.connections.mongo.api.MongoStore;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.credential.CredentialInput;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
@@ -635,4 +636,24 @@ public class MongoUserProvider implements UserProvider {
     public void preRemove(RealmModel realm, ComponentModel component) {
 
     }
+
+    @Override
+    public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
+        return false;
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+
+    }
+
+    @Override
+    public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
+        return false;
+    }
+
+    @Override
+    public Set<String> requiredActionsFor(RealmModel realm, UserModel user, String type) {
+        return Collections.EMPTY_SET;
+    }
 }
diff --git a/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java b/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java
new file mode 100644
index 0000000..7a0393a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java
@@ -0,0 +1,58 @@
+/*
+ * 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.component;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.storage.UserStorageProviderModel;
+
+import java.util.Comparator;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class PrioritizedComponentModel extends ComponentModel {
+    public static Comparator<ComponentModel> comparator = new Comparator<ComponentModel>() {
+        @Override
+        public int compare(ComponentModel o1, ComponentModel o2) {
+            return parsePriority(o1) - parsePriority(o2);
+        }
+    };
+
+    public PrioritizedComponentModel(ComponentModel copy) {
+        super(copy);
+    }
+
+    public PrioritizedComponentModel() {
+    }
+
+    public static int parsePriority(ComponentModel component) {
+        String priority = component.getConfig().getFirst("priority");
+        if (priority == null) return 0;
+        return Integer.valueOf(priority);
+
+    }
+
+    public int getPriority() {
+        return parsePriority(this);
+
+    }
+
+    public void setPriority(int priority) {
+        getConfig().putSingle("priority", Integer.toString(priority));
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java
new file mode 100644
index 0000000..805fb25
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java
@@ -0,0 +1,25 @@
+/*
+ * 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.credential;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialInput {
+    String getType();
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
new file mode 100644
index 0000000..e24870c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
@@ -0,0 +1,32 @@
+/*
+ * 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.credential;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialInputUpdater {
+    boolean supportsCredentialType(String credentialType);
+    Set<String> requiredActionsFor(RealmModel realm, UserModel user, String credentialType);
+    void updateCredential(RealmModel realm, UserModel user, CredentialInput input);
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java
new file mode 100644
index 0000000..a7a4c6d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java
@@ -0,0 +1,35 @@
+/*
+ * 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.credential;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialInputValidator {
+    boolean supportsCredentialType(String credentialType);
+    boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType);
+    boolean isValid(RealmModel realm, UserModel user, CredentialInput input);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
new file mode 100755
index 0000000..082c64b
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
@@ -0,0 +1,144 @@
+/*
+ * 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.credential;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Used just in cases when we want to "directly" update or retrieve the hash or salt of user credential (For example during export/import)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CredentialModel implements Serializable {
+    private String id;
+    private String type;
+    private String value;
+    private String device;
+    private byte[] salt;
+    private int hashIterations;
+    private Long createdDate;
+
+    // otp stuff
+    private int counter;
+    private String algorithm;
+    private int digits;
+    private int period;
+    private MultivaluedHashMap<String, String> config;
+
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+
+    public void setDevice(String device) {
+        this.device = device;
+    }
+
+    public byte[] getSalt() {
+        return salt;
+    }
+
+    public void setSalt(byte[] salt) {
+        this.salt = salt;
+    }
+
+    public int getHashIterations() {
+        return hashIterations;
+    }
+
+    public void setHashIterations(int iterations) {
+        this.hashIterations = iterations;
+    }
+
+    public Long getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(Long createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public int getCounter() {
+        return counter;
+    }
+
+    public void setCounter(int counter) {
+        this.counter = counter;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public int getDigits() {
+        return digits;
+    }
+
+    public void setDigits(int digits) {
+        this.digits = digits;
+    }
+
+    public int getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(int period) {
+        this.period = period;
+    }
+
+    public MultivaluedHashMap<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(MultivaluedHashMap<String, String> config) {
+        this.config = config;
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java
new file mode 100644
index 0000000..a830433
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java
@@ -0,0 +1,31 @@
+/*
+ * 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.credential;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialProvider extends Provider {
+    @Override
+    default
+    void close() {
+
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java b/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java
new file mode 100755
index 0000000..480dd1c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java
@@ -0,0 +1,85 @@
+/*
+ * 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.credential;
+
+import org.keycloak.Config;
+import org.keycloak.component.ComponentFactory;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.storage.UserStorageProvider;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialProviderFactory<T extends CredentialProvider> extends ComponentFactory<T, CredentialProvider> {
+    /**
+     * called per Keycloak transaction.
+     *
+     * @param session
+     * @param model
+     * @return
+     */
+    T create(KeycloakSession session, ComponentModel model);
+
+    /**
+     * This is the name of the provider and will be showed in the admin console as an option.
+     *
+     * @return
+     */
+    @Override
+    String getId();
+
+    @Override
+    default void init(Config.Scope config) {
+
+    }
+
+    @Override
+    default void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    default void close() {
+
+    }
+
+    @Override
+    default String getHelpText() {
+        return "";
+    }
+
+    @Override
+    default List<ProviderConfigProperty> getConfigProperties() {
+        return Collections.EMPTY_LIST;
+    }
+
+    @Override
+    default void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException {
+
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java
new file mode 100644
index 0000000..df497f1
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java
@@ -0,0 +1,38 @@
+/*
+ * 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.credential;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialStore extends Provider {
+    void updateCredential(RealmModel realm, UserModel user, CredentialModel cred);
+    CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
+    boolean removeCredential(RealmModel realm, String id);
+    CredentialModel getCredentialById(String id);
+    List<CredentialModel> getCredentials(RealmModel realm);
+    List<CredentialModel> getCredentials(RealmModel realm, UserModel user);
+    List<CredentialModel> getCredentialsByType(RealmModel realm, UserModel user, String type);
+    CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
index 3e03508..fa96707 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
@@ -17,13 +17,15 @@
 
 package org.keycloak.models;
 
+import org.keycloak.credential.CredentialInput;
+
 import java.util.UUID;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class UserCredentialModel {
+public class UserCredentialModel implements CredentialInput {
     public static final String PASSWORD = "password";
     public static final String PASSWORD_HISTORY = "password-history";
     public static final String PASSWORD_TOKEN = "password-token";
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index cb6867a..46c748e 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -19,6 +19,7 @@ package org.keycloak.models;
 
 import org.jboss.logging.Logger;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.policy.PasswordPolicyManagerProvider;
 import org.keycloak.policy.PolicyError;
@@ -445,6 +446,27 @@ public class UserFederationManager implements UserProvider {
     }
 
     @Override
+    public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
+        return session.userStorage().isValid(realm, user, inputs);
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+        session.userStorage().updateCredential(realm, user, input);
+
+    }
+
+    @Override
+    public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
+        return session.userStorage().isConfiguredFor(realm, user, type);
+    }
+
+    @Override
+    public Set<String> requiredActionsFor(RealmModel realm, UserModel user, String type) {
+        return session.userStorage().requiredActionsFor(realm, user, type);
+    }
+
+    @Override
     public void preRemove(RealmModel realm) {
         for (UserFederationProviderModel federation : realm.getUserFederationProviders()) {
             UserFederationProvider fed = getFederationProvider(federation);
diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
index d6ef2cd..5dbc04e 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -18,6 +18,7 @@
 package org.keycloak.models;
 
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
 import org.keycloak.provider.Provider;
 import org.keycloak.storage.user.UserCredentialValidatorProvider;
 import org.keycloak.storage.user.UserLookupProvider;
@@ -85,4 +86,12 @@ public interface UserProvider extends Provider,
     void close();
 
     void preRemove(RealmModel realm, ComponentModel component);
+
+    boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs);
+
+    void updateCredential(RealmModel realm, UserModel user, CredentialInput input);
+
+    boolean isConfiguredFor(RealmModel realm, UserModel user, String type);
+
+    Set<String> requiredActionsFor(RealmModel realm, UserModel user, String type);
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
index 7239182..2202f62 100644
--- a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.storage.federated;
 
+import org.keycloak.credential.CredentialModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
@@ -28,8 +29,20 @@ import java.util.List;
  * @version $Revision: 1 $
  */
 public interface UserCredentialsFederatedStorage {
+    // deprecated
     void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred);
     void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
     void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
     List<UserCredentialValueModel> getCredentials(RealmModel realm, UserModel user);
+
+    // new
+    void updateCredential(RealmModel realm, UserModel user, CredentialModel cred);
+    CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
+    boolean removeCredential(RealmModel realm, String id);
+    CredentialModel getCredentialById(String id);
+    List<CredentialModel> getCredentials(RealmModel realm);
+    List<CredentialModel> getUserCredentials(RealmModel realm, UserModel user);
+    List<CredentialModel> getCredentialsByType(RealmModel realm, UserModel user, String type);
+    CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
index 1a1fca5..6620f49 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -20,6 +20,12 @@ package org.keycloak.storage;
 import org.jboss.logging.Logger;
 import org.keycloak.common.util.reflections.Types;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.component.PrioritizedComponentModel;
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialInputUpdater;
+import org.keycloak.credential.CredentialInputValidator;
+import org.keycloak.credential.CredentialProvider;
+import org.keycloak.credential.CredentialProviderFactory;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
@@ -85,7 +91,7 @@ public class UserStorageManager implements UserProvider {
         return null;
     }
 
-    private UserStorageProvider getStorageProviderInstance(UserStorageProviderModel model, UserStorageProviderFactory factory) {
+    protected UserStorageProvider getStorageProviderInstance(UserStorageProviderModel model, UserStorageProviderFactory factory) {
         UserStorageProvider instance = (UserStorageProvider)session.getAttribute(model.getId());
         if (instance != null) return instance;
         instance = factory.create(session, model);
@@ -609,4 +615,151 @@ public class UserStorageManager implements UserProvider {
     @Override
     public void close() {
     }
+
+    @Override
+    public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
+
+        List<CredentialInput> toValidate = new LinkedList<>();
+        toValidate.addAll(inputs);
+        if (!StorageId.isLocalStorage(user)) {
+            String providerId = StorageId.resolveProviderId(user);
+            UserStorageProvider provider = getStorageProvider(realm, providerId);
+            if (provider instanceof CredentialInputValidator) {
+                Iterator<CredentialInput> it = toValidate.iterator();
+                while (it.hasNext()) {
+                    CredentialInput input = it.next();
+                    CredentialInputValidator validator = (CredentialInputValidator)provider;
+                    if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
+                        it.remove();
+                    }
+                }
+            }
+        }
+
+        if (toValidate.isEmpty()) return true;
+
+        List<ComponentModel> components = getCredentialProviderComponents(realm);
+        for (ComponentModel component : components) {
+            CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+            if (!Types.supports(CredentialInputValidator.class, factory, CredentialProviderFactory.class)) continue;
+            Iterator<CredentialInput> it = toValidate.iterator();
+            while (it.hasNext()) {
+                CredentialInput input = it.next();
+                CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
+                if (validator == null) {
+                    validator = (CredentialInputValidator)factory.create(session, component);
+                    session.setAttribute(component.getId(), validator);
+                }
+                if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
+                    it.remove();
+                }
+            }
+        }
+
+        return toValidate.isEmpty();
+    }
+
+    protected List<ComponentModel> getCredentialProviderComponents(RealmModel realm) {
+        List<ComponentModel> components = realm.getComponents(realm.getId(), CredentialProvider.class.getName());
+        Collections.sort(components, PrioritizedComponentModel.comparator);
+        return components;
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+        if (!StorageId.isLocalStorage(user)) {
+            String providerId = StorageId.resolveProviderId(user);
+            UserStorageProvider provider = getStorageProvider(realm, providerId);
+            if (provider instanceof CredentialInputUpdater) {
+                CredentialInputUpdater updater = (CredentialInputUpdater)provider;
+                if (updater.supportsCredentialType(input.getType())) {
+                    updater.updateCredential(realm, user, input);
+                    return;
+                }
+            }
+        }
+
+        List<ComponentModel> components = getCredentialProviderComponents(realm);
+        for (ComponentModel component : components) {
+            CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+            if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+            CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
+            if (updater == null) {
+                updater = (CredentialInputUpdater)factory.create(session, component);
+                session.setAttribute(component.getId(), updater);
+            }
+            if (!updater.supportsCredentialType(input.getType())) continue;
+            updater.updateCredential(realm, user, input);
+            return;
+        }
+
+    }
+    @Override
+    public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
+        if (!StorageId.isLocalStorage(user)) {
+            String providerId = StorageId.resolveProviderId(user);
+            UserStorageProvider provider = getStorageProvider(realm, providerId);
+            if (provider instanceof CredentialInputValidator) {
+                CredentialInputValidator validator = (CredentialInputValidator)provider;
+                if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
+                    return true;
+                }
+            }
+        }
+
+        List<ComponentModel> components = getCredentialProviderComponents(realm);
+        for (ComponentModel component : components) {
+            CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+            if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+            CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
+            if (validator == null) {
+                validator = (CredentialInputValidator)factory.create(session, component);
+                session.setAttribute(component.getId(), validator);
+            }
+            if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
+                return true;
+            }
+        }
+        return false;
+
+    }
+
+    @Override
+    public Set<String> requiredActionsFor(RealmModel realm, UserModel user, String type) {
+        Set<String> requiredActionsFor = Collections.EMPTY_SET;
+        if (!StorageId.isLocalStorage(user)) {
+            String providerId = StorageId.resolveProviderId(user);
+            UserStorageProvider provider = getStorageProvider(realm, providerId);
+            if (provider instanceof CredentialInputUpdater) {
+                CredentialInputUpdater updater = (CredentialInputUpdater)provider;
+                if (updater.supportsCredentialType(type)) {
+                    requiredActionsFor = updater.requiredActionsFor(realm, user, type);
+                    if (!requiredActionsFor.isEmpty()) {
+                        return requiredActionsFor;
+                    }
+                }
+            }
+        }
+
+        List<ComponentModel> components = getCredentialProviderComponents(realm);
+        for (ComponentModel component : components) {
+            CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+            if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+            CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
+            if (updater == null) {
+                updater = (CredentialInputUpdater)factory.create(session, component);
+                session.setAttribute(component.getId(), updater);
+            }
+            if (updater.supportsCredentialType(type)) {
+                requiredActionsFor = updater.requiredActionsFor(realm, user, type);
+                if (!requiredActionsFor.isEmpty()) {
+                    return requiredActionsFor;
+                }
+            }
+        }
+        return requiredActionsFor;
+
+    }
+
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
index 351107f..42ad397 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
@@ -17,14 +17,8 @@
 
 package org.keycloak.storage;
 
-import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.component.ComponentModel;
-import org.keycloak.models.RealmModel;
-
-import java.io.Serializable;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Map;
+import org.keycloak.component.PrioritizedComponentModel;
 
 /**
  * Stored configuration of a User Storage provider instance.
@@ -32,14 +26,7 @@ import java.util.Map;
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
  */
-public class UserStorageProviderModel extends ComponentModel {
-
-    public static Comparator<UserStorageProviderModel> comparator = new Comparator<UserStorageProviderModel>() {
-        @Override
-        public int compare(UserStorageProviderModel o1, UserStorageProviderModel o2) {
-            return o1.getPriority() - o2.getPriority();
-        }
-    };
+public class UserStorageProviderModel extends PrioritizedComponentModel {
 
     public UserStorageProviderModel() {
         setProviderType(UserStorageProvider.class.getName());
@@ -49,14 +36,4 @@ public class UserStorageProviderModel extends ComponentModel {
         super(copy);
     }
 
-    public int getPriority() {
-        String priority = getConfig().getFirst("priority");
-        if (priority == null) return 0;
-        return Integer.valueOf(priority);
-
-    }
-
-    public void setPriority(int priority) {
-        getConfig().putSingle("priority", Integer.toString(priority));
-    }
 }