Updated login form and added initial registration form

This commit is contained in:
Stian Thorgersen 2013-08-05 16:39:40 +01:00
parent e9f3a39162
commit dc5a83134a
11 changed files with 320 additions and 292 deletions

View file

@ -1,11 +1,14 @@
package org.keycloak.sdk;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
@ -19,129 +22,174 @@ import org.keycloak.services.models.RequiredCredentialModel;
@RequestScoped
public class LoginBean {
private String style = "saas";
private String clientId;
private String scope;
private String state;
private String redirectUri;
private RealmModel realm;
private String loginAction;
private String socialLoginUrl;
private String themeUrl;
private List<SocialProvider> providers;
private List<RequiredCredential> requiredCredentials;
private RealmModel realm;
private String username;
private String baseUrl;
private List<RequiredCredential> requiredCredentials;
private List<Property> hiddenProperties;
private List<SocialProvider> providers;
private String theme = "saas";
private String themeUrl;
private Map<String, Object> themeConfig;
@PostConstruct
public void init() {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
FacesContext ctx = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) ctx.getExternalContext().getRequest();
realm = (RealmModel) request.getAttribute(RealmModel.class.getName());
clientId = (String) request.getAttribute("client_id");
scope = (String) request.getAttribute("scope");
state = (String) request.getAttribute("state");
redirectUri = (String) request.getAttribute("redirect_uri");
loginAction = ((URI) request.getAttribute("KEYCLOAK_LOGIN_ACTION")).toString();
socialLoginUrl = ((URI) request.getAttribute("KEYCLOAK_SOCIAL_LOGIN")).toString();
username = (String) request.getAttribute("username");
providers = new LinkedList<SocialProvider>();
for (Iterator<org.keycloak.social.SocialProvider> itr = ServiceRegistry
.lookupProviders(org.keycloak.social.SocialProvider.class); itr.hasNext();) {
org.keycloak.social.SocialProvider p = itr.next();
providers.add(new SocialProvider(p.getId(), p.getName()));
if (request.getAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE") != null) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
(String) request.getAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE"), null);
ctx.addMessage(null, message);
}
requiredCredentials = new LinkedList<RequiredCredential>();
for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
if (m.isInput()) {
requiredCredentials.add(new RequiredCredential(m.getType(), m.isSecret()));
}
addRequiredCredentials();
addHiddenProperties(request, "client_id", "scope", "state", "redirect_uri");
addSocialProviders();
// TODO Get theme name from realm
theme = "saas";
themeUrl = FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() + "/sdk/theme/" + theme;
themeConfig = new HashMap<String, Object>();
themeConfig.put("styles", themeUrl + "/styles.css");
if (RealmModel.DEFAULT_REALM.equals(realm.getName())) {
themeConfig.put("logo", themeUrl + "/img/red-hat-logo.png");
themeConfig.put("background", themeUrl + "/img/login-screen-background.jpg");
} else {
themeConfig.put("background", themeUrl + "/img/customer-login-screen-bg2.jpg");
themeConfig.put("displayPoweredBy", true);
}
baseUrl = FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() + "/sdk";
themeUrl = baseUrl + "/theme/" + style;
}
public List<RequiredCredential> getRequiredCredentials() {
return requiredCredentials;
}
public String getStylesheet() {
return themeUrl + "/styles.css";
}
public String getLoginTemplate() {
return "theme/" + style + "/login.xhtml";
}
public String getLoginAction() {
return loginAction;
}
public String getStyle() {
return style;
public Map<String, Object> getThemeConfig() {
return themeConfig;
}
public String getName() {
return realm.getName();
}
public String getClientId() {
return clientId;
public String getLoginAction() {
return loginAction;
}
public String getScope() {
return scope;
public List<Property> getHiddenProperties() {
return hiddenProperties;
}
public String getState() {
return state;
public List<RequiredCredential> getRequiredCredentials() {
return requiredCredentials;
}
public String getRedirectUri() {
return redirectUri;
}
public String getUsername() {
return username;
public String getTheme() {
return theme;
}
public String getThemeUrl() {
return themeUrl;
}
public String socialLoginUrl(String id) {
StringBuilder sb = new StringBuilder();
sb.append(socialLoginUrl);
sb.append("?provider_id=" + id);
sb.append("&client_id=" + clientId);
if (scope != null) {
sb.append("&scope=" + scope);
public String getUsername() {
return username;
}
public boolean isSocial() {
// TODO Check if social is enabled in realm
return true && providers.size() > 0;
}
public boolean isRegistrationAllowed() {
return realm.isRegistrationAllowed();
}
private void addHiddenProperties(HttpServletRequest request, String... names) {
hiddenProperties = new LinkedList<Property>();
for (String name : names) {
Object v = request.getAttribute(name);
if (v != null) {
hiddenProperties.add(new Property(name, (String) v));
}
}
if (state != null) {
sb.append("&state=" + state);
}
private void addRequiredCredentials() {
requiredCredentials = new LinkedList<RequiredCredential>();
for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
if (m.isInput()) {
requiredCredentials.add(new RequiredCredential(m.getType(), m.isSecret()));
}
}
}
private void addSocialProviders() {
// TODO Add providers configured for realm instead of all providers
providers = new LinkedList<SocialProvider>();
for (Iterator<org.keycloak.social.SocialProvider> itr = ServiceRegistry
.lookupProviders(org.keycloak.social.SocialProvider.class); itr.hasNext();) {
org.keycloak.social.SocialProvider p = itr.next();
providers.add(new SocialProvider(p.getId(), p.getName()));
}
}
public class Property {
private String name;
private String value;
public Property(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
}
public class RequiredCredential {
private String type;
private boolean secret;
public RequiredCredential(String type, boolean secure) {
this.type = type;
this.secret = secure;
}
public String getName() {
return type;
}
public String getLabel() {
return type;
}
public String getInputType() {
return secret ? "password" : "text";
}
sb.append("&redirect_uri=" + redirectUri);
return sb.toString();
}
public List<SocialProvider> getProviders() {
@ -169,38 +217,11 @@ public class LoginBean {
StringBuilder sb = new StringBuilder();
sb.append(socialLoginUrl);
sb.append("?provider_id=" + id);
sb.append("&client_id=" + clientId);
if (scope != null) {
sb.append("&scope=" + scope);
for (Property p : hiddenProperties) {
sb.append("&" + p.getName() + "=" + p.getValue());
}
if (state != null) {
sb.append("&state=" + state);
}
sb.append("&redirect_uri=" + redirectUri);
return sb.toString();
}
public String getIconUrl() {
return themeUrl + "/icons/" + id + ".png";
}
}
public class RequiredCredential {
private String type;
private boolean secret;
public RequiredCredential(String type, boolean secure) {
this.type = type;
this.secret = secure;
}
public String getType() {
return type;
}
public boolean isSecret() {
return secret;
}
}
}

View file

@ -1,13 +1 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<meta charset="utf-8"></meta>
<title>Log in to #{login.name}</title>
<link href="#{login.stylesheet}" rel="stylesheet" />
</h:head>
<h:body class="rcue-login-register">
<ui:include src="#{login.loginTemplate}" />
</h:body>
</html>
<ui:include xmlns:ui="http://java.sun.com/jsf/facelets" src="theme/#{login.theme}/login.xhtml" />

View file

@ -1,58 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link href="css/bootstrap.css" rel="stylesheet">
<link href="css/default.css" rel="stylesheet">
<script src="js/angular.js"></script>
<script src="js/angular-resource.js"></script>
<script src="js/app.js"></script>
</head>
<body class=keycloak-login-page data-ng-app=keycloak>
<div id=keycloak-login-container data-ng-controller=GlobalCtrl>
<div id=keycloak-login-standard>
<h1>Register with {{config.name}}</h1>
<div class="alert alert-info" data-ng-show="info">{{info}}</div>
<div class="alert alert-error" data-ng-show="error">{{error}}</div>
<form action="#">
<label for=firstname>Firstname</label>
<input id=firstname type=text data-ng-model=firstname/>
<label for=lastname>Lastname</label>
<input id=lastname type=text data-ng-model=lastname/>
<label for=email>Email</label>
<input id=email type=email data-ng-model=email/>
<label for=username>Username</label>
<input id=username type=text data-ng-model=username/>
<label for=password>Password</label>
<input id=password type=text data-ng-model=password required/>
<label for=password-confirm>Password confirmation</label>
<input id=password-confirm type=text data-ng-model=passwordConfirm required pattern="{{password}}"
title="Passwords don't match"/>
<div>
<button class="btn btn-primary" id=keycloak-login-submit type=submit>Register</button>
<a class="btn" href="{{config.callbackUrl}}">Cancel</a>
</div>
</form>
</div>
<div id=keycloak-login-social>
<h3>Login with</h3>
<div data-ng-repeat="p in config.providers">
<a href="/social/{{config.id}}/provider/{{p}}"><img data-ng-src="icons/{{p}}.png"/></a>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,13 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<meta charset="utf-8"></meta>
<title>Register with #{register.name}</title>
<link href="#{register.themeUrl}/styles.css" rel="stylesheet" />
</h:head>
<h:body class="keycloak-register">
<ui:include src="theme/#{register.theme}/register.xhtml" />
</h:body>
</html>

View file

@ -1,67 +1,56 @@
fieldset {
border: none;
}
input[type="text"],
input[type="password"] {
font-size: 1.1em;
padding: 0 0.545454545454545em; /* 0 6px */
min-width: 18.1818181818182em; /* 200px */
height: 2.18181818181818em; /* 24px */
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0,0,0,0.1);
color: #333;
input[type="password"],
input[type="email"] {
font-size: 1.1em;
padding: 0 0.545454545454545em;
min-width: 18.1818181818182em;
height: 2.18181818181818em;
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
color: #333;
}
input[type="text"]:hover,
input[type="password"]:hover {
input[type="password"]:hover,
input[type="email"]:hover {
border-color: #62afdb;
}
input[type="text"]:focus,
input[type="password"]:focus {
input[type="password"]:focus,
input[type="email"]:focus {
border-color: #62afdb;
box-shadow: #62afdb 0 0 5px;
}
input[type="submit"] {
font-size: 1.3em;
padding: 0.30769230769231em 1.07692307692308em; /* 4px 14px */
border: 1px #21799e solid;
border-radius: 2px;
background-image: linear-gradient(top, #00A9EC 0%, #009BD3 100%);
background-image: -o-linear-gradient(top, #00A9EC 0%, #009BD3 100%);
background-image: -moz-linear-gradient(top, #00A9EC 0%, #009BD3 100%);
background-image: -webkit-linear-gradient(top, #00A9EC 0%, #009BD3 100%);
background-image: -ms-linear-gradient(top, #00A9EC 0%, #009BD3 100%);
background-image: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0.0, #00A9EC),
color-stop(1,0, #009BD3)
);
color: #fff;
font-weight: bold;
letter-spacing: 0.04em;
font-size: 1.3em;
padding: 0.30769230769231em 1.07692307692308em;
border: 1px #21799e solid;
border-radius: 2px;
background-image: linear-gradient(top, #00a9ec 0%, #009bd3 100%);
background-image: -o-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
background-image: -moz-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
background-image: -webkit-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
background-image: -ms-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #00a9ec), color-stop(1, 0, #009bd3));
color: #fff;
font-weight: bold;
letter-spacing: 0.04em;
}
input[type="submit"]:hover,
input[type="submit"]:focus {
background-color: #009BD3;
background-image: none;
cursor: pointer;
}
input[type="submit"]:active {
background-color: #0099d4;
background-image: none;
cursor: pointer;
box-shadow: inset 0 0 5px 3px #0074ae;
}
input[type="checkbox"] {
margin-right: 0.5em;
}

View file

@ -3,7 +3,6 @@ body {
}
.rcue-login-register {
background-color: #1D2226;
background-image: url("img/login-screen-background.jpg");
background-position: top left;
background-size: auto;
background-repeat: no-repeat;
@ -22,14 +21,12 @@ body {
}
.rcue-login-register .content {
position: absolute;
bottom: 15%;
bottom: 10%;
width: 100%;
min-width: 76em;
}
.rcue-login-register h2 {
padding-left: 4.34782608695652em;
/* 100px */
font-family: "Overpass", sans-serif;
font-size: 2.3em;
font-weight: 100;
@ -255,14 +252,16 @@ a.zocial:before {
line-height: 1.3em;
}
/* Customer login */
.rcue-login-register.customer {
background-image: url("img/customer-login-screen-bg2.jpg");
.rcue-login-register p.powered {
font-size: 1.1em;
margin-top: 1.27272727272727em;
text-align: right;
margin-right: 5.81818181818182em;
}
.rcue-login-register.customer h2 {
display: inline-block;
.rcue-login-register p.powered a {
color: #666;
}
.rcue-login-register.customer p.powered {
display: inline-block;
font-size: 1.3em;
margin-left: 1.2em;
.rcue-login-register p.powered a:hover {
color: #0099D3;
}

View file

@ -1,65 +1,36 @@
<ui:composition xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:c="http://java.sun.com/jstl/core">
<h1><a href="#" title="Go to the home page"><img src="#{login.themeUrl}/img/red-hat-logo.png" alt="Red Hat logo"/></a></h1>
<div class="content">
<h2>
Log in to <strong>#{login.name}</strong>
</h2>
<div class="background-area">
<div class="form-area social clearfix">
<section class="app-form">
<h3>Application login area</h3>
<form action="#{login.loginAction}" method="post">
<div>
<label for="username">Username</label>
<input id="username" name="username" value="#{login.username}" type="text" />
</div>
<ui:composition xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jstl/core" template="template.xhtml">
<ui:repeat var="c" value="#{login.requiredCredentials}">
<div>
<label for="#{c.type}">#{c.type}</label>
<input id="#{c.type}" name="#{c.type}" type="#{c.secret ? 'password' : 'text'}" />
</div>
</ui:repeat>
<input name="client_id" value="#{login.clientId}" type="hidden" />
<h:panelGroup rendered="#{not empty login.scope}">
<input name="scope" value="#{login.scope}" type="hidden" />
</h:panelGroup>
<h:panelGroup rendered="#{not empty login.state}">
<input name="state" value="#{login.state}" type="hidden" />
</h:panelGroup>
<h:panelGroup rendered="#{not empty login.redirectUri}">
<input name="redirect_uri" value="#{login.redirectUri}" type="hidden" />
</h:panelGroup>
<input type="submit" value="Log In" />
</form>
</section>
<section class="social-login">
<span>or</span>
<h3>Social login area</h3>
<p>Log In with</p>
<ul>
<ui:repeat var="p" value="#{login.providers}">
<li><a href="#{p.loginUrl}" class="zocial #{p.id}"> <span class="text">#{p.name}</span></a></li>
</ui:repeat>
</ul>
</section>
<section class="info-area">
<h3>Info area</h3>
<p>
No account? <a href="saas-register.html">Register</a>.
</p>
<ul>
<li><strong>Domain:</strong> 10.0.0.1</li>
<li><strong>Zone:</strong> Live</li>
<li><strong>Appliance:</strong> Yep</li>
</ul>
</section>
<ui:define name="header">Log in to <strong>#{login.name}</strong></ui:define>
<ui:define name="form">
<form action="#{login.loginAction}" method="post">
<div>
<label for="username">Username</label> <input id="username" name="username" value="#{login.username}" type="text" />
</div>
</div>
</div>
<ui:repeat var="c" value="#{login.requiredCredentials}">
<div>
<label for="#{c.name}">#{c.label}</label> <input id="#{c.name}" name="#{c.name}" type="#{c.inputType}" />
</div>
</ui:repeat>
<ui:repeat var="p" value="#{login.hiddenProperties}">
<input name="#{p.name}" value="#{p.value}" type="hidden" />
</ui:repeat>
<div class="aside-btn">
<input type="checkbox" id="remember" /><label for="remember">Remember Username</label>
<p>Forgot <a href="#">Username</a> or <a href="#">Password</a>?</p>
</div>
<input type="submit" value="Log In" />
</form>
</ui:define>
<ui:define name="info">
<h:panelGroup rendered="#{login.registrationAllowed}">
<p>No account? <a href="saas-register.html">Register</a>.</p>
</h:panelGroup>
</ui:define>
</ui:composition>

View file

@ -0,0 +1,40 @@
<ui:composition xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jstl/core" template="template.xhtml">
<ui:define name="header">Register with <strong>#{login.name}</strong></ui:define>
<ui:define name="form">
<form action="#{login.registerAction}" method="post">
<div>
<label for="name">Full name</label>
<input type="text" id="name" />
</div>
<div>
<label for="email">Email</label>
<input type="email" id="email" />
</div>
<div>
<label for="username">Username</label>
<input type="text" id="username" />
</div>
<div>
<label for="password">Password</label>
<input type="password" id="password" />
</div>
<ui:repeat var="p" value="#{login.hiddenProperties}">
<input name="#{p.name}" value="#{p.value}" type="hidden" />
</ui:repeat>
<div class="aside-btn">
<p>By registering you agree to the <a href="#">Terms of Service</a> and the <a href="#">Privacy Policy</a>.</p>
</div>
<input type="submit" value="Register" />
</form>
</ui:define>
<ui:define name="info">
<p>Already have an account? <a href="realm-login.html">Log in</a>.</p>
</ui:define>
</ui:composition>

View file

@ -0,0 +1,65 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Log in to #{login.name}</title>
<link href="#{login.themeConfig['styles']}" rel="stylesheet" />
<style>
body {
background-image: url("#{login.themeConfig['background']}");
}
</style>
</h:head>
<h:body class="rcue-login-register">
<h:panelGroup rendered="#{not empty login.themeConfig['logo']}">
<h1><a href="#" title="Go to the home page"><img src="#{login.themeConfig['logo']}" alt="Logo" /></a></h1>
</h:panelGroup>
<div class="content">
<h2><ui:insert name="header" /></h2>
<div class="background-area">
<div class="form-area #{login.social ? 'social' : ''} clearfix">
<section class="app-form">
<h3>Application login area</h3>
<h:messages />
<ui:insert name="form" />
</section>
<h:panelGroup rendered="#{login.social}">
<section class="social-login">
<span>or</span>
<h3>Social login area</h3>
<p>Log In with</p>
<ul>
<ui:repeat var="p" value="#{login.providers}">
<li><a href="#{p.loginUrl}" class="zocial #{p.id}"> <span class="text">#{p.name}</span></a></li>
</ui:repeat>
</ul>
</section>
</h:panelGroup>
<section class="info-area">
<h3>Info area</h3>
<ui:insert name="info" />
<ul>
<li><strong>Domain:</strong> 10.0.0.1</li>
<li><strong>Zone:</strong> Live</li>
<li><strong>Appliance:</strong> Yep</li>
</ul>
</section>
</div>
</div>
<h:panelGroup rendered="#{login.themeConfig['displayPoweredBy']}">
<p class="powered"><a href="#">Powered by Keycloak</a></p>
</h:panelGroup>
</div>
<ui:insert name="content" />
</h:body>
</html>