keycloak-uncached

hotp

8/6/2015 5:54:59 PM

Changes

Details

diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml
index 301b76f..4866834 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml
@@ -17,6 +17,9 @@
             <column name="DIGITS" type="INT" defaultValueNumeric="6">
                 <constraints nullable="true"/>
             </column>
+            <column name="PERIOD" type="INT" defaultValueNumeric="30">
+                <constraints nullable="true"/>
+            </column>
             <column name="ALGORITHM" type="VARCHAR(36)" defaultValue="HmacSHA1">
                 <constraints nullable="true"/>
             </column>
@@ -28,6 +31,9 @@
             <column name="OTP_POLICY_WINDOW" type="INT" defaultValueNumeric="1">
                 <constraints nullable="true"/>
             </column>
+            <column name="OTP_POLICY_PERIOD" type="INT" defaultValueNumeric="30">
+                <constraints nullable="true"/>
+            </column>
             <column name="OTP_POLICY_DIGITS" type="INT" defaultValueNumeric="6">
                 <constraints nullable="true"/>
             </column>
diff --git a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
index cf81d81..1e57dbc 100755
--- a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
@@ -26,6 +26,7 @@ public class CredentialRepresentation {
     protected Integer counter;
     private String algorithm;
     private Integer digits;
+    private Integer period;
 
     // only used when updating a credential.  Might set required action
     protected boolean temporary;
@@ -109,4 +110,12 @@ public class CredentialRepresentation {
     public void setDigits(Integer digits) {
         this.digits = digits;
     }
+
+    public Integer getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(Integer period) {
+        this.period = period;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index ee412a5..d93a0d6 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -54,6 +54,7 @@ public class RealmRepresentation {
     protected Integer otpPolicyInitialCounter;
     protected Integer otpPolicyDigits;
     protected Integer otpPolicyLookAheadWindow;
+    protected Integer otpPolicyPeriod;
 
     protected List<UserRepresentation> users;
     protected List<ScopeMappingRepresentation> scopeMappings;
@@ -699,4 +700,12 @@ public class RealmRepresentation {
     public void setOtpPolicyLookAheadWindow(Integer otpPolicyLookAheadWindow) {
         this.otpPolicyLookAheadWindow = otpPolicyLookAheadWindow;
     }
+
+    public Integer getOtpPolicyPeriod() {
+        return otpPolicyPeriod;
+    }
+
+    public void setOtpPolicyPeriod(Integer otpPolicyPeriod) {
+        this.otpPolicyPeriod = otpPolicyPeriod;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java b/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java
index 699d04a..22818a5 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java
@@ -16,6 +16,7 @@ public class CredentialEntity {
     private int counter;
     private String algorithm;
     private int digits;
+    private int period;
 
 
     public String getId() {
@@ -105,4 +106,12 @@ public class CredentialEntity {
     public void setDigits(int digits) {
         this.digits = digits;
     }
+
+    public int getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(int period) {
+        this.period = period;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 199d612..64228e4 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -25,6 +25,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     protected int otpPolicyInitialCounter;
     protected int otpPolicyDigits;
     protected int otpPolicyLookAheadWindow;
+    protected int otpPolicyPeriod;
 
 
     private boolean editUsernameAllowed;
@@ -557,6 +558,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     public void setOtpPolicyLookAheadWindow(int otpPolicyLookAheadWindow) {
         this.otpPolicyLookAheadWindow = otpPolicyLookAheadWindow;
     }
+
+    public int getOtpPolicyPeriod() {
+        return otpPolicyPeriod;
+    }
+
+    public void setOtpPolicyPeriod(int otpPolicyPeriod) {
+        this.otpPolicyPeriod = otpPolicyPeriod;
+    }
 }
 
 
diff --git a/model/api/src/main/java/org/keycloak/models/OTPPolicy.java b/model/api/src/main/java/org/keycloak/models/OTPPolicy.java
index 4e70335..4ea52ac 100755
--- a/model/api/src/main/java/org/keycloak/models/OTPPolicy.java
+++ b/model/api/src/main/java/org/keycloak/models/OTPPolicy.java
@@ -18,6 +18,7 @@ public class OTPPolicy {
     protected int initialCounter;
     protected int digits;
     protected int lookAheadWindow;
+    protected int period;
 
     private static final Map<String, String> algToKeyUriAlg = new HashMap<>();
 
@@ -30,15 +31,16 @@ public class OTPPolicy {
     public OTPPolicy() {
     }
 
-    public OTPPolicy(String type, String algorithm, int initialCounter, int digits, int lookAheadWindow) {
+    public OTPPolicy(String type, String algorithm, int initialCounter, int digits, int lookAheadWindow, int period) {
         this.type = type;
         this.algorithm = algorithm;
         this.initialCounter = initialCounter;
         this.digits = digits;
         this.lookAheadWindow = lookAheadWindow;
+        this.period = period;
     }
 
-    public static OTPPolicy DEFAULT_POLICY = new OTPPolicy(UserCredentialModel.TOTP, HmacOTP.HMAC_SHA1, 0, 6, 1);
+    public static OTPPolicy DEFAULT_POLICY = new OTPPolicy(UserCredentialModel.TOTP, HmacOTP.HMAC_SHA1, 0, 6, 1, 30);
 
     public String getType() {
         return type;
@@ -80,12 +82,23 @@ public class OTPPolicy {
         this.lookAheadWindow = lookAheadWindow;
     }
 
+    public int getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(int period) {
+        this.period = period;
+    }
+
     public String getKeyURI(RealmModel realm, String secret) {
 
         String uri = "otpauth://" + type + "/" + realm.getName() + "?secret=" + Base32.encode(secret.getBytes()) + "&digits=" + digits + "&algorithm=" + algToKeyUriAlg.get(algorithm);
         if (type.equals(UserCredentialModel.HOTP)) {
             uri += "&counter=" + initialCounter;
         }
+        if (type.equals(UserCredentialModel.TOTP)) {
+            uri += "&period=" + period;
+        }
         return uri;
 
     }
diff --git a/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java b/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java
index 42fc4ca..202afe5 100755
--- a/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java
@@ -20,6 +20,7 @@ public class UserCredentialValueModel implements Serializable {
     private int counter;
     private String algorithm;
     private int digits;
+    private int period;
 
 
     public String getType() {
@@ -93,4 +94,12 @@ public class UserCredentialValueModel implements Serializable {
     public void setDigits(int digits) {
         this.digits = digits;
     }
+
+    public int getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(int period) {
+        this.period = period;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index c17c016..fd4569b 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -424,7 +424,9 @@ public class UserFederationManager implements UserProvider {
                         || cred.getDigits() != otpPolicy.getDigits()) {
                         return false;
                     }
-
+                    if (type.equals(UserCredentialModel.TOTP) && cred.getPeriod() != otpPolicy.getPeriod()) {
+                        return false;
+                    }
                 }
                 return true;
             }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index b2eaf72..d1eef51 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -105,7 +105,7 @@ public class CredentialValidation {
     public static boolean validOTP(RealmModel realm, String token, String secret) {
         OTPPolicy policy = realm.getOTPPolicy();
         if (policy.getType().equals(UserCredentialModel.TOTP)) {
-            TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), 30, policy.getLookAheadWindow());
+            TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
             return validator.validateTOTP(token, secret.getBytes());
         } else {
             HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
@@ -118,7 +118,7 @@ public class CredentialValidation {
     public static boolean validTOTP(RealmModel realm, UserModel user, String otp) {
         UserCredentialValueModel passwordCred = null;
         OTPPolicy policy = realm.getOTPPolicy();
-        TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), 30, policy.getLookAheadWindow());
+        TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
         for (UserCredentialValueModel cred : user.getCredentialsDirectly()) {
             if (cred.getType().equals(UserCredentialModel.TOTP)) {
                 if (validator.validateTOTP(otp, cred.getValue().getBytes())) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java b/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java
index 7770755..210f82b 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java
@@ -59,8 +59,8 @@ public class HmacOTP {
     public int validateHOTP(String token, String key, int counter) {
 
         int newCounter = counter;
-        for (newCounter = counter; newCounter < counter + lookAheadWindow; newCounter++) {
-            String candidate = generateHOTP(key, counter);
+        for (newCounter = counter; newCounter <= counter + lookAheadWindow; newCounter++) {
+            String candidate = generateHOTP(key, newCounter);
             if (candidate.equals(token)) {
                 return newCounter + 1;
             }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 5761256..39a936d 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -155,6 +155,7 @@ public class ModelToRepresentation {
         }
         OTPPolicy otpPolicy = realm.getOTPPolicy();
         rep.setOtpPolicyAlgorithm(otpPolicy.getAlgorithm());
+        rep.setOtpPolicyPeriod(otpPolicy.getPeriod());
         rep.setOtpPolicyDigits(otpPolicy.getDigits());
         rep.setOtpPolicyInitialCounter(otpPolicy.getInitialCounter());
         rep.setOtpPolicyType(otpPolicy.getType());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 33d640b..f90e7b9 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -71,6 +71,7 @@ public class RepresentationToModel {
         policy.setInitialCounter(rep.getOtpPolicyInitialCounter());
         policy.setAlgorithm(rep.getOtpPolicyAlgorithm());
         policy.setDigits(rep.getOtpPolicyDigits());
+        policy.setPeriod(rep.getOtpPolicyPeriod());
         return policy;
 
     }
@@ -945,12 +946,16 @@ public class RepresentationToModel {
             if (cred.getCounter() != null) hashedCred.setCounter(cred.getCounter());
             if (cred.getDigits() != null) hashedCred.setDigits(cred.getDigits());
             if (cred.getAlgorithm() != null) hashedCred.setAlgorithm(cred.getAlgorithm());
+            if (cred.getPeriod() != null) hashedCred.setPeriod(cred.getPeriod());
             if (cred.getDigits() == null && UserCredentialModel.isOtp(cred.getType())) {
                 hashedCred.setDigits(6);
             }
             if (cred.getAlgorithm() == null && UserCredentialModel.isOtp(cred.getType())) {
                 hashedCred.setAlgorithm(HmacOTP.HMAC_SHA1);
             }
+            if (cred.getPeriod() == null && UserCredentialModel.TOTP.equals(cred.getType())) {
+                hashedCred.setPeriod(30);
+            }
             user.updateCredentialDirectly(hashedCred);
         }
     }
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index 17692b0..d4b3916 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -298,6 +298,7 @@ public class RealmAdapter implements RealmModel {
             otpPolicy.setInitialCounter(realm.getOtpPolicyInitialCounter());
             otpPolicy.setLookAheadWindow(realm.getOtpPolicyLookAheadWindow());
             otpPolicy.setType(realm.getOtpPolicyType());
+            otpPolicy.setPeriod(realm.getOtpPolicyPeriod());
         }
         return otpPolicy;
     }
@@ -309,6 +310,7 @@ public class RealmAdapter implements RealmModel {
         realm.setOtpPolicyInitialCounter(policy.getInitialCounter());
         realm.setOtpPolicyLookAheadWindow(policy.getLookAheadWindow());
         realm.setOtpPolicyType(policy.getType());
+        realm.setOtpPolicyPeriod(policy.getPeriod());
 
     }
 
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index f145141..7461cbd 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
@@ -297,6 +297,7 @@ public class UserAdapter implements UserModel, Comparable {
             credentialEntity.setAlgorithm(otpPolicy.getAlgorithm());
             credentialEntity.setDigits(otpPolicy.getDigits());
             credentialEntity.setCounter(otpPolicy.getInitialCounter());
+            credentialEntity.setPeriod(otpPolicy.getPeriod());
             user.getCredentials().add(credentialEntity);
         } else {
             credentialEntity.setValue(cred.getValue());
@@ -304,6 +305,7 @@ public class UserAdapter implements UserModel, Comparable {
             credentialEntity.setDigits(policy.getDigits());
             credentialEntity.setCounter(policy.getInitialCounter());
             credentialEntity.setAlgorithm(policy.getAlgorithm());
+            credentialEntity.setPeriod(policy.getPeriod());
         }
     }
 
@@ -415,9 +417,28 @@ public class UserAdapter implements UserModel, Comparable {
             credModel.setValue(credEntity.getValue());
             credModel.setSalt(credEntity.getSalt());
             credModel.setHashIterations(credEntity.getHashIterations());
-            credModel.setCounter(credEntity.getCounter());
-            credModel.setAlgorithm(credEntity.getAlgorithm());
-            credModel.setDigits(credEntity.getDigits());
+            if (UserCredentialModel.isOtp(credEntity.getType())) {
+                credModel.setCounter(credEntity.getCounter());
+                if (credEntity.getAlgorithm() == null) {
+                    // for migration where these values would be null
+                    credModel.setAlgorithm(realm.getOTPPolicy().getAlgorithm());
+                } else {
+                    credModel.setAlgorithm(credEntity.getAlgorithm());
+                }
+                if (credEntity.getDigits() == 0) {
+                    // for migration where these values would be 0
+                    credModel.setDigits(realm.getOTPPolicy().getDigits());
+                } else {
+                    credModel.setDigits(credEntity.getDigits());
+                }
+
+                if (credEntity.getPeriod() == 0) {
+                    // for migration where these values would be 0
+                    credModel.setPeriod(realm.getOTPPolicy().getPeriod());
+                } else {
+                    credModel.setPeriod(credEntity.getPeriod());
+                }
+            }
 
             result.add(credModel);
         }
@@ -445,6 +466,7 @@ public class UserAdapter implements UserModel, Comparable {
         credentialEntity.setCounter(credModel.getCounter());
         credentialEntity.setAlgorithm(credModel.getAlgorithm());
         credentialEntity.setDigits(credModel.getDigits());
+        credentialEntity.setPeriod(credModel.getPeriod());
     }
 
     @Override
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 fc36f47..387d0b3 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
@@ -51,6 +51,8 @@ public class CredentialEntity {
     protected String algorithm;
     @Column(name="DIGITS")
     protected int digits;
+    @Column(name="PERIOD")
+    protected int period;
 
 
     public String getId() {
@@ -140,4 +142,12 @@ public class CredentialEntity {
     public void setDigits(int digits) {
         this.digits = digits;
     }
+
+    public int getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(int period) {
+        this.period = period;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index f187cec..67ba5a6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -69,6 +69,8 @@ public class RealmEntity {
     protected int otpPolicyDigits;
     @Column(name="OTP_POLICY_WINDOW")
     protected int otpPolicyLookAheadWindow;
+    @Column(name="OTP_POLICY_PERIOD")
+    protected int otpPolicyPeriod;
 
 
     @Column(name="EDIT_USERNAME_ALLOWED")
@@ -633,5 +635,13 @@ public class RealmEntity {
     public void setOtpPolicyLookAheadWindow(int otpPolicyLookAheadWindow) {
         this.otpPolicyLookAheadWindow = otpPolicyLookAheadWindow;
     }
+
+    public int getOtpPolicyPeriod() {
+        return otpPolicyPeriod;
+    }
+
+    public void setOtpPolicyPeriod(int otpPolicyPeriod) {
+        this.otpPolicyPeriod = otpPolicyPeriod;
+    }
 }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index f9e5d01..9706393 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1028,6 +1028,7 @@ public class RealmAdapter implements RealmModel {
             otpPolicy.setInitialCounter(realm.getOtpPolicyInitialCounter());
             otpPolicy.setLookAheadWindow(realm.getOtpPolicyLookAheadWindow());
             otpPolicy.setType(realm.getOtpPolicyType());
+            otpPolicy.setPeriod(realm.getOtpPolicyPeriod());
         }
         return otpPolicy;
     }
@@ -1039,6 +1040,7 @@ public class RealmAdapter implements RealmModel {
         realm.setOtpPolicyInitialCounter(policy.getInitialCounter());
         realm.setOtpPolicyLookAheadWindow(policy.getLookAheadWindow());
         realm.setOtpPolicyType(policy.getType());
+        realm.setOtpPolicyPeriod(policy.getPeriod());
         em.flush();
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index ae5c66c..67def6b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -321,6 +321,7 @@ public class UserAdapter implements UserModel {
             credentialEntity.setAlgorithm(otpPolicy.getAlgorithm());
             credentialEntity.setDigits(otpPolicy.getDigits());
             credentialEntity.setCounter(otpPolicy.getInitialCounter());
+            credentialEntity.setPeriod(otpPolicy.getPeriod());
             em.persist(credentialEntity);
             user.getCredentials().add(credentialEntity);
         } else {
@@ -329,6 +330,7 @@ public class UserAdapter implements UserModel {
             credentialEntity.setCounter(policy.getInitialCounter());
             credentialEntity.setAlgorithm(policy.getAlgorithm());
             credentialEntity.setValue(cred.getValue());
+            credentialEntity.setPeriod(policy.getPeriod());
         }
     }
 
@@ -450,6 +452,7 @@ public class UserAdapter implements UserModel {
                 credModel.setCounter(credEntity.getCounter());
                 credModel.setAlgorithm(credEntity.getAlgorithm());
                 credModel.setDigits(credEntity.getDigits());
+                credModel.setPeriod(credEntity.getPeriod());
 
                 result.add(credModel);
             }
@@ -479,6 +482,7 @@ public class UserAdapter implements UserModel {
         credentialEntity.setCounter(credModel.getCounter());
         credentialEntity.setAlgorithm(credModel.getAlgorithm());
         credentialEntity.setDigits(credModel.getDigits());
+        credentialEntity.setPeriod(credModel.getPeriod());
 
         em.flush();
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 19f2601..d5d31eb 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -283,6 +283,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             otpPolicy.setInitialCounter(realm.getOtpPolicyInitialCounter());
             otpPolicy.setLookAheadWindow(realm.getOtpPolicyLookAheadWindow());
             otpPolicy.setType(realm.getOtpPolicyType());
+            otpPolicy.setPeriod(realm.getOtpPolicyPeriod());
         }
         return otpPolicy;
     }
@@ -294,6 +295,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         realm.setOtpPolicyInitialCounter(policy.getInitialCounter());
         realm.setOtpPolicyLookAheadWindow(policy.getLookAheadWindow());
         realm.setOtpPolicyType(policy.getType());
+        realm.setOtpPolicyPeriod(policy.getPeriod());
         updateRealm();
     }
 
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index be5c45d..8130ff5 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -24,6 +24,7 @@ import org.keycloak.models.entities.UserConsentEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
 import org.keycloak.models.mongo.utils.MongoModelUtils;
+import org.keycloak.models.utils.HmacOTP;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
 import org.keycloak.util.Time;
@@ -269,6 +270,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
             credentialEntity.setAlgorithm(otpPolicy.getAlgorithm());
             credentialEntity.setDigits(otpPolicy.getDigits());
             credentialEntity.setCounter(otpPolicy.getInitialCounter());
+            credentialEntity.setPeriod(otpPolicy.getPeriod());
             user.getCredentials().add(credentialEntity);
         } else {
             credentialEntity.setValue(cred.getValue());
@@ -276,6 +278,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
             credentialEntity.setDigits(policy.getDigits());
             credentialEntity.setCounter(policy.getInitialCounter());
             credentialEntity.setAlgorithm(policy.getAlgorithm());
+            credentialEntity.setPeriod(policy.getPeriod());
         }
     }
 
@@ -386,9 +389,28 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
             credModel.setValue(credEntity.getValue());
             credModel.setSalt(credEntity.getSalt());
             credModel.setHashIterations(credEntity.getHashIterations());
-            credModel.setCounter(credEntity.getCounter());
-            credModel.setAlgorithm(credEntity.getAlgorithm());
-            credModel.setDigits(credEntity.getDigits());
+            if (UserCredentialModel.isOtp(credEntity.getType())) {
+                credModel.setCounter(credEntity.getCounter());
+                if (credEntity.getAlgorithm() == null) {
+                    // for migration where these values would be null
+                    credModel.setAlgorithm(realm.getOTPPolicy().getAlgorithm());
+                } else {
+                    credModel.setAlgorithm(credEntity.getAlgorithm());
+                }
+                if (credEntity.getDigits() == 0) {
+                    // for migration where these values would be 0
+                    credModel.setDigits(realm.getOTPPolicy().getDigits());
+                } else {
+                    credModel.setDigits(credEntity.getDigits());
+                }
+
+                if (credEntity.getPeriod() == 0) {
+                    // for migration where these values would be 0
+                    credModel.setPeriod(realm.getOTPPolicy().getPeriod());
+                } else {
+                    credModel.setPeriod(credEntity.getPeriod());
+                }
+            }
 
             result.add(credModel);
         }
@@ -414,6 +436,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
         credentialEntity.setCounter(credModel.getCounter());
         credentialEntity.setAlgorithm(credModel.getAlgorithm());
         credentialEntity.setDigits(credModel.getDigits());
+        credentialEntity.setPeriod(credModel.getPeriod());
 
 
         getMongoStore().updateEntity(user, invocationContext);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index bcaa98e..95d644e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -167,7 +167,7 @@ public class AccountTest {
         });
     }
 
-    @Test
+    //@Test
     public void ideTesting() throws Exception {
         Thread.sleep(100000000);
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
index cb5c721..9279e8c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
@@ -25,13 +25,21 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.constants.KerberosConstants;
 import org.keycloak.events.Details;
 import org.keycloak.events.Event;
 import org.keycloak.events.EventType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.HmacOTP;
 import org.keycloak.models.utils.TimeBasedOTP;
+import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.AssertEvents;
@@ -55,6 +63,8 @@ import org.openqa.selenium.WebDriver;
  */
 public class RequiredActionTotpSetupTest {
 
+    private static OTPPolicy originalPolicy;
+
     @ClassRule
     public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
 
@@ -66,6 +76,7 @@ public class RequiredActionTotpSetupTest {
             requiredAction.setDefaultAction(true);
             appRealm.updateRequiredActionProvider(requiredAction);
             appRealm.setResetPasswordAllowed(true);
+            originalPolicy = appRealm.getOTPPolicy();
         }
 
     });
@@ -152,6 +163,8 @@ public class RequiredActionTotpSetupTest {
         events.expectLogin().assertEvent();
     }
 
+
+
     @Test
     public void setupTotpRegisteredAfterTotpRemoval() {
         // Register new user
@@ -221,4 +234,164 @@ public class RequiredActionTotpSetupTest {
         events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setuptotp2").assertEvent();
     }
 
+    @Test
+    public void setupOtpPolicyChangedTotp8Digits() {
+        // set policy to 8 digits
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                OTPPolicy newPolicy = new OTPPolicy();
+                newPolicy.setLookAheadWindow(1);
+                newPolicy.setDigits(8);
+                newPolicy.setPeriod(30);
+                newPolicy.setType(UserCredentialModel.TOTP);
+                newPolicy.setAlgorithm(HmacOTP.HMAC_SHA1);
+                newPolicy.setInitialCounter(0);
+                appRealm.setOTPPolicy(newPolicy);
+            }
+
+        });
+
+
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        totpPage.assertCurrent();
+
+        String totpSecret = totpPage.getTotpSecret();
+
+        TimeBasedOTP timeBased = new TimeBasedOTP(HmacOTP.HMAC_SHA1, 8, 30, 1);
+        totpPage.configure(timeBased.generateTOTP(totpSecret));
+
+        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        Event loginEvent = events.expectLogin().session(sessionId).assertEvent();
+
+        oauth.openLogout();
+
+        events.expectLogout(loginEvent.getSessionId()).assertEvent();
+
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+        String src = driver.getPageSource();
+        String token = timeBased.generateTOTP(totpSecret);
+        Assert.assertEquals(8, token.length());
+        loginTotpPage.login(token);
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().assertEvent();
+
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setOTPPolicy(originalPolicy);
+            }
+
+        });
+
+    }
+
+    @Test
+    public void setupOtpPolicyChangedHotp() {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                OTPPolicy newPolicy = new OTPPolicy();
+                newPolicy.setLookAheadWindow(0);
+                newPolicy.setDigits(6);
+                newPolicy.setPeriod(30);
+                newPolicy.setType(UserCredentialModel.HOTP);
+                newPolicy.setAlgorithm(HmacOTP.HMAC_SHA1);
+                newPolicy.setInitialCounter(0);
+                appRealm.setOTPPolicy(newPolicy);
+            }
+
+        });
+
+
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        totpPage.assertCurrent();
+
+        String totpSecret = totpPage.getTotpSecret();
+
+        HmacOTP otpgen = new HmacOTP(6, HmacOTP.HMAC_SHA1, 1);
+        totpPage.configure(otpgen.generateHOTP(totpSecret, 0));
+        String uri = driver.getCurrentUrl();
+        String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        Event loginEvent = events.expectLogin().session(sessionId).assertEvent();
+
+        oauth.openLogout();
+
+        events.expectLogout(loginEvent.getSessionId()).assertEvent();
+
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+        String token = otpgen.generateHOTP(totpSecret, 1);
+        loginTotpPage.login(token);
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().assertEvent();
+
+        oauth.openLogout();
+        events.expectLogout(null).session(AssertEvents.isUUID()).assertEvent();
+
+        // test lookAheadWindow
+
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                OTPPolicy newPolicy = new OTPPolicy();
+                newPolicy.setLookAheadWindow(5);
+                newPolicy.setDigits(6);
+                newPolicy.setPeriod(30);
+                newPolicy.setType(UserCredentialModel.HOTP);
+                newPolicy.setAlgorithm(HmacOTP.HMAC_SHA1);
+                newPolicy.setInitialCounter(0);
+                appRealm.setOTPPolicy(newPolicy);
+            }
+
+        });
+
+
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+        token = otpgen.generateHOTP(totpSecret, 4);
+        loginTotpPage.assertCurrent();
+        loginTotpPage.login(token);
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().assertEvent();
+
+
+
+
+
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setOTPPolicy(originalPolicy);
+            }
+
+        });
+
+
+    }
+
+
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginHotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginHotpTest.java
new file mode 100755
index 0000000..3721c11
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginHotpTest.java
@@ -0,0 +1,178 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.forms;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.events.Details;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.HmacOTP;
+import org.keycloak.models.utils.TimeBasedOTP;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginTotpPage;
+import org.keycloak.testsuite.rule.GreenMailRule;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import java.net.MalformedURLException;
+import java.util.Collections;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginHotpTest {
+
+    public static OTPPolicy policy;
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
+
+        @Override
+        public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
+            UserModel user = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
+            policy = appRealm.getOTPPolicy();
+            policy.setType(UserCredentialModel.HOTP);
+            policy.setLookAheadWindow(2);
+            appRealm.setOTPPolicy(policy);
+
+            UserCredentialModel credentials = new UserCredentialModel();
+            credentials.setType(CredentialRepresentation.HOTP);
+            credentials.setValue("hotpSecret");
+            user.updateCredential(credentials);
+
+            user.setOtpEnabled(true);
+            appRealm.setEventsListeners(Collections.singleton("dummy"));
+        }
+
+    });
+
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @Rule
+    public GreenMailRule greenMail = new GreenMailRule();
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected AppPage appPage;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @WebResource
+    protected LoginTotpPage loginTotpPage;
+
+    private HmacOTP otp = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+
+    private int lifespan;
+
+    private static int counter = 0;
+
+    @Before
+    public void before() throws MalformedURLException {
+        otp = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+    }
+
+    @Test
+    public void loginWithHotpFailure() throws Exception {
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        loginTotpPage.assertCurrent();
+
+        loginTotpPage.login("123456");
+        loginTotpPage.assertCurrent();
+        Assert.assertEquals("Invalid authenticator code.", loginPage.getError());
+
+        //loginPage.assertCurrent();  // Invalid authenticator code.
+        //Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        events.expectLogin().error("invalid_user_credentials").session((String) null)
+                .removeDetail(Details.CONSENT)
+                .assertEvent();
+    }
+
+    @Test
+    public void loginWithMissingHotp() throws Exception {
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        loginTotpPage.assertCurrent();
+
+        loginTotpPage.login(null);
+        loginTotpPage.assertCurrent();
+        Assert.assertEquals("Invalid authenticator code.", loginPage.getError());
+
+        //loginPage.assertCurrent();  // Invalid authenticator code.
+        //Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        events.expectLogin().error("invalid_user_credentials").session((String) null)
+                .removeDetail(Details.CONSENT)
+                .assertEvent();
+    }
+
+    @Test
+    public void loginWithHotpSuccess() throws Exception {
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        loginTotpPage.assertCurrent();
+
+        loginTotpPage.login(otp.generateHOTP("hotpSecret", counter++));
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().assertEvent();
+    }
+
+    @Test
+    public void loginWithHotpInvalidPassword() throws Exception {
+        loginPage.open();
+        loginPage.login("test-user@localhost", "invalid");
+
+        loginPage.assertCurrent();
+
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        events.expectLogin().error("invalid_user_credentials").session((String) null)
+                .removeDetail(Details.CONSENT)
+                .assertEvent();
+    }
+}