Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2013-10-09 17:28:09 -04:00
commit e730b5a2aa
32 changed files with 689 additions and 251 deletions

View file

@ -453,7 +453,6 @@ input[type="email"].tiny {
background-image: url(img/chosen-arrow-down.png), -moz-linear-gradient(center top, #eeeeee 0%, #ffffff 50%); background-image: url(img/chosen-arrow-down.png), -moz-linear-gradient(center top, #eeeeee 0%, #ffffff 50%);
border-bottom: none; border-bottom: none;
border-radius: 2px 2px 0 0; border-radius: 2px 2px 0 0;
background-image: ;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right top; background-position: right top;
} }
@ -524,7 +523,7 @@ input[type="email"].tiny {
line-height: 1.45454545454545em; line-height: 1.45454545454545em;
} }
.tokenfield.form-control .token .close { .tokenfield.form-control .token .close {
text-indent: -9999999em; text-indent: -99999em;
width: 1.6em; width: 1.6em;
height: 1.6em; height: 1.6em;
line-height: 1.6em; line-height: 1.6em;
@ -569,7 +568,7 @@ input[type="email"].tiny {
line-height: 1.45454545454545em; line-height: 1.45454545454545em;
} }
.token .close { .token .close {
text-indent: -9999999em; text-indent: -99999em;
width: 1.6em; width: 1.6em;
height: 1.6em; height: 1.6em;
line-height: 1.6em; line-height: 1.6em;

View file

@ -544,7 +544,6 @@ input[type="email"] {
background-image: url(img/chosen-arrow-down.png), -moz-linear-gradient(center top , #eee 0%, #fff 50%); background-image: url(img/chosen-arrow-down.png), -moz-linear-gradient(center top , #eee 0%, #fff 50%);
border-bottom: none; border-bottom: none;
border-radius: 2px 2px 0 0; border-radius: 2px 2px 0 0;
background-image: ;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right top; background-position: right top;
} }
@ -628,7 +627,7 @@ input[type="email"] {
} }
.close { .close {
text-indent: -9999999em; text-indent: -99999em;
width: 1.6em; width: 1.6em;
height: 1.6em; height: 1.6em;
line-height: 1.6em; line-height: 1.6em;
@ -680,7 +679,7 @@ input[type="email"] {
} }
.close { .close {
text-indent: -9999999em; text-indent: -99999em;
width: 1.6em; width: 1.6em;
height: 1.6em; height: 1.6em;
line-height: 1.6em; line-height: 1.6em;

View file

@ -5,7 +5,7 @@
height: 16px; height: 16px;
background-image: url(img/sprites.png); /* Modified by Gabriel */ background-image: url(img/sprites.png); /* Modified by Gabriel */
background-repeat: no-repeat; background-repeat: no-repeat;
text-indent: -9999999em; text-indent: -99999em;
margin-right: 0.5em; margin-right: 0.5em;
vertical-align: text-top; vertical-align: text-top;
} }

View file

@ -60,8 +60,8 @@
<div class="form-group clearfix block"> <div class="form-group clearfix block">
<label class="control-label">User registration</label> <label class="control-label">User registration</label>
<div class="onoffswitch"> <div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.userRegistration" class="onoffswitch-checkbox" name="userRegistration" id="userRegistration"> <input type="checkbox" data-ng-model="realm.registrationAllowed" class="onoffswitch-checkbox" name="registrationAllowed" id="registrationAllowed">
<label for="userRegistration" class="onoffswitch-label"> <label for="registrationAllowed" class="onoffswitch-label">
<span class="onoffswitch-inner"> <span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span> <span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span> <span class="onoffswitch-inactive">OFF</span>

View file

@ -1,4 +1,4 @@
<%@ page import="org.keycloak.services.models.*,org.keycloak.services.resources.*,javax.ws.rs.core.*,java.util.*" language="java" contentType="text/html; charset=ISO-8859-1" <%@ page import="org.keycloak.models.*,org.keycloak.services.resources.*,javax.ws.rs.core.*,java.util.*" language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%> pageEncoding="ISO-8859-1"%>
<% <%
RealmModel realm = (RealmModel)request.getAttribute(RealmModel.class.getName()); RealmModel realm = (RealmModel)request.getAttribute(RealmModel.class.getName());

24
examples/js/index.html Normal file
View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<script src="keycloak.js"></script>
</head>
<body>
<script>
keycloak.init({
clientId : 'test-app',
clientSecret : 'password',
baseUrl : 'http://localhost:8081/auth-server',
realm : 'test'
});
if (keycloak.authenticated) {
document.write('User: ' + keycloak.user);
} else {
document.write('<a href="#" id="login" onclick="keycloak.login()">Login</a>');
}
</script>
</body>
</html>

120
examples/js/keycloak.js Normal file
View file

@ -0,0 +1,120 @@
window.keycloak = (function() {
var kc = {};
var config = null;
kc.init = function(c) {
config = c;
var token = getTokenFromCode();
if (token) {
var t = parseToken(token);
kc.user = t.prn;
kc.authenticated = true;
} else {
kc.authenticated = false;
}
}
kc.login = function() {
var clientId = encodeURIComponent(config.clientId);
var redirectUri = encodeURIComponent(window.location.href);
var state = encodeURIComponent(createUUID());
var realm = encodeURIComponent(config.realm);
var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+ '&state=' + state;
window.location.href = url;
}
return kc;
function parseToken(token) {
var t = base64Decode(token.split('.')[1]);
return JSON.parse(t);
}
function getTokenFromCode() {
var code = getQueryParam('code');
if (code) {
window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
var clientId = encodeURIComponent(config.clientId);
var clientSecret = encodeURIComponent(config.clientSecret);
var realm = encodeURIComponent(config.realm);
var params = 'code=' + code + '&client_id=' + config.clientId + '&password=' + config.clientSecret;
var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
var http = new XMLHttpRequest();
http.open('POST', url, false);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.send(params);
if (http.status == 200) {
return JSON.parse(http.responseText)['access_token'];
}
}
return undefined;
}
function getQueryParam(name) {
var params = window.location.search.substring(1).split('&');
for ( var i = 0; i < params.length; i++) {
var p = params[i].split('=');
if (decodeURIComponent(p[0]) == name) {
return p[1];
}
}
}
function createUUID() {
var s = [];
var hexDigits = '0123456789abcdef';
for ( var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = '4';
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = '-';
var uuid = s.join('');
return uuid;
}
function base64Decode (data) {
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
dec = "",
tmp_arr = [];
if (!data) {
return data;
}
data += '';
do {
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >> 16 & 0xff;
o2 = bits >> 8 & 0xff;
o3 = bits & 0xff;
if (h3 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1);
} else if (h4 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1, o2);
} else {
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
}
} while (i < data.length);
dec = tmp_arr.join('');
return dec;
}
})();

59
examples/js/testrealm.json Executable file
View file

@ -0,0 +1,59 @@
{
"id": "test",
"realm": "test",
"enabled": true,
"tokenLifespan": 300,
"accessCodeLifespan": 10,
"accessCodeLifespanUserAction": 600,
"sslNotRequired": true,
"cookieLoginAllowed": true,
"registrationAllowed": true,
"resetPasswordAllowed": true,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],
"requiredApplicationCredentials": [ "password" ],
"requiredOAuthClientCredentials": [ "password" ],
"defaultRoles": [ "user" ],
"users" : [
{
"username" : "test-user@localhost",
"enabled": true,
"email" : "test-user@localhost",
"credentials" : [
{ "type" : "password",
"value" : "password" }
]
}
],
"roles": [
{
"name": "user",
"description": "Have User privileges"
},
{
"name": "admin",
"description": "Have Administrator privileges"
}
],
"roleMappings": [
{
"username": "test-user@localhost",
"roles": ["user"]
}
],
"applications": [
{
"name": "test-app",
"enabled": true,
"adminUrl": "http://localhost:8081/app/logout",
"useRealmMappings": true,
"credentials": [
{
"type": "password",
"value": "password"
}
]
}
]
}

View file

@ -34,6 +34,16 @@ public class UrlBean {
private RealmBean realm; private RealmBean realm;
private boolean socialRegistration;
public boolean isSocialRegistration() {
return socialRegistration;
}
public void setSocialRegistration(boolean socialRegistration) {
this.socialRegistration = socialRegistration;
}
public UrlBean(RealmBean realm, URI baseURI){ public UrlBean(RealmBean realm, URI baseURI){
this.realm = realm; this.realm = realm;
this.baseURI = baseURI; this.baseURI = baseURI;
@ -82,6 +92,8 @@ public class UrlBean {
public String getRegistrationAction() { public String getRegistrationAction() {
if (realm.isSaas()) { if (realm.isSaas()) {
return Urls.saasRegisterAction(baseURI).toString(); return Urls.saasRegisterAction(baseURI).toString();
} else if (socialRegistration){
return Urls.socialRegisterAction(baseURI, realm.getId()).toString();
} else { } else {
return Urls.realmRegisterAction(baseURI, realm.getId()).toString(); return Urls.realmRegisterAction(baseURI, realm.getId()).toString();
} }

View file

@ -65,12 +65,12 @@ public class FormServiceImpl implements FormService {
commandMap.put(Pages.LOGIN_RESET_PASSWORD, new CommandPassword()); commandMap.put(Pages.LOGIN_RESET_PASSWORD, new CommandPassword());
commandMap.put(Pages.LOGIN_UPDATE_PASSWORD, new CommandPassword()); commandMap.put(Pages.LOGIN_UPDATE_PASSWORD, new CommandPassword());
commandMap.put(Pages.ACCESS, new CommandAccess()); commandMap.put(Pages.ACCESS, new CommandAccess());
commandMap.put(Pages.SECURITY_FAILURE, new CommandSecurityFailure());
commandMap.put(Pages.SOCIAL, new CommandSocial()); commandMap.put(Pages.SOCIAL, new CommandSocial());
commandMap.put(Pages.TOTP, new CommandTotp()); commandMap.put(Pages.TOTP, new CommandTotp());
commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp()); commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp());
commandMap.put(Pages.LOGIN_TOTP, new CommandLoginTotp()); commandMap.put(Pages.LOGIN_TOTP, new CommandLoginTotp());
commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandLoginTotp()); commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandLoginTotp());
commandMap.put(Pages.ERROR, new CommandError());
} }
public String getId(){ public String getId(){
@ -143,11 +143,6 @@ public class FormServiceImpl implements FormService {
} }
} }
private class CommandSecurityFailure implements Command {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
}
}
private class CommandPassword implements Command { private class CommandPassword implements Command {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) { public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
if (dataBean.getError() != null){ if (dataBean.getError() != null){
@ -174,6 +169,7 @@ public class FormServiceImpl implements FormService {
attributes.put("realm", realm); attributes.put("realm", realm);
UrlBean url = new UrlBean(realm, dataBean.getBaseURI()); UrlBean url = new UrlBean(realm, dataBean.getBaseURI());
url.setSocialRegistration(dataBean.getSocialRegistration());
attributes.put("url", url); attributes.put("url", url);
attributes.put("user", new UserBean(dataBean.getUserModel())); attributes.put("user", new UserBean(dataBean.getUserModel()));
@ -218,6 +214,7 @@ public class FormServiceImpl implements FormService {
attributes.put("realm", realm); attributes.put("realm", realm);
UrlBean url = new UrlBean(realm, dataBean.getBaseURI()); UrlBean url = new UrlBean(realm, dataBean.getBaseURI());
url.setSocialRegistration(dataBean.getSocialRegistration());
attributes.put("url", url); attributes.put("url", url);
attributes.put("user", new UserBean(dataBean.getUserModel())); attributes.put("user", new UserBean(dataBean.getUserModel()));
@ -241,6 +238,7 @@ public class FormServiceImpl implements FormService {
attributes.put("realm", realm); attributes.put("realm", realm);
UrlBean url = new UrlBean(realm, dataBean.getBaseURI()); UrlBean url = new UrlBean(realm, dataBean.getBaseURI());
url.setSocialRegistration(dataBean.getSocialRegistration());
attributes.put("url", url); attributes.put("url", url);
attributes.put("user", new UserBean(dataBean.getUserModel())); attributes.put("user", new UserBean(dataBean.getUserModel()));
@ -253,6 +251,14 @@ public class FormServiceImpl implements FormService {
} }
} }
private class CommandError implements Command {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
if (dataBean.getError() != null){
attributes.put("error", new ErrorBean(dataBean.getError()));
}
}
}
private interface Command { private interface Command {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean); public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean);
} }

View file

@ -1,6 +1,6 @@
<#-- TODO: Only a placeholder, implementation needed --> <#-- TODO: Only a placeholder, implementation needed -->
<#import "template-login-action.ftl" as layout> <#import "template-login-action.ftl" as layout>
<@layout.registrationLayout bodyClass="reset"; section> <@layout.registrationLayout bodyClass="reset" isErrorPage=true; section>
<#if section = "title"> <#if section = "title">
We're sorry... We're sorry...
@ -12,7 +12,7 @@
<#elseif section = "form"> <#elseif section = "form">
<p class="instruction">Something happened and we could not process your request.</p> <p class="instruction">Something happened and we could not process your request.</p>
<p class="instruction second">Please make sure the URL you entered is correct.</p> <p class="instruction second">${error.summary}</p>
<a href="saas-login.html" class="link-right">Go to the homepage »</a> <a href="saas-login.html" class="link-right">Go to the homepage »</a>
<#elseif section = "info" > <#elseif section = "info" >

View file

@ -1,4 +1,4 @@
<#macro registrationLayout bodyClass> <#macro registrationLayout bodyClass isErrorPage=false>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!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"> <html xmlns="http://www.w3.org/1999/xhtml">
@ -37,7 +37,7 @@
<#nested "form"> <#nested "form">
</div> </div>
<#if error?has_content> <#if !isErrorPage && error?has_content>
<div class="feedback error bottom-left show"> <div class="feedback error bottom-left show">
<p> <p>
<strong id="loginError">${rb.getString(error.summary)}</strong> <strong id="loginError">${rb.getString(error.summary)}</strong>

View file

@ -296,6 +296,7 @@ public class AccountService {
return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user) return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user)
.forwardToAction(requiredActions.iterator().next()); .forwardToAction(requiredActions.iterator().next());
} else { } else {
accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode, return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
accessCode.getState(), accessCode.getRedirectUri()); accessCode.getState(), accessCode.getRedirectUri());
} }

View file

@ -57,7 +57,6 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager; import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows; import org.keycloak.services.resources.flows.OAuthFlows;
import org.keycloak.services.resources.flows.PageFlows;
import org.keycloak.services.resources.flows.Urls; import org.keycloak.services.resources.flows.Urls;
import org.keycloak.social.AuthCallback; import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest; import org.keycloak.social.AuthRequest;
@ -221,9 +220,12 @@ public class SocialResource {
@QueryParam("provider_id") final String providerId, @QueryParam("client_id") final String clientId, @QueryParam("provider_id") final String providerId, @QueryParam("client_id") final String clientId,
@QueryParam("scope") final String scope, @QueryParam("state") final String state, @QueryParam("scope") final String scope, @QueryParam("state") final String state,
@QueryParam("redirect_uri") final String redirectUri) { @QueryParam("redirect_uri") final String redirectUri) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealm(realmId);
SocialProvider provider = getProvider(providerId); SocialProvider provider = getProvider(providerId);
if (provider == null) { if (provider == null) {
return Flows.pages(request).forwardToSecurityFailure("Social provider not found"); return Flows.forms(realm, request, uriInfo).setError("Social provider not found").forwardToErrorPage();
} }
String key = System.getProperty("keycloak.social." + providerId + ".key"); String key = System.getProperty("keycloak.social." + providerId + ".key");
@ -244,7 +246,7 @@ public class SocialResource {
return Response.status(Status.FOUND).location(authRequest.getAuthUri()).build(); return Response.status(Status.FOUND).location(authRequest.getAuthUri()).build();
} catch (Throwable t) { } catch (Throwable t) {
return Flows.pages(request).forwardToSecurityFailure("Failed to redirect to social auth"); return Flows.forms(realm, request, uriInfo).setError("Failed to redirect to social auth").forwardToErrorPage();
} }
} }
@ -253,24 +255,24 @@ public class SocialResource {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response socialRegistration(@PathParam("realm") final String realmId, public Response socialRegistration(@PathParam("realm") final String realmId,
final MultivaluedMap<String, String> formData) { final MultivaluedMap<String, String> formData) {
PageFlows pageFlows = Flows.pages(request); RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealm(realmId);
Cookie cookie = headers.getCookies().get(SocialConstants.SOCIAL_REGISTRATION_COOKIE); Cookie cookie = headers.getCookies().get(SocialConstants.SOCIAL_REGISTRATION_COOKIE);
if (cookie == null) { if (cookie == null) {
return pageFlows.forwardToSecurityFailure("Social registration cookie not found"); return Flows.forms(realm, request, uriInfo).setError("Social registration cookie not found").forwardToErrorPage();
} }
String requestId = cookie.getValue(); String requestId = cookie.getValue();
if (!socialRequestManager.isRequestId(requestId)) { if (!socialRequestManager.isRequestId(requestId)) {
logger.error("Unknown requestId found in cookie. Maybe it's expired. requestId=" + requestId); logger.error("Unknown requestId found in cookie. Maybe it's expired. requestId=" + requestId);
return pageFlows.forwardToSecurityFailure("Unknown requestId found in cookie. Maybe it's expired."); return Flows.forms(realm, request, uriInfo).setError("Unknown requestId found in cookie. Maybe it's expired.").forwardToErrorPage();
} }
RequestDetails requestData = socialRequestManager.getData(requestId); RequestDetails requestData = socialRequestManager.getData(requestId);
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealm(realmId);
if (realm == null || !realm.isEnabled()) { if (realm == null || !realm.isEnabled()) {
return pageFlows.forwardToSecurityFailure("Realm doesn't exists or is not enabled."); return Flows.forms(realm, request, uriInfo).setError("Realm doesn't exists or is not enabled.").forwardToErrorPage();
} }
TokenService tokenService = new TokenService(realm, tokenManager); TokenService tokenService = new TokenService(realm, tokenManager);
resourceContext.initResource(tokenService); resourceContext.initResource(tokenService);

View file

@ -544,6 +544,7 @@ public class TokenService {
return redirectAccessDenied(redirect, state); return redirectAccessDenied(redirect, state);
} }
accessCodeEntry.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
return oauth.redirectAccessCode(accessCodeEntry, state, redirect); return oauth.redirectAccessCode(accessCodeEntry, state, redirect);
} }

View file

@ -36,10 +36,6 @@ public class Flows {
private Flows() { private Flows() {
} }
public static PageFlows pages(HttpRequest request) {
return new PageFlows(request);
}
public static FormFlows forms(RealmModel realm, HttpRequest request, UriInfo uriInfo) { public static FormFlows forms(RealmModel realm, HttpRequest request, UriInfo uriInfo) {
return new FormFlows(realm, request, uriInfo); return new FormFlows(realm, request, uriInfo);
} }

View file

@ -168,6 +168,10 @@ public class FormFlows {
return forwardToForm(Pages.TOTP); return forwardToForm(Pages.TOTP);
} }
public Response forwardToErrorPage() {
return forwardToForm(Pages.ERROR);
}
public FormFlows setAccessCode(AccessCodeEntry accessCode) { public FormFlows setAccessCode(AccessCodeEntry accessCode) {
this.accessCode = accessCode; this.accessCode = accessCode;
return this; return this;

View file

@ -126,7 +126,7 @@ public class OAuthFlows {
} }
public Response forwardToSecurityFailure(String message) { public Response forwardToSecurityFailure(String message) {
return Flows.pages(request).forwardToSecurityFailure(message); return Flows.forms(realm, request, uriInfo).setError(message).forwardToErrorPage();
} }
} }

View file

@ -1,52 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.services.resources.flows;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.services.JspRequestParameters;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class PageFlows {
private static final Logger log = Logger.getLogger(PageFlows.class);
private HttpRequest request;
PageFlows(HttpRequest request) {
this.request = request;
}
public Response forwardToSecurityFailure(String message) {
log.error(message);
request.setAttribute(JspRequestParameters.KEYCLOAK_SECURITY_FAILURE_MESSAGE, message);
request.forward(Pages.SECURITY_FAILURE);
return null;
}
}

View file

@ -48,7 +48,7 @@ public class Pages {
public final static String REGISTER = "/forms/register.ftl"; public final static String REGISTER = "/forms/register.ftl";
public final static String SECURITY_FAILURE = "/saas/securityFailure.jsp"; public final static String ERROR = "/forms/error.ftl";
public final static String SOCIAL = "/forms/social.ftl"; public final static String SOCIAL = "/forms/social.ftl";

View file

@ -1,14 +0,0 @@
import org.picketlink.common.util.Base32;
import org.picketlink.idm.credential.util.TimeBasedOTP;
public class TotpUtil {
public static void main(String[] args) {
String google = "PJBX GURY NZIT C2JX I44T S3D2 JBKD G6SB";
google = google.replace(" ", "");
google = new String(Base32.decode(google));
TimeBasedOTP otp = new TimeBasedOTP();
System.out.println(otp.generate(google));
}
}

View file

@ -6,4 +6,49 @@ Browser
The testsuite uses Sellenium. By default it uses the HtmlUnit WebDriver, but can also be executed with Chrome or Firefox. The testsuite uses Sellenium. By default it uses the HtmlUnit WebDriver, but can also be executed with Chrome or Firefox.
To run the tests with Firefox add `-Dbrowser=firefox` or for Chrome add `-Dbrowser=chrome` To run the tests with Firefox add `-Dbrowser=firefox` or for Chrome add `-Dbrowser=chrome`
Test utils
==========
Keycloak server
---------------
To start a basic Keycloak server for testing run:
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.KeycloakServer
or just run KeycloakServer from your favourite IDE!
When starting the server it can also import a realm from a json file:
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.KeycloakServer -Dexec.args="-import testrealm.json"
You can also change the host and port the server is bound to:
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.KeycloakServer -Dexec.args="-b host -p 8080"
TOTP codes
----------
To generate totp codes without Google authenticator run:
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.TotpGenerator -Dexec.args="PJBX GURY NZIT C2JX I44T S3D2 JBKD G6SB"
or just run TotpGenerator from your favourite IDE!
Replace value of -Dexec.args with the secret from the totp configuration page
Mail server
-----------
To start a test mail server for testing email sending run:
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.MailServer
or just run MailServer from your favourite IDE!
To configure Keycloak to use the above server add:
-Dkeycloak.mail.smtp.from=auto@keycloak.org -Dkeycloak.mail.smtp.host=localhost -Dkeycloak.mail.smtp.port=3025

View file

@ -26,7 +26,6 @@
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId> <artifactId>bcprov-jdk16</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
@ -38,6 +37,11 @@
<artifactId>keycloak-services</artifactId> <artifactId>keycloak-services</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-social-core</artifactId> <artifactId>keycloak-social-core</artifactId>
@ -67,37 +71,30 @@
<dependency> <dependency>
<groupId>org.jboss.logging</groupId> <groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.picketlink</groupId> <groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-api</artifactId> <artifactId>picketlink-idm-api</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.picketlink</groupId> <groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId> <artifactId>picketlink-common</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.picketlink</groupId> <groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-impl</artifactId> <artifactId>picketlink-idm-impl</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.picketlink</groupId> <groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-simple-schema</artifactId> <artifactId>picketlink-idm-simple-schema</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.picketlink</groupId> <groupId>org.picketlink</groupId>
<artifactId>picketlink-config</artifactId> <artifactId>picketlink-config</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId> <artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>log4j</groupId> <groupId>log4j</groupId>
@ -116,89 +113,72 @@
<dependency> <dependency>
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId> <artifactId>jaxrs-api</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId> <artifactId>resteasy-client</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-crypto</artifactId> <artifactId>resteasy-crypto</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>jose-jwt</artifactId> <artifactId>jose-jwt</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-undertow</artifactId> <artifactId>resteasy-undertow</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.undertow</groupId> <groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId> <artifactId>undertow-servlet</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.undertow</groupId> <groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId> <artifactId>undertow-core</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId> <artifactId>jackson-core-asl</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId> <artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.spec.javax.servlet</groupId> <groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId> <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId> <artifactId>jackson-xc</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate.javax.persistence</groupId> <groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId> <artifactId>hibernate-jpa-2.0-api</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<version>1.3.161</version> <version>1.3.161</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate</groupId> <groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId> <artifactId>hibernate-entitymanager</artifactId>
<version>3.6.6.Final</version> <version>3.6.6.Final</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.icegreen</groupId> <groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId> <artifactId>greenmail</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.seleniumhq.selenium</groupId> <groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId> <artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>

View file

@ -0,0 +1,264 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.testutils;
import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.server.handlers.resource.ClassPathResourceManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.DispatcherType;
import org.jboss.resteasy.jwt.JsonSerialization;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.FormService;
import org.keycloak.services.filters.KeycloakSessionServletFilter;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.resources.SaasService;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class KeycloakServer {
private static final Logger log = Logger.getLogger(KeycloakServer.class);
private boolean sysout = false;
public static class KeycloakServerConfig {
private String host = "localhost";
private int port = 8081;
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
}
private static <T> T loadJson(InputStream is, Class<T> type) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = is.read()) != -1) {
os.write(c);
}
byte[] bytes = os.toByteArray();
return JsonSerialization.fromBytes(type, bytes);
} catch (IOException e) {
throw new RuntimeException("Failed to parse json", e);
}
}
public static void main(String[] args) throws Throwable {
KeycloakServerConfig config = new KeycloakServerConfig();
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-b")) {
config.setHost(args[++i]);
}
if (args[i].equals("-p")) {
config.setPort(Integer.valueOf(args[++i]));
}
}
final KeycloakServer keycloak = new KeycloakServer(config);
keycloak.sysout = true;
keycloak.start();
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-import")) {
keycloak.importRealm(new FileInputStream(args[++i]));
}
}
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
keycloak.stop();
}
});
}
private KeycloakServerConfig config;
private KeycloakSessionFactory factory;
private UndertowJaxrsServer server;
public KeycloakServer() {
this(new KeycloakServerConfig());
}
public KeycloakServer(KeycloakServerConfig config) {
this.config = config;
}
public KeycloakSessionFactory getKeycloakSessionFactory() {
return factory;
}
public UndertowJaxrsServer getServer() {
return server;
}
public void importRealm(InputStream realm) {
RealmRepresentation rep = loadJson(realm, RealmRepresentation.class);
importRealm(rep);
}
public void importRealm(RealmRepresentation rep) {
KeycloakSession session = factory.createSession();
session.getTransaction().begin();
try {
RealmManager manager = new RealmManager(session);
if (rep.getId() == null) {
throw new RuntimeException("Realm id not specified");
}
if (manager.getRealm(rep.getId()) != null) {
info("Not importing realm " + rep.getRealm() + " realm already exists");
return;
}
RealmModel realm = manager.createRealm(rep.getId(), rep.getRealm());
manager.importRealm(rep, realm);
info("Imported realm " + realm.getName());
session.getTransaction().commit();
} finally {
session.close();
}
}
protected void setupDefaultRealm() {
KeycloakSession session = factory.createSession();
session.getTransaction().begin();
try {
RealmManager manager = new RealmManager(session);
if (manager.getRealm(RealmModel.DEFAULT_REALM) != null) {
return;
}
RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
manager.generateRealmKeys(defaultRealm);
defaultRealm.setEnabled(true);
defaultRealm.setTokenLifespan(300);
defaultRealm.setAccessCodeLifespan(60);
defaultRealm.setAccessCodeLifespanUserAction(600);
defaultRealm.setSslNotRequired(false);
defaultRealm.setCookieLoginAllowed(true);
defaultRealm.setRegistrationAllowed(true);
defaultRealm.setAutomaticRegistrationAfterSocialLogin(false);
defaultRealm.setVerifyEmail(false);
defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
UserModel admin = defaultRealm.addUser("admin");
defaultRealm.grantRole(admin, role);
session.getTransaction().commit();
} finally {
session.close();
}
}
public void start() throws Throwable {
long start = System.currentTimeMillis();
ResteasyDeployment deployment = new ResteasyDeployment();
deployment.setApplicationClass(KeycloakApplication.class.getName());
Builder builder = Undertow.builder().addListener(config.getPort(), config.getHost());
server = new UndertowJaxrsServer().start(builder);
DeploymentInfo di = server.undertowDeployment(deployment, "rest");
di.setClassLoader(getClass().getClassLoader());
di.setContextPath("/auth-server");
di.setDeploymentName("Keycloak");
di.setResourceManager(new ClassPathResourceManager(FormService.class.getClassLoader(), "META-INF/resources"));
FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class);
di.addFilter(filter);
di.addFilterUrlMapping("SessionFilter", "/rest/*", DispatcherType.REQUEST);
server.deploy(di);
factory = KeycloakApplication.buildSessionFactory();
setupDefaultRealm();
info("Started Keycloak (http://" + config.getHost() + ":" + config.getPort() + "/auth-server) in "
+ (System.currentTimeMillis() - start) + " ms\n");
}
private void info(String message) {
if (sysout) {
System.out.println(message);
} else {
log.info(message);
}
}
public void stop() {
factory.close();
server.stop();
info("Stopped Keycloak");
}
}

View file

@ -0,0 +1,30 @@
package org.keycloak.testutils;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.ServerSetup;
public class MailServer {
public static void main(String[] args) throws Exception {
ServerSetup setup = new ServerSetup(3025, "localhost", "smtp");
GreenMail greenMail = new GreenMail(setup);
greenMail.start();
while (true) {
int c = greenMail.getReceivedMessages().length;
if (greenMail.waitForIncomingEmail(Long.MAX_VALUE, c + 1)) {
MimeMessage message = greenMail.getReceivedMessages()[c++];
System.out.println("Received mail to " + message.getRecipients(RecipientType.TO)[0]);
System.out.println();
System.out.println(message.getContent());
System.out.println("-------------------------------------------------------");
}
}
}
}

View file

@ -0,0 +1,31 @@
package org.keycloak.testutils;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import org.picketlink.common.util.Base32;
import org.picketlink.idm.credential.util.TimeBasedOTP;
public class TotpGenerator {
public static void main(String[] args) {
String totp = "";
for (String a : args) {
totp += a.trim();
}
totp = totp.replace(" ", "");
final String google = new String(Base32.decode(totp));
final TimeBasedOTP otp = new TimeBasedOTP();
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(otp.generate(google));
}
}, 0, TimeUnit.SECONDS.toMillis(TimeBasedOTP.DEFAULT_INTERVAL_SECONDS));
}
}

View file

@ -24,6 +24,7 @@ package org.keycloak.testsuite;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.PublicKey;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -40,8 +41,11 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.jboss.resteasy.security.PemUtils;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.Assert; import org.junit.Assert;
import org.keycloak.RSATokenVerifier;
import org.keycloak.representations.SkeletonKeyToken;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
@ -52,7 +56,7 @@ public class OAuthClient {
private WebDriver driver; private WebDriver driver;
private String baseUrl = "http://localhost:8081/auth-server/rest"; private String baseUrl = Constants.AUTH_SERVER_ROOT + "/rest";
private String realm = "test"; private String realm = "test";
@ -68,8 +72,13 @@ public class OAuthClient {
private String state; private String state;
public OAuthClient(WebDriver driver) { private PublicKey realmPublicKey;
public OAuthClient(WebDriver driver) throws Exception {
this.driver = driver; this.driver = driver;
JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/testrealm.json")));
realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey"));
} }
public AuthorizationCodeResponse doLogin(String username, String password) { public AuthorizationCodeResponse doLogin(String username, String password) {
@ -109,6 +118,10 @@ public class OAuthClient {
return new AccessTokenResponse(client.execute(post)); return new AccessTokenResponse(client.execute(post));
} }
public SkeletonKeyToken verifyToken(String token) throws Exception {
return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);
}
public boolean isAuthorizationResponse() { public boolean isAuthorizationResponse() {
return getCurrentRequest().equals(redirectUri) && getCurrentQuery().containsKey("code"); return getCurrentRequest().equals(redirectUri) && getCurrentQuery().containsKey("code");
} }

View file

@ -21,17 +21,10 @@
*/ */
package org.keycloak.testsuite.oauth; package org.keycloak.testsuite.oauth;
import java.security.PublicKey;
import org.apache.commons.io.IOUtils;
import org.jboss.resteasy.security.PemUtils;
import org.json.JSONObject;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.RSATokenVerifier;
import org.keycloak.representations.SkeletonKeyToken; import org.keycloak.representations.SkeletonKeyToken;
import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse; import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
@ -61,14 +54,6 @@ public class AccessTokenTest {
@WebResource @WebResource
protected LoginPage loginPage; protected LoginPage loginPage;
private PublicKey realmPublicKey;
@Before
public void before() throws Exception {
JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/testrealm.json")));
realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey"));
}
@Test @Test
public void accessTokenRequest() throws Exception { public void accessTokenRequest() throws Exception {
oauth.doLogin("test-user@localhost", "password"); oauth.doLogin("test-user@localhost", "password");
@ -82,7 +67,8 @@ public class AccessTokenTest {
Assert.assertEquals("bearer", response.getTokenType()); Assert.assertEquals("bearer", response.getTokenType());
SkeletonKeyToken token = RSATokenVerifier.verifyToken(response.getAccessToken(), realmPublicKey, oauth.getRealm()); SkeletonKeyToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("test-user@localhost", token.getPrincipal()); Assert.assertEquals("test-user@localhost", token.getPrincipal());
Assert.assertEquals(1, token.getRealmAccess().getRoles().size()); Assert.assertEquals(1, token.getRealmAccess().getRoles().size());

View file

@ -21,81 +21,45 @@
*/ */
package org.keycloak.testsuite.rule; package org.keycloak.testsuite.rule;
import io.undertow.server.handlers.resource.ClassPathResourceManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo;
import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletInfo;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import javax.servlet.DispatcherType;
import javax.servlet.Servlet; import javax.servlet.Servlet;
import org.jboss.resteasy.jwt.JsonSerialization; import org.jboss.resteasy.jwt.JsonSerialization;
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.junit.rules.ExternalResource; import org.junit.rules.ExternalResource;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.filters.KeycloakSessionServletFilter;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.models.UserModel; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.resources.SaasService;
import org.keycloak.testsuite.ApplicationServlet; import org.keycloak.testsuite.ApplicationServlet;
import org.keycloak.testutils.KeycloakServer;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class KeycloakRule extends ExternalResource { public class KeycloakRule extends ExternalResource {
private String testRealm = "testrealm.json"; private KeycloakServer server;
private UndertowJaxrsServer server;
private KeycloakSessionFactory factory;
private KeycloakSetup setup; private KeycloakSetup setup;
public KeycloakRule() { public KeycloakRule() {
} }
public KeycloakRule(String testRealm) {
this.testRealm = testRealm;
}
public KeycloakRule(KeycloakSetup setup) { public KeycloakRule(KeycloakSetup setup) {
this.setup = setup; this.setup = setup;
} }
protected void before() throws Throwable { protected void before() throws Throwable {
ResteasyDeployment deployment = new ResteasyDeployment(); server = new KeycloakServer();
deployment.setApplicationClass(KeycloakApplication.class.getName()); server.start();
server = new UndertowJaxrsServer().start();
DeploymentInfo di = server.undertowDeployment(deployment, "rest"); server.importRealm(getClass().getResourceAsStream("/testrealm.json"));
di.setClassLoader(getClass().getClassLoader());
di.setContextPath("/auth-server");
di.setDeploymentName("Keycloak");
di.setResourceManager(new ClassPathResourceManager(getClass().getClassLoader(), "META-INF/resources"));
FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class);
di.addFilter(filter);
di.addFilterUrlMapping("SessionFilter", "/rest/*", DispatcherType.REQUEST);
server.deploy(di);
factory = KeycloakApplication.buildSessionFactory();
setupDefaultRealm();
importRealm(testRealm);
if (setup != null) { if (setup != null) {
configure(setup); configure(setup);
@ -114,12 +78,11 @@ public class KeycloakRule extends ExternalResource {
servlet.addMapping("/*"); servlet.addMapping("/*");
deploymentInfo.addServlet(servlet); deploymentInfo.addServlet(servlet);
server.deploy(deploymentInfo); server.getServer().deploy(deploymentInfo);
} }
@Override @Override
protected void after() { protected void after() {
factory.close();
server.stop(); server.stop();
} }
@ -131,78 +94,31 @@ public class KeycloakRule extends ExternalResource {
os.write(c); os.write(c);
} }
byte[] bytes = os.toByteArray(); byte[] bytes = os.toByteArray();
System.out.println(new String(bytes));
return JsonSerialization.fromBytes(RealmRepresentation.class, bytes); return JsonSerialization.fromBytes(RealmRepresentation.class, bytes);
} }
public void setupDefaultRealm() {
KeycloakSession session = createSession();
session.getTransaction().begin();
RealmManager manager = new RealmManager(session);
RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
defaultRealm.setName(RealmModel.DEFAULT_REALM);
defaultRealm.setEnabled(true);
defaultRealm.setTokenLifespan(300);
defaultRealm.setAccessCodeLifespan(60);
defaultRealm.setAccessCodeLifespanUserAction(600);
defaultRealm.setSslNotRequired(false);
defaultRealm.setCookieLoginAllowed(true);
defaultRealm.setRegistrationAllowed(true);
defaultRealm.setAutomaticRegistrationAfterSocialLogin(false);
manager.generateRealmKeys(defaultRealm);
defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
UserModel admin = defaultRealm.addUser("admin");
defaultRealm.grantRole(admin, role);
session.getTransaction().commit();
session.close();
}
public void importRealm(String name) throws IOException {
KeycloakSession session = createSession();
session.getTransaction().begin();
RealmManager manager = new RealmManager(session);
RealmModel defaultRealm = manager.getRealm(RealmModel.DEFAULT_REALM);
UserModel admin = defaultRealm.getUser("admin");
RealmRepresentation rep = loadJson(name);
RealmModel realm = manager.createRealm("test", rep.getRealm());
manager.importRealm(rep, realm);
realm.addRealmAdmin(admin);
session.getTransaction().commit();
session.close();
}
public void configure(KeycloakSetup configurer) { public void configure(KeycloakSetup configurer) {
KeycloakSession session = createSession(); KeycloakSession session = server.getKeycloakSessionFactory().createSession();
session.getTransaction().begin(); session.getTransaction().begin();
RealmManager manager = new RealmManager(session); try {
RealmManager manager = new RealmManager(session);
RealmModel defaultRealm = manager.getRealm(RealmModel.DEFAULT_REALM); RealmModel defaultRealm = manager.getRealm(RealmModel.DEFAULT_REALM);
RealmModel appRealm = manager.getRealm("test"); RealmModel appRealm = manager.getRealm("test");
configurer.config(manager, defaultRealm, appRealm); configurer.config(manager, defaultRealm, appRealm);
session.getTransaction().commit(); session.getTransaction().commit();
session.close(); } finally {
session.close();
}
} }
public KeycloakSession createSession() {
return factory.createSession();
}
public interface KeycloakSetup { public interface KeycloakSetup {
void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm); void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm);
} }
} }

View file

@ -26,12 +26,15 @@ import org.junit.BeforeClass;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.representations.SkeletonKeyToken;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.testsuite.DummySocialServlet; import org.keycloak.testsuite.DummySocialServlet;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup; import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebResource;
@ -65,13 +68,16 @@ public class SocialLoginTest {
@WebResource @WebResource
protected LoginPage loginPage; protected LoginPage loginPage;
@WebResource
protected OAuthClient oauth;
@BeforeClass @BeforeClass
public static void before() { public static void before() {
keycloakRule.deployServlet("dummy-social", "/dummy-social", DummySocialServlet.class); keycloakRule.deployServlet("dummy-social", "/dummy-social", DummySocialServlet.class);
} }
@Test @Test
public void loginSuccess() { public void loginSuccess() throws Exception {
loginPage.open(); loginPage.open();
loginPage.clickSocial("dummy"); loginPage.clickSocial("dummy");
@ -80,6 +86,15 @@ public class SocialLoginTest {
driver.findElement(By.id("submit")).click(); driver.findElement(By.id("submit")).click();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password");
SkeletonKeyToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("dummy-user", token.getPrincipal());
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
Assert.assertTrue(token.getRealmAccess().isUserInRole("user"));
} }
} }

View file

@ -1,4 +1,5 @@
{ {
"id": "test",
"realm": "test", "realm": "test",
"enabled": true, "enabled": true,
"tokenLifespan": 300, "tokenLifespan": 300,