keycloak-aplcache

Details

diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
index 893e816..58c39ec 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
@@ -188,7 +188,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
 
         switch (page) {
             case TOTP:
-                attributes.put("totp", new TotpBean(session, realm, user, baseUri));
+                attributes.put("totp", new TotpBean(session, realm, user));
                 break;
             case FEDERATED_IDENTITY:
                 attributes.put("federatedIdentity", new AccountFederatedIdentityBean(session, realm, user, uriInfo.getBaseUri(), stateChecker));
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java
old mode 100755
new mode 100644
index 1312e5d..197f985
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java
@@ -14,12 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.keycloak.forms.account.freemarker.model;
 
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.Base32;
+import org.keycloak.models.utils.HmacOTP;
+import org.keycloak.utils.TotpUtils;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
@@ -34,35 +37,15 @@ public class TotpBean {
 
     private final String totpSecret;
     private final String totpSecretEncoded;
+    private final String totpSecretQrCode;
     private final boolean enabled;
-    private final String contextUrl;
-    private final String keyUri;
 
-    public TotpBean(KeycloakSession session, RealmModel realm, UserModel user, URI baseUri) {
+    public TotpBean(KeycloakSession session, RealmModel realm, UserModel user) {
         this.enabled = session.users().configuredForCredentialType(realm.getOTPPolicy().getType(), realm, user);
-        this.contextUrl = baseUri.getPath();
-
-        this.totpSecret = randomString(20);
-        this.totpSecretEncoded = Base32.encode(totpSecret.getBytes());
-        this.keyUri = realm.getOTPPolicy().getKeyURI(realm, user, this.totpSecret);
-    }
-
-    private static String randomString(int length) {
-        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW1234567890";
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < length; i++) {
-            char c = chars.charAt(random.nextInt(chars.length()));
-            sb.append(c);
-        }
-        return sb.toString();
-    }
-
-    private static final SecureRandom random;
 
-    static
-    {
-        random = new SecureRandom();
-        random.nextInt();
+        this.totpSecret = HmacOTP.generateSecret(20);
+        this.totpSecretEncoded = TotpUtils.encode(totpSecret);
+        this.totpSecretQrCode = TotpUtils.qrCode(totpSecret, realm, user);
     }
 
     public boolean isEnabled() {
@@ -74,19 +57,11 @@ public class TotpBean {
     }
 
     public String getTotpSecretEncoded() {
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < totpSecretEncoded.length(); i += 4) {
-            sb.append(totpSecretEncoded.substring(i, i + 4 < totpSecretEncoded.length() ? i + 4 : totpSecretEncoded.length()));
-            if (i + 4 < totpSecretEncoded.length()) {
-                sb.append(" ");
-            }
-        }
-        return sb.toString();
+        return totpSecretEncoded;
     }
 
-    public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
-        String contents = URLEncoder.encode(keyUri, "utf-8");
-        return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
+    public String getTotpSecretQrCode() {
+        return totpSecretQrCode;
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
index 1a81870..44ee44d 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -279,7 +279,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
         switch (page) {
             case LOGIN_CONFIG_TOTP:
-                attributes.put("totp", new TotpBean(realm, user, baseUri));
+                attributes.put("totp", new TotpBean(realm, user));
                 break;
             case LOGIN_UPDATE_PROFILE:
                 UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java
index a403176..d1e1d9c 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java
@@ -20,6 +20,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.Base32;
 import org.keycloak.models.utils.HmacOTP;
+import org.keycloak.utils.TotpUtils;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
@@ -32,17 +33,15 @@ public class TotpBean {
 
     private final String totpSecret;
     private final String totpSecretEncoded;
+    private final String totpSecretQrCode;
     private final boolean enabled;
-    private final String contextUrl;
-    private final String keyUri;
 
-    public TotpBean(RealmModel realm, UserModel user, URI baseUri) {
+    public TotpBean(RealmModel realm, UserModel user) {
         this.enabled = user.isOtpEnabled();
-        this.contextUrl = baseUri.getPath();
-        
+
         this.totpSecret = HmacOTP.generateSecret(20);
-        this.totpSecretEncoded = Base32.encode(totpSecret.getBytes());
-        this.keyUri = realm.getOTPPolicy().getKeyURI(realm, user, this.totpSecret);
+        this.totpSecretEncoded = TotpUtils.encode(totpSecret);
+        this.totpSecretQrCode = TotpUtils.qrCode(totpSecret, realm, user);
     }
 
     public boolean isEnabled() {
@@ -54,19 +53,11 @@ public class TotpBean {
     }
 
     public String getTotpSecretEncoded() {
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < totpSecretEncoded.length(); i += 4) {
-            sb.append(totpSecretEncoded.substring(i, i + 4 < totpSecretEncoded.length() ? i + 4 : totpSecretEncoded.length()));
-            if (i + 4 < totpSecretEncoded.length()) {
-                sb.append(" ");
-            }
-        }
-        return sb.toString();
+        return totpSecretEncoded;
     }
 
-    public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
-        String contents = URLEncoder.encode(keyUri, "utf-8");
-        return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
+    public String getTotpSecretQrCode() {
+        return totpSecretQrCode;
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 4de67ed..e979465 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -82,7 +82,6 @@ public class KeycloakApplication extends Application {
         singletons.add(new ServerVersionResource());
         singletons.add(new RealmsResource());
         singletons.add(new AdminRoot());
-        classes.add(QRCodeResource.class);
         classes.add(ThemeResource.class);
         classes.add(JsResource.class);
 
diff --git a/services/src/main/java/org/keycloak/utils/TotpUtils.java b/services/src/main/java/org/keycloak/utils/TotpUtils.java
new file mode 100644
index 0000000..f9f3b60
--- /dev/null
+++ b/services/src/main/java/org/keycloak/utils/TotpUtils.java
@@ -0,0 +1,69 @@
+/*
+ * 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.utils;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import org.keycloak.common.util.Base64;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.Base32;
+
+import java.io.ByteArrayOutputStream;
+import java.net.URLEncoder;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TotpUtils {
+
+    public static String encode(String totpSecret) {
+        String encoded = Base32.encode(totpSecret.getBytes());
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < encoded.length(); i += 4) {
+            sb.append(encoded.substring(i, i + 4 < encoded.length() ? i + 4 : encoded.length()));
+            if (i + 4 < encoded.length()) {
+                sb.append(" ");
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String qrCode(String totpSecret, RealmModel realm, UserModel user) {
+        try {
+            String keyUri = realm.getOTPPolicy().getKeyURI(realm, user, totpSecret);
+
+            int width = 246;
+            int height = 246;
+
+            QRCodeWriter writer = new QRCodeWriter();
+            final BitMatrix bitMatrix = writer.encode(keyUri, BarcodeFormat.QR_CODE, width, height);
+
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            MatrixToImageWriter.writeToStream(bitMatrix, "png", bos);
+            bos.close();
+
+            return Base64.encodeBytes(bos.toByteArray());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/themes/src/main/resources/theme/base/account/totp.ftl b/themes/src/main/resources/theme/base/account/totp.ftl
index 1cfefba..8ab36fa 100755
--- a/themes/src/main/resources/theme/base/account/totp.ftl
+++ b/themes/src/main/resources/theme/base/account/totp.ftl
@@ -30,7 +30,7 @@
             </li>
             <li>
                 <p>${msg("totpStep2")}</p>
-                <img src="${totp.totpSecretQrCodeUrl}" alt="Figure: Barcode"><br/>
+                <img src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
                 <span class="code">${totp.totpSecretEncoded}</span>
             </li>
             <li>
diff --git a/themes/src/main/resources/theme/base/login/login-config-totp.ftl b/themes/src/main/resources/theme/base/login/login-config-totp.ftl
index 649a833..07a8801 100755
--- a/themes/src/main/resources/theme/base/login/login-config-totp.ftl
+++ b/themes/src/main/resources/theme/base/login/login-config-totp.ftl
@@ -34,7 +34,7 @@
             </li>
             <li>
                 <p>${msg("loginTotpStep2")}</p>
-                <img src="${totp.totpSecretQrCodeUrl}" alt="Figure: Barcode"><br/>
+                <img src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
                 <span class="code">${totp.totpSecretEncoded}</span>
             </li>
             <li>
diff --git a/themes/src/main/resources/theme/keycloak/account/resources/css/account.css b/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
index c1353d7..be0c31e 100644
--- a/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
+++ b/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
@@ -204,17 +204,20 @@ ol li {
 
 ol li img {
     margin-top: 15px;
-    width: 180px;
     margin-bottom: 5px;
     border: 1px solid #eee;
 }
 
 ol li span {
-    bottom: 80px;
-    left: 200px;
+    padding: 15px;
+    background-color: #f5f5f5;
+    border: 1px solid #eee;
+    top: 46px;
+    left: 270px;
+    right: 50px;
     position: absolute;
     font-family: courier, ​monospace;
-    font-size: 13px;
+    font-size: 25px;
 }
 
 hr + .form-horizontal {