By registering you agree to the Terms of Service and the Privacy Policy.
@@ -40,5 +48,11 @@
#{messages.alreadyHaveAccount} #{messages.logIn}.
+
+
+ Google Authenticator setup
+
+
+
\ No newline at end of file
diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/styles.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/styles.css
similarity index 100%
rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/styles.css
rename to forms/src/main/resources/META-INF/resources/forms/theme/default/styles.css
diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/template.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/template.xhtml
similarity index 98%
rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/template.xhtml
rename to forms/src/main/resources/META-INF/resources/forms/theme/default/template.xhtml
index fdbf1a418f..de5e973d7e 100644
--- a/forms/src/main/resources/META-INF/resources/sdk/theme/default/template.xhtml
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template.xhtml
@@ -29,7 +29,7 @@
#{forms.error}
- #{forms.errorDetails}
+ #{messages[forms.errorDetails]}
diff --git a/forms/src/main/resources/org/keycloak/forms/messages.properties b/forms/src/main/resources/org/keycloak/forms/messages.properties
index 842091c14a..18c1ad5a35 100644
--- a/forms/src/main/resources/org/keycloak/forms/messages.properties
+++ b/forms/src/main/resources/org/keycloak/forms/messages.properties
@@ -26,7 +26,11 @@ missingName=Please specify full name
missingEmail=Please specify email
missingUsername=Please specify username
missingPassword=Please specify password
+missingTotp=Please specify authenticator code
invalidPasswordConfirm=Password confirmation doesn't match
+invalidTotp=Invalid authenticator code
-usernameExists=Username already exists
\ No newline at end of file
+usernameExists=Username already exists
+
+error=A system error has occured, contact admin
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index b0e3b2205a..f937a7d52f 100755
--- a/pom.xml
+++ b/pom.xml
@@ -200,6 +200,16 @@
twitter4j-core
3.0.3
+
+ com.google.zxing
+ core
+ 2.2
+
+
+ com.google.zxing
+ javase
+ 2.2
+
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 0441cc2a1a..60ea232cd9 100644
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -42,6 +42,12 @@ public class Messages {
public static final String MISSING_USERNAME = "missingUsername";
+ public static final String MISSING_TOTP = "missingTotp";
+
+ public static final String INVALID_TOTP = "invalidTotp";
+
public static final String USERNAME_EXISTS = "usernameExists";
+ public static final String ERROR = "error";
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/SaasService.java b/services/src/main/java/org/keycloak/services/resources/SaasService.java
index ea786cca90..7b5a151a66 100755
--- a/services/src/main/java/org/keycloak/services/resources/SaasService.java
+++ b/services/src/main/java/org/keycloak/services/resources/SaasService.java
@@ -11,16 +11,20 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.models.RealmModel;
+import org.keycloak.services.models.RequiredCredentialModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserCredentialModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.resources.admin.RealmsAdminResource;
import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.services.validation.Validation;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
import java.util.StringTokenizer;
/**
@@ -308,7 +312,12 @@ public class SaasService {
RealmManager realmManager = new RealmManager(session);
RealmModel defaultRealm = realmManager.defaultRealm();
- String error = validateRegistrationForm(formData);
+ List
requiredCredentialTypes = new LinkedList();
+ for (RequiredCredentialModel m : defaultRealm.getRequiredCredentials()) {
+ requiredCredentialTypes.add(m.getType());
+ }
+
+ String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
if (error != null) {
return Flows.forms(defaultRealm, request).setError(error).setFormData(formData)
.forwardToRegistration();
@@ -341,7 +350,15 @@ public class SaasService {
newUser.setFirstName(first.toString());
newUser.setLastName(last);
}
- newUser.credential(CredentialRepresentation.PASSWORD, formData.getFirst("password"));
+
+ if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
+ newUser.credential(CredentialRepresentation.PASSWORD, formData.getFirst("password"));
+ }
+
+ if (requiredCredentialTypes.contains(CredentialRepresentation.TOTP)) {
+ newUser.credential(CredentialRepresentation.TOTP, formData.getFirst("password"));
+ }
+
UserModel user = registerMe(defaultRealm, newUser);
if (user == null) {
return Flows.forms(defaultRealm, request).setError(Messages.USERNAME_EXISTS)
@@ -384,32 +401,4 @@ public class SaasService {
return user;
}
- private String validateRegistrationForm(MultivaluedMap formData) {
- if (isEmpty(formData.getFirst("name"))) {
- return Messages.MISSING_NAME;
- }
-
- if (isEmpty(formData.getFirst("email"))) {
- return Messages.MISSING_EMAIL;
- }
-
- if (isEmpty(formData.getFirst("username"))) {
- return Messages.MISSING_USERNAME;
- }
-
- if (isEmpty(formData.getFirst("password"))) {
- return Messages.MISSING_PASSWORD;
- }
-
- if (!formData.getFirst("password").equals(formData.getFirst("password-confirm"))) {
- return Messages.INVALID_PASSWORD_CONFIRM;
- }
-
- return null;
- }
-
- private boolean isEmpty(String s) {
- return s == null || s.length() == 0;
- }
-
}
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 8c283108e4..a257bf3ca4 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -19,11 +19,14 @@ import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.models.RealmModel;
+import org.keycloak.services.models.RequiredCredentialModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserCredentialModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows;
+import org.keycloak.services.validation.Validation;
+import org.picketlink.idm.credential.util.TimeBasedOTP;
import javax.ws.rs.Consumes;
import javax.ws.rs.ForbiddenException;
@@ -46,6 +49,8 @@ import javax.ws.rs.ext.Providers;
import java.security.PrivateKey;
import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
@@ -250,7 +255,12 @@ public class TokenService {
return oauth.forwardToSecurityFailure("Registration not allowed");
}
- String error = validateRegistrationForm(formData);
+ List requiredCredentialTypes = new LinkedList();
+ for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
+ requiredCredentialTypes.add(m.getType());
+ }
+
+ String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
if (error != null) {
return Flows.forms(realm, request).setError(error).setFormData(formData).forwardToRegistration();
}
@@ -291,10 +301,19 @@ public class TokenService {
user.setEmail(formData.getFirst("email"));
- UserCredentialModel credentials = new UserCredentialModel();
- credentials.setType(CredentialRepresentation.PASSWORD);
- credentials.setValue(formData.getFirst("password"));
- realm.updateCredential(user, credentials);
+ if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
+ UserCredentialModel credentials = new UserCredentialModel();
+ credentials.setType(CredentialRepresentation.PASSWORD);
+ credentials.setValue(formData.getFirst("password"));
+ realm.updateCredential(user, credentials);
+ }
+
+ if (requiredCredentialTypes.contains(CredentialRepresentation.TOTP)) {
+ UserCredentialModel credentials = new UserCredentialModel();
+ credentials.setType(CredentialRepresentation.TOTP);
+ credentials.setValue(formData.getFirst("totpSecret"));
+ realm.updateCredential(user, credentials);
+ }
for (RoleModel role : realm.getDefaultRoles()) {
realm.grantRole(user, role);
@@ -577,32 +596,4 @@ public class TokenService {
return location.build();
}
- private String validateRegistrationForm(MultivaluedMap formData) {
- if (isEmpty(formData.getFirst("name"))) {
- return Messages.MISSING_NAME;
- }
-
- if (isEmpty(formData.getFirst("email"))) {
- return Messages.MISSING_EMAIL;
- }
-
- if (isEmpty(formData.getFirst("username"))) {
- return Messages.MISSING_USERNAME;
- }
-
- if (isEmpty(formData.getFirst("password"))) {
- return Messages.MISSING_PASSWORD;
- }
-
- if (!formData.getFirst("password").equals(formData.getFirst("password-confirm"))) {
- return Messages.INVALID_PASSWORD_CONFIRM;
- }
-
- return null;
- }
-
- private boolean isEmpty(String s) {
- return s == null || s.length() == 0;
- }
-
}
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Pages.java b/services/src/main/java/org/keycloak/services/resources/flows/Pages.java
index 1f069652c5..0fffe7f5e7 100644
--- a/services/src/main/java/org/keycloak/services/resources/flows/Pages.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Pages.java
@@ -26,11 +26,11 @@ package org.keycloak.services.resources.flows;
*/
public class Pages {
- public final static String LOGIN = "/sdk/login.xhtml";
+ public final static String LOGIN = "/forms/login.xhtml";
public final static String OAUTH_GRANT = "/saas/oauthGrantForm.jsp";
- public final static String REGISTER = "/sdk/register.xhtml";
+ public final static String REGISTER = "/forms/register.xhtml";
public final static String SECURITY_FAILURE = "/saas/securityFailure.jsp";
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
new file mode 100644
index 0000000000..c54304ce75
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -0,0 +1,55 @@
+package org.keycloak.services.validation;
+
+import java.util.List;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.messages.Messages;
+import org.picketlink.idm.credential.util.TimeBasedOTP;
+
+public class Validation {
+
+ public static String validateRegistrationForm(MultivaluedMap formData, List requiredCredentialTypes) {
+ if (isEmpty(formData.getFirst("name"))) {
+ return Messages.MISSING_NAME;
+ }
+
+ if (isEmpty(formData.getFirst("email"))) {
+ return Messages.MISSING_EMAIL;
+ }
+
+ if (isEmpty(formData.getFirst("username"))) {
+ return Messages.MISSING_USERNAME;
+ }
+
+ if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
+ if (isEmpty(formData.getFirst(CredentialRepresentation.PASSWORD))) {
+ return Messages.MISSING_PASSWORD;
+ }
+
+ if (!formData.getFirst("password").equals(formData.getFirst("password-confirm"))) {
+ return Messages.INVALID_PASSWORD_CONFIRM;
+ }
+ }
+
+ if (requiredCredentialTypes.contains(CredentialRepresentation.TOTP)) {
+ if (isEmpty(formData.getFirst("totp"))) {
+ return Messages.MISSING_TOTP;
+ }
+
+ boolean validTotp = new TimeBasedOTP().validate(formData.getFirst("totp"), formData.getFirst("totpSecret")
+ .getBytes());
+ if (!validTotp) {
+ return Messages.INVALID_TOTP;
+ }
+ }
+
+ return null;
+ }
+
+ public static boolean isEmpty(String s) {
+ return s == null || s.length() == 0;
+ }
+
+}