Skip to content

Commit

Permalink
KEYCLOAK-2683 Remove QRCodeResource and embed QR code in image
Browse files Browse the repository at this point in the history
  • Loading branch information
stianst committed Apr 8, 2016
1 parent 1e1bf97 commit 8ea057a
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 169 deletions.
Expand Up @@ -188,7 +188,7 @@ public Response createResponse(AccountPages page) {


switch (page) { switch (page) {
case TOTP: case TOTP:
attributes.put("totp", new TotpBean(session, realm, user, baseUri)); attributes.put("totp", new TotpBean(session, realm, user));
break; break;
case FEDERATED_IDENTITY: case FEDERATED_IDENTITY:
attributes.put("federatedIdentity", new AccountFederatedIdentityBean(session, realm, user, uriInfo.getBaseUri(), stateChecker)); attributes.put("federatedIdentity", new AccountFederatedIdentityBean(session, realm, user, uriInfo.getBaseUri(), stateChecker));
Expand Down
47 changes: 11 additions & 36 deletions services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java 100755 → 100644
Expand Up @@ -14,12 +14,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */

package org.keycloak.forms.account.freemarker.model; package org.keycloak.forms.account.freemarker.model;


import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.Base32; import org.keycloak.models.utils.Base32;
import org.keycloak.models.utils.HmacOTP;
import org.keycloak.utils.TotpUtils;


import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
Expand All @@ -34,35 +37,15 @@ public class TotpBean {


private final String totpSecret; private final String totpSecret;
private final String totpSecretEncoded; private final String totpSecretEncoded;
private final String totpSecretQrCode;
private final boolean enabled; 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.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 this.totpSecret = HmacOTP.generateSecret(20);
{ this.totpSecretEncoded = TotpUtils.encode(totpSecret);
random = new SecureRandom(); this.totpSecretQrCode = TotpUtils.qrCode(totpSecret, realm, user);
random.nextInt();
} }


public boolean isEnabled() { public boolean isEnabled() {
Expand All @@ -74,19 +57,11 @@ public String getTotpSecret() {
} }


public String getTotpSecretEncoded() { public String getTotpSecretEncoded() {
StringBuilder sb = new StringBuilder(); return totpSecretEncoded;
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();
} }


public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException { public String getTotpSecretQrCode() {
String contents = URLEncoder.encode(keyUri, "utf-8"); return totpSecretQrCode;
return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
} }


} }
Expand Down
Expand Up @@ -279,7 +279,7 @@ private Response createResponse(LoginFormsPages page) {


switch (page) { switch (page) {
case LOGIN_CONFIG_TOTP: case LOGIN_CONFIG_TOTP:
attributes.put("totp", new TotpBean(realm, user, baseUri)); attributes.put("totp", new TotpBean(realm, user));
break; break;
case LOGIN_UPDATE_PROFILE: case LOGIN_UPDATE_PROFILE:
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR); UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
Expand Down
Expand Up @@ -20,6 +20,7 @@
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.Base32; import org.keycloak.models.utils.Base32;
import org.keycloak.models.utils.HmacOTP; import org.keycloak.models.utils.HmacOTP;
import org.keycloak.utils.TotpUtils;


import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
Expand All @@ -32,17 +33,15 @@ public class TotpBean {


private final String totpSecret; private final String totpSecret;
private final String totpSecretEncoded; private final String totpSecretEncoded;
private final String totpSecretQrCode;
private final boolean enabled; 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.enabled = user.isOtpEnabled();
this.contextUrl = baseUri.getPath();

this.totpSecret = HmacOTP.generateSecret(20); this.totpSecret = HmacOTP.generateSecret(20);
this.totpSecretEncoded = Base32.encode(totpSecret.getBytes()); this.totpSecretEncoded = TotpUtils.encode(totpSecret);
this.keyUri = realm.getOTPPolicy().getKeyURI(realm, user, this.totpSecret); this.totpSecretQrCode = TotpUtils.qrCode(totpSecret, realm, user);
} }


public boolean isEnabled() { public boolean isEnabled() {
Expand All @@ -54,19 +53,11 @@ public String getTotpSecret() {
} }


public String getTotpSecretEncoded() { public String getTotpSecretEncoded() {
StringBuilder sb = new StringBuilder(); return totpSecretEncoded;
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();
} }


public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException { public String getTotpSecretQrCode() {
String contents = URLEncoder.encode(keyUri, "utf-8"); return totpSecretQrCode;
return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
} }


} }
Expand Down
Expand Up @@ -82,7 +82,6 @@ public KeycloakApplication(@Context ServletContext context, @Context Dispatcher
singletons.add(new ServerVersionResource()); singletons.add(new ServerVersionResource());
singletons.add(new RealmsResource()); singletons.add(new RealmsResource());
singletons.add(new AdminRoot()); singletons.add(new AdminRoot());
classes.add(QRCodeResource.class);
classes.add(ThemeResource.class); classes.add(ThemeResource.class);
classes.add(JsResource.class); classes.add(JsResource.class);


Expand Down

This file was deleted.

69 changes: 69 additions & 0 deletions 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);
}
}

}
2 changes: 1 addition & 1 deletion themes/src/main/resources/theme/base/account/totp.ftl
Expand Up @@ -30,7 +30,7 @@
</li> </li>
<li> <li>
<p>${msg("totpStep2")}</p> <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> <span class="code">${totp.totpSecretEncoded}</span>
</li> </li>
<li> <li>
Expand Down
Expand Up @@ -34,7 +34,7 @@
</li> </li>
<li> <li>
<p>${msg("loginTotpStep2")}</p> <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> <span class="code">${totp.totpSecretEncoded}</span>
</li> </li>
<li> <li>
Expand Down
Expand Up @@ -204,17 +204,20 @@ ol li {


ol li img { ol li img {
margin-top: 15px; margin-top: 15px;
width: 180px;
margin-bottom: 5px; margin-bottom: 5px;
border: 1px solid #eee; border: 1px solid #eee;
} }


ol li span { ol li span {
bottom: 80px; padding: 15px;
left: 200px; background-color: #f5f5f5;
border: 1px solid #eee;
top: 46px;
left: 270px;
right: 50px;
position: absolute; position: absolute;
font-family: courier, ​monospace; font-family: courier, ​monospace;
font-size: 13px; font-size: 25px;
} }


hr + .form-horizontal { hr + .form-horizontal {
Expand Down

0 comments on commit 8ea057a

Please sign in to comment.