diff --git a/forms/pom.xml b/forms/pom.xml index be51c9f121..83387660fe 100755 --- a/forms/pom.xml +++ b/forms/pom.xml @@ -13,6 +13,11 @@ + + org.keycloak + keycloak-core + ${project.version} + org.keycloak keycloak-services @@ -23,11 +28,16 @@ keycloak-social-core ${project.version} - - org.jboss.resteasy - jaxrs-api - provided - + + org.picketlink + picketlink-common + provided + + + org.jboss.resteasy + resteasy-jaxrs + provided + org.jboss.spec.javax.servlet jboss-servlet-api_3.0_spec @@ -38,6 +48,14 @@ jsf-api 2.1 + + com.google.zxing + core + + + com.google.zxing + javase + diff --git a/forms/src/main/java/org/keycloak/forms/FormsBean.java b/forms/src/main/java/org/keycloak/forms/FormsBean.java index 5588658749..6473dce055 100755 --- a/forms/src/main/java/org/keycloak/forms/FormsBean.java +++ b/forms/src/main/java/org/keycloak/forms/FormsBean.java @@ -21,12 +21,15 @@ */ package org.keycloak.forms; +import java.io.UnsupportedEncodingException; import java.net.URI; +import java.net.URLEncoder; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.UUID; import javax.annotation.PostConstruct; import javax.faces.bean.ManagedBean; @@ -37,10 +40,12 @@ import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriBuilder; +import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.models.RealmModel; import org.keycloak.services.models.RequiredCredentialModel; import org.keycloak.services.resources.flows.FormFlows; import org.keycloak.services.resources.flows.Urls; +import org.picketlink.common.util.Base32; /** * @author Stian Thorgersen @@ -81,6 +86,10 @@ public class FormsBean { private Map formData; + private String totpSecret; + + private String formsUrl; + @PostConstruct public void init() { FacesContext ctx = FacesContext.getCurrentInstance(); @@ -99,7 +108,7 @@ public class FormsBean { view = ctx.getViewRoot().getViewId(); view = view.substring(view.lastIndexOf('/') + 1, view.lastIndexOf('.')); - + UriBuilder b = UriBuilder.fromUri(request.getRequestURI()).replaceQuery(request.getQueryString()) .replacePath(request.getContextPath()).path("rest"); URI baseURI = b.build(); @@ -125,9 +134,11 @@ public class FormsBean { addSocialProviders(); addErrors(request); + formsUrl = FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() + "/forms"; + // TODO Get theme name from realm theme = "default"; - themeUrl = FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() + "/sdk/theme/" + theme; + themeUrl = formsUrl + "/theme/" + theme; themeConfig = new HashMap(); @@ -220,10 +231,34 @@ public class FormsBean { for (RequiredCredentialModel m : realm.getRequiredCredentials()) { if (m.isInput()) { requiredCredentials.add(new RequiredCredential(m.getType(), m.isSecret(), m.getFormLabel())); + + if (m.getType().equals(CredentialRepresentation.TOTP)) { + if (formData != null) { + totpSecret = formData.get("totpSecret"); + } + + if (totpSecret == null) { + totpSecret = UUID.randomUUID().toString(); + } + } } } } + public boolean isTotp() { + return totpSecret != null; + } + + public String getTotpSecret() { + return totpSecret; + } + + public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException { + String totpSecretEncoded = Base32.encode(getTotpSecret().getBytes()); + String contents = URLEncoder.encode("otpauth://totp/keycloak?secret=" + totpSecretEncoded, "utf-8"); + return formsUrl + "/qrcode" + "?size=200x200&contents=" + contents; + } + private void addSocialProviders() { // TODO Add providers configured for realm instead of all providers providers = new LinkedList(); diff --git a/forms/src/main/java/org/keycloak/forms/QRServlet.java b/forms/src/main/java/org/keycloak/forms/QRServlet.java new file mode 100644 index 0000000000..56f0d0590c --- /dev/null +++ b/forms/src/main/java/org/keycloak/forms/QRServlet.java @@ -0,0 +1,46 @@ +package org.keycloak.forms; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jboss.resteasy.logging.Logger; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; + +@WebServlet(urlPatterns = "/forms/qrcode") +public class QRServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private static final Logger log = Logger.getLogger(QRServlet.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String[] size = req.getParameter("size").split("x"); + int width = Integer.parseInt(size[0]); + int height = Integer.parseInt(size[1]); + + String contents = req.getParameter("contents"); + + try { + QRCodeWriter writer = new QRCodeWriter(); + + BitMatrix bitMatrix = writer.encode(contents, BarcodeFormat.QR_CODE, width, height); + + MatrixToImageWriter.writeToStream(bitMatrix, "png", resp.getOutputStream()); + resp.setContentType("image/png"); + } catch (Exception e) { + log.warn("Failed to generate qr code", e); + resp.sendError(500); + } + } + +} diff --git a/forms/src/main/resources/META-INF/resources/sdk/login.xhtml b/forms/src/main/resources/META-INF/resources/forms/login.xhtml similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/login.xhtml rename to forms/src/main/resources/META-INF/resources/forms/login.xhtml diff --git a/forms/src/main/resources/META-INF/resources/sdk/register.xhtml b/forms/src/main/resources/META-INF/resources/forms/register.xhtml similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/register.xhtml rename to forms/src/main/resources/META-INF/resources/forms/register.xhtml diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/base.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/base.css similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/base.css rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/base.css diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/forms.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/forms.css similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/forms.css rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/forms.css diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/btn-social-fb.svg b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/btn-social-fb.svg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/btn-social-fb.svg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/btn-social-fb.svg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/customer-login-screen-bg.jpg b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/customer-login-screen-bg.jpg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/customer-login-screen-bg.jpg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/customer-login-screen-bg.jpg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/customer-login-screen-bg.svg b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/customer-login-screen-bg.svg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/customer-login-screen-bg.svg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/customer-login-screen-bg.svg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/feedback-error-arrow-down.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-arrow-down.png similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/feedback-error-arrow-down.png rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-arrow-down.png diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/feedback-error-arrow-down.svg b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-arrow-down.svg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/feedback-error-arrow-down.svg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-arrow-down.svg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/feedback-error-sign.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-sign.png similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/feedback-error-sign.png rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-sign.png diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/feedback-error-sign.svg b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-sign.svg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/feedback-error-sign.svg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-sign.svg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/login-register-separator.svg b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.svg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/login-register-separator.svg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.svg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/login-register-separators.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separators.png similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/login-register-separators.png rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separators.png diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/login-register-social-separators.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separators.png similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/login-register-social-separators.png rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separators.png diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/login-register-social-separators.svg b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separators.svg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/login-register-social-separators.svg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separators.svg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/register-login-bg.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/register-login-bg.png similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/img/register-login-bg.png rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/register-login-bg.png diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/login-register.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/login-register.css rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/reset.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/reset.css similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/reset.css rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/reset.css diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial-regular-webfont.eot b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial-regular-webfont.eot similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial-regular-webfont.eot rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial-regular-webfont.eot diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial-regular-webfont.svg b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial-regular-webfont.svg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial-regular-webfont.svg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial-regular-webfont.svg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial-regular-webfont.ttf b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial-regular-webfont.ttf similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial-regular-webfont.ttf rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial-regular-webfont.ttf diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial-regular-webfont.woff b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial-regular-webfont.woff similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial-regular-webfont.woff rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial-regular-webfont.woff diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial.css similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial.css rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial.css diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial.less b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial.less similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/css/zocial/zocial.less rename to forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial.less diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/img/customer-login-screen-bg2.jpg b/forms/src/main/resources/META-INF/resources/forms/theme/default/img/customer-login-screen-bg2.jpg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/img/customer-login-screen-bg2.jpg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/img/customer-login-screen-bg2.jpg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/img/login-screen-background.jpg b/forms/src/main/resources/META-INF/resources/forms/theme/default/img/login-screen-background.jpg similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/img/login-screen-background.jpg rename to forms/src/main/resources/META-INF/resources/forms/theme/default/img/login-screen-background.jpg diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/img/red-hat-logo.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/img/red-hat-logo.png similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/img/red-hat-logo.png rename to forms/src/main/resources/META-INF/resources/forms/theme/default/img/red-hat-logo.png diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/login.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/login.xhtml similarity index 100% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/login.xhtml rename to forms/src/main/resources/META-INF/resources/forms/theme/default/login.xhtml diff --git a/forms/src/main/resources/META-INF/resources/sdk/theme/default/register.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml similarity index 79% rename from forms/src/main/resources/META-INF/resources/sdk/theme/default/register.xhtml rename to forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml index 12ae4884ef..b3d789366f 100755 --- a/forms/src/main/resources/META-INF/resources/sdk/theme/default/register.xhtml +++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml @@ -29,6 +29,14 @@ + + +
+ + + +
+

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 @@ 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; + } + +}