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; package org.keycloak.sdk;
import java.net.URI; import java.net.URI;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean; import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped; import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext; import javax.faces.context.FacesContext;
@ -19,129 +22,174 @@ import org.keycloak.services.models.RequiredCredentialModel;
@RequestScoped @RequestScoped
public class LoginBean { public class LoginBean {
private String style = "saas"; private RealmModel realm;
private String clientId;
private String scope;
private String state;
private String redirectUri;
private String loginAction; private String loginAction;
private String socialLoginUrl; private String socialLoginUrl;
private String themeUrl;
private List<SocialProvider> providers;
private List<RequiredCredential> requiredCredentials;
private RealmModel realm;
private String username; 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 @PostConstruct
public void init() { 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()); 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(); loginAction = ((URI) request.getAttribute("KEYCLOAK_LOGIN_ACTION")).toString();
socialLoginUrl = ((URI) request.getAttribute("KEYCLOAK_SOCIAL_LOGIN")).toString(); socialLoginUrl = ((URI) request.getAttribute("KEYCLOAK_SOCIAL_LOGIN")).toString();
username = (String) request.getAttribute("username"); username = (String) request.getAttribute("username");
providers = new LinkedList<SocialProvider>(); if (request.getAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE") != null) {
for (Iterator<org.keycloak.social.SocialProvider> itr = ServiceRegistry FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
.lookupProviders(org.keycloak.social.SocialProvider.class); itr.hasNext();) { (String) request.getAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE"), null);
org.keycloak.social.SocialProvider p = itr.next(); ctx.addMessage(null, message);
providers.add(new SocialProvider(p.getId(), p.getName()));
} }
requiredCredentials = new LinkedList<RequiredCredential>(); addRequiredCredentials();
for (RequiredCredentialModel m : realm.getRequiredCredentials()) { addHiddenProperties(request, "client_id", "scope", "state", "redirect_uri");
if (m.isInput()) { addSocialProviders();
requiredCredentials.add(new RequiredCredential(m.getType(), m.isSecret()));
} // 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() { public Map<String, Object> getThemeConfig() {
return requiredCredentials; return themeConfig;
}
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 String getName() { public String getName() {
return realm.getName(); return realm.getName();
} }
public String getClientId() { public String getLoginAction() {
return clientId; return loginAction;
} }
public String getScope() { public List<Property> getHiddenProperties() {
return scope; return hiddenProperties;
} }
public String getState() { public List<RequiredCredential> getRequiredCredentials() {
return state; return requiredCredentials;
} }
public String getRedirectUri() { public String getTheme() {
return redirectUri; return theme;
}
public String getUsername() {
return username;
} }
public String getThemeUrl() { public String getThemeUrl() {
return themeUrl; return themeUrl;
} }
public String socialLoginUrl(String id) { public String getUsername() {
StringBuilder sb = new StringBuilder(); return username;
sb.append(socialLoginUrl); }
sb.append("?provider_id=" + id);
sb.append("&client_id=" + clientId); public boolean isSocial() {
if (scope != null) { // TODO Check if social is enabled in realm
sb.append("&scope=" + scope); 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() { public List<SocialProvider> getProviders() {
@ -169,38 +217,11 @@ public class LoginBean {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(socialLoginUrl); sb.append(socialLoginUrl);
sb.append("?provider_id=" + id); sb.append("?provider_id=" + id);
sb.append("&client_id=" + clientId); for (Property p : hiddenProperties) {
if (scope != null) { sb.append("&" + p.getName() + "=" + p.getValue());
sb.append("&scope=" + scope);
} }
if (state != null) {
sb.append("&state=" + state);
}
sb.append("&redirect_uri=" + redirectUri);
return sb.toString(); 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" <ui:include xmlns:ui="http://java.sun.com/jsf/facelets" src="theme/#{login.theme}/login.xhtml" />
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>

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

View file

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

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"> <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"
<h1><a href="#" title="Go to the home page"><img src="#{login.themeUrl}/img/red-hat-logo.png" alt="Red Hat logo"/></a></h1> xmlns:c="http://java.sun.com/jstl/core" template="template.xhtml">
<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:repeat var="c" value="#{login.requiredCredentials}"> <ui:define name="header">Log in to <strong>#{login.name}</strong></ui:define>
<div>
<label for="#{c.type}">#{c.type}</label> <ui:define name="form">
<input id="#{c.type}" name="#{c.type}" type="#{c.secret ? 'password' : 'text'}" /> <form action="#{login.loginAction}" method="post">
</div> <div>
</ui:repeat> <label for="username">Username</label> <input id="username" name="username" value="#{login.username}" type="text" />
<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>
</div> </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> </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>