Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2014-03-03 15:50:21 -05:00
commit 01154f18dd
17 changed files with 294 additions and 155 deletions

View file

@ -1,6 +1,6 @@
<ul class="nav nav-tabs nav-tabs-pf">
<li ng-class="{active: !path[2]}"><a href="#/realms/{{realm.realm}}">General</a></li>
<li ng-class="{active: path[2] == 'social'}" data-ng-show="kcSocial && access.viewRealm"><a href="#/realms/{{realm.realm}}/social-settings">Social</a></li>
<li ng-class="{active: path[2] == 'social'}" data-ng-show="realm.social && access.viewRealm"><a href="#/realms/{{realm.realm}}/social-settings">Social</a></li>
<li ng-class="{active: path[2] == 'roles'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/roles">Roles</a></li>
<li ng-class="{active: path[2] == 'default-roles'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/default-roles">Default Roles</a></li>
<li ng-class="{active: path[2] == 'required-credentials'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/required-credentials">Credentials</a></li>

View file

@ -0,0 +1,19 @@
<#import "template.ftl" as layout>
<@layout.registrationLayout; section>
<#if section = "title">
<#if code.success>
Success code=${code.code}
<#else>
Error error=${code.error}
</#if>
<#elseif section = "form">
<div id="kc-code">
<#if code.success>
<p>Please copy this code and paste it into your application:</p>
<textarea id="code">${code.code}</textarea>
<#else>
<p>${code.error}</p>
</#if>
</div>
</#if>
</@layout.registrationLayout>

View file

@ -1,33 +0,0 @@
<#import "template.ftl" as layout>
<@layout.registrationLayout displayInfo=true; section>
<#if section = "title">
${rb.emailUsernameForgotHeader}
<#elseif section = "header">
${rb.emailUsernameForgotHeader}
<#elseif section = "form">
<form id="kc-username-reminder-form" class="${properties.kcFormClass!}" action="${url.loginUsernameReminderUrl}" method="post">
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="email" class="${properties.kcLabelClass!}">${rb.email}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="email" name="email" class="${properties.kcInputClass!}" />
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
<div class="${properties.kcFormOptionsWrapperClass!}">
<span><a href="${url.loginUrl}">${rb.backToLogin}</a></span>
</div>
</div>
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<input class="btn btn-primary btn-lg" type="submit" value="${rb.submit}"/>
</div>
</div>
</form>
<#elseif section = "info" >
${rb.emailUsernameInstruction}
</#if>
</@layout.registrationLayout>

View file

@ -36,9 +36,6 @@
</div>
</#if>
<div class="${properties.kcFormOptionsWrapperClass!}">
<#if realm.registrationAllowed>
<span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.register}</a></span>
</#if>
<#if realm.resetPasswordAllowed>
<span>${rb.loginForgot} <a href="${url.loginPasswordResetUrl}">${rb.password}</a>?</span>
</#if>
@ -54,6 +51,12 @@
</div>
</form>
<#elseif section = "info" >
<#if realm.registrationAllowed>
<div id="kc-registration">
<span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.register}</a></span>
</div>
</#if>
<div id="kc-social-providers">
<ul>
<#list social.providers as p>

View file

@ -4,6 +4,11 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<#if properties.meta?has_content>
<#list properties.meta?split(' ') as meta>
<meta name="${meta?split('==')[0]}" content="${meta?split('==')[1]}"/>
</#list>
</#if>
<title><#nested "title"></title>
<link rel="icon" href="${url.resourcesPath}/img/favicon.ico" />
<#if properties.styles?has_content>
@ -19,7 +24,7 @@
</head>
<body class="${properties.kcBodyClass!}">
<dv id="kc-logo"></dv>
<div id="kc-logo"><div id="kc-logo-wrapper"></div></div>
<div id="kc-container" class="${properties.kcContainerClass!}">
<div id="kc-container-wrapper" class="${properties.kcContainerWrapperClass!}">
@ -31,24 +36,28 @@
<#if displayMessage && message?has_content>
<div id="kc-feedback" class="feedback-${message.type} ${properties.kcFeedBackClass!}">
<div id="kc-feedback-wrapper">
<span>${message.summary}</span>
<span class="kc-feedback-text">${message.summary}</span>
</div>
</div>
</#if>
<div id="kc-form" class="${properties.kcFormAreaClass!}">
<div id="kc-form-wrapper" class="${properties.kcFormAreaWrapperClass!}">
<#nested "form">
<div id="kc-content" class="${properties.kcContentClass!}">
<div id="kc-content-wrapper" class="${properties.kcContentWrapperClass!}">
<div id="kc-form" class="${properties.kcFormAreaClass!}">
<div id="kc-form-wrapper" class="${properties.kcFormAreaWrapperClass!}">
<#nested "form">
</div>
</div>
<#if displayInfo>
<div id="kc-info" class="${properties.kcInfoAreaClass!}">
<div id="kc-info-wrapper" class="${properties.kcInfoAreaWrapperClass!}">
<#nested "info">
</div>
</div>
</#if>
</div>
</div>
<#if displayInfo>
<div id="kc-info" class="${properties.kcInfoAreaClass!}">
<div id="kc-info-wrapper" class="${properties.kcInfoAreaWrapperClass!}">
<#nested "info">
</div>
</div>
</#if>
</div>
</div>

View file

@ -1,20 +1,43 @@
.login-pf .container {
padding-top: 40px;
}
#kc-logo {
width: 100%;
}
#kc-logo-wrapper {
background-image: url("../img/keycloak-logo.png");
background-repeat: no-repeat;
position: absolute;
top: 50px;
right: 50px;
background-position: top right;
height: 37px;
width: 150px;
margin: 50px;
}
#kc-header {
overflow: visible;
padding-left: 80px;
white-space: nowrap;
}
#kc-header-wrapper {
font-size: 26px;
height: 18px;
text-transform: uppercase;
display: block;
/* display: block;
position: relative;
top: -80px;*/
}
#kc-container-wrapper {
bottom: 13%;
position: absolute;
width: 100%;
}
#kc-content {
position: relative;
top: -80px;
}
#kc-form-options span {
@ -27,18 +50,18 @@
margin-bottom: 10px;
}
#kc-feedback-wrapper {
display: inline-block;
width: auto;
#kc-feedback {
background-position: left bottom;
background-repeat: no-repeat;
padding-bottom: 21px;
padding-bottom: 10px;
position: absolute;
top: -40px;
white-space: nowrap;
}
#kc-feedback span {
display: block;
padding: 0.90909090909091em 3.63636363636364em;
border-style: solid;
border-width: 1px 1px 0px 1px;
@ -52,7 +75,7 @@
margin-bottom: 0;
}
.feedback-error {
.feedback-error #kc-feedback-wrapper {
background-image: url(../img/feedback-error-arrow-down.png);
}
.feedback-error span {
@ -61,7 +84,7 @@
background-color: #f8e7e7;
}
.feedback-success {
.feedback-success #kc-feedback-wrapper {
background-image: url(../img/feedback-success-arrow-down.png);
}
.feedback-success span {
@ -70,7 +93,7 @@
background-color: #e4f1e1;
}
.feedback-warning {
.feedback-warning #kc-feedback-wrapper {
background-image: url(../img/feedback-warning-arrow-down.png);
}
.feedback-warning span {
@ -79,6 +102,10 @@
background-color: #fef1e9;
}
#kc-registration {
margin-bottom: 15px;
}
/* TOTP */
ol#kc-totp-settings {
@ -121,6 +148,12 @@ ol#kc-totp-settings li:first-of-type {
width: 50%;
}
/* Code */
#kc-code textarea {
width: 100%;
height: 8em;
}
/* Social */
#kc-social-providers ul {
@ -130,14 +163,17 @@ ol#kc-totp-settings li:first-of-type {
#kc-social-providers li {
display: block;
margin-top: 1em;
width: 130px;
margin-top: 5px;
}
#kc-social-providers li:first-of-type {
margin-top: 0;
}
.zocial {
width: 125px;
}
.zocial.facebook,
.zocial.github,
.zocial.google,
@ -167,26 +203,50 @@ ol#kc-totp-settings li:first-of-type {
}
@media (max-width: 767px) {
#kc-logo {
position: inherit;
display: inline-block;
#kc-logo-wrapper {
background-image: url("../img/keycloak-logo.png");
background-repeat: no-repeat;
background-position: top center;
height: 37px;
margin: 20px;
float: right;
}
#kc-header {
padding-left: 40px;
padding-right: 40px;
white-space: normal;
float: none;
}
#kc-feedback {
position: inherit;
display: inline-block;
margin-left: 20px;
padding-left: 40px;
padding-right: 40px;
float: none;
}
#kc-social-providers {
margin-top: 30px;
#kc-container-wrapper {
position: inherit;
float: none;
}
#kc-form {
padding-left: 40px;
padding-right: 40px;
float: none;
}
#kc-info-wrapper {
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin-top: 20px;
padding-top: 20px;
padding-left: 20px;
padding-right: 40px;
}
#kc-social-providers li {
float: left;
margin-right: 10px;
margin-top: 0;
display: inline-block;
margin-right: 5px;
}
}

View file

@ -1,24 +1,25 @@
parent=base
styles=lib/patternfly/css/patternfly.css lib/zocial/zocial.css css/login.css
meta=viewport==width=device-width,initial-scale=1
kcHtmlClass=login-pf
kcContainerClass=container
kcContainerWrapperClass=row
kcContentClass=col-sm-12 col-md-12 col-lg-12 container
kcContentWrapperClass=row
kcHeaderClass=col-sm-12
kcHeaderClass=col-xs-12 col-sm-7 col-md-6 col-lg-5
kcFeedBackClass=col-sm-offset-2 col-md-offset-4
kcFeedBackClass=col-xs-12 col-sm-5 col-md-6 col-lg-7
kcFormAreaClass=col-sm-7 col-md-6 col-lg-5 login
kcFormAreaClass=col-xs-12 col-sm-8 col-md-8 col-lg-6 login
kcFormClass=form-horizontal
kcFormGroupClass=form-group
kcLabelClass=control-label
kcLabelWrapperClass=col-sm-4 col-md-4 col-lg-3
kcLabelWrapperClass=col-xs-12 col-sm-12 col-md-4 col-lg-3
kcInputClass=form-control
kcInputWrapperClass=col-sm-8 col-md-8 col-lg-9
kcFormOptionsClass=col-sm-offset-4 col-sm-4 col-md-offset-4 col-md-4 col-lg-offset-3 col-lg-5
kcFormButtonsClass=col-sm-4 col-md-4 col-lg-4 submit
kcInputWrapperClass=col-xs-12 col-sm-12 col-md-8 col-lg-9
kcFormOptionsClass=col-xs-5 col-sm-5 col-md-offset-4 col-md-4 col-lg-offset-3 col-lg-5
kcFormButtonsClass=col-xs-7 col-sm-7 col-md-4 col-lg-4 submit
kcInfoAreaClass=col-sm-5 col-md-6 col-lg-7 details
kcInfoAreaClass=col-xs-12 col-sm-4 col-md-4 col-lg-6 details

View file

@ -27,6 +27,8 @@ public interface LoginForms {
public Response createOAuthGrant();
public Response createCode();
public LoginForms setAccessCode(String accessCodeId, String accessCode);
public LoginForms setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);

View file

@ -5,6 +5,6 @@ package org.keycloak.login;
*/
public enum LoginFormsPages {
LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, LOGIN_USERNAME_REMINDER, REGISTER, ERROR, LOGIN_UPDATE_PROFILE;
LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, ERROR, LOGIN_UPDATE_PROFILE, CODE;
}

View file

@ -8,6 +8,7 @@ import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeLoader;
import org.keycloak.login.LoginForms;
import org.keycloak.login.LoginFormsPages;
import org.keycloak.login.freemarker.model.CodeBean;
import org.keycloak.login.freemarker.model.LoginBean;
import org.keycloak.login.freemarker.model.MessageBean;
import org.keycloak.login.freemarker.model.OAuthGrantBean;
@ -178,6 +179,9 @@ public class FreeMarkerLoginForms implements LoginForms {
case OAUTH_GRANT:
attributes.put("oauth", new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested));
break;
case CODE:
attributes.put("code", new CodeBean(accessCode, messageType == MessageType.ERROR ? message : null));
break;
}
try {
@ -197,10 +201,6 @@ public class FreeMarkerLoginForms implements LoginForms {
return createResponse(LoginFormsPages.LOGIN_RESET_PASSWORD);
}
public Response createUsernameReminder() {
return createResponse(LoginFormsPages.LOGIN_USERNAME_REMINDER);
}
public Response createLoginTotp() {
return createResponse(LoginFormsPages.LOGIN_TOTP);
}
@ -218,6 +218,11 @@ public class FreeMarkerLoginForms implements LoginForms {
return createResponse(LoginFormsPages.OAUTH_GRANT);
}
@Override
public Response createCode() {
return createResponse(LoginFormsPages.CODE);
}
public FreeMarkerLoginForms setError(String message) {
this.message = message;
this.messageType = MessageType.ERROR;

View file

@ -29,6 +29,8 @@ public class Templates {
return "error.ftl";
case LOGIN_UPDATE_PROFILE:
return "login-update-profile.ftl";
case CODE:
return "code.ftl";
default:
throw new IllegalArgumentException();
}

View file

@ -0,0 +1,27 @@
package org.keycloak.login.freemarker.model;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class CodeBean {
private final String code;
private final String error;
public CodeBean(String code, String error) {
this.code = code;
this.error = error;
}
public boolean isSuccess() {
return code != null && error == null;
}
public String getCode() {
return code;
}
public String getError() {
return error;
}
}

View file

@ -5,7 +5,7 @@ var Keycloak = function (options) {
return new Keycloak(options);
}
var instance = this;
var kc = this;
if (!options.url) {
var scripts = document.getElementsByTagName('script');
@ -33,7 +33,7 @@ var Keycloak = function (options) {
throw 'clientSecret missing';
}
this.init = function (successCallback, errorCallback) {
kc.init = function (successCallback, errorCallback) {
if (window.oauth.callback) {
delete sessionStorage.oauthToken;
processCallback(successCallback, errorCallback);
@ -44,50 +44,50 @@ var Keycloak = function (options) {
} else if (options.onload) {
switch (options.onload) {
case 'login-required' :
window.location = createLoginUrl(true);
window.location = kc.createLoginUrl(true);
break;
case 'check-sso' :
window.location = createLoginUrl(false);
window.location = kc.createLoginUrl(false);
break;
}
}
}
this.login = function () {
window.location.href = createLoginUrl(true);
kc.login = function () {
window.location.href = kc.createLoginUrl(true);
}
this.logout = function () {
kc.logout = function () {
setToken(undefined);
window.location.href = createLogoutUrl();
window.location.href = kc.createLogoutUrl();
}
this.hasRealmRole = function (role) {
var access = this.realmAccess;
kc.hasRealmRole = function (role) {
var access = kc.realmAccess;
return access && access.roles.indexOf(role) >= 0 || false;
}
this.hasResourceRole = function (role, resource) {
if (!this.resourceAccess) {
kc.hasResourceRole = function (role, resource) {
if (!kc.resourceAccess) {
return false;
}
var access = this.resourceAccess[resource || options.clientId];
var access = kc.resourceAccess[resource || options.clientId];
return access && access.roles.indexOf(role) >= 0 || false;
}
this.loadUserProfile = function (success, error) {
var url = getRealmUrl() + '/account';
kc.loadUserProfile = function (success, error) {
var url = kc.getRealmUrl() + '/account';
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.setRequestHeader('Accept', 'application/json');
req.setRequestHeader('Authorization', 'bearer ' + this.token);
req.setRequestHeader('Authorization', 'bearer ' + kc.token);
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
instance.profile = JSON.parse(req.responseText);
success && success(instance.profile)
kc.profile = JSON.parse(req.responseText);
success && success(kc.profile)
} else {
var response = { status: req.status, statusText: req.status };
if (req.responseText) {
@ -108,22 +108,22 @@ var Keycloak = function (options) {
* @param successCallback
* @param errorCallback
*/
this.onValidAccessToken = function(successCallback, errorCallback) {
if (!this.tokenParsed) {
kc.onValidAccessToken = function(successCallback, errorCallback) {
if (!kc.tokenParsed) {
console.log('no token');
errorCallback();
return;
}
var currTime = new Date().getTime() / 1000;
if (currTime > this.tokenParsed['exp']) {
if (!this.refreshToken) {
if (currTime > kc.tokenParsed['exp']) {
if (!kc.refreshToken) {
console.log('no refresh token');
errorCallback();
return;
}
console.log('calling refresh');
var params = 'grant_type=refresh_token&' + 'refresh_token=' + this.refreshToken;
var url = getRealmUrl() + '/tokens/refresh';
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
var url = kc.getRealmUrl() + '/tokens/refresh';
var req = new XMLHttpRequest();
req.open('POST', url, true, options.clientId, options.clientSecret);
@ -134,8 +134,8 @@ var Keycloak = function (options) {
if (req.status == 200) {
console.log('Refresh Success');
var tokenResponse = JSON.parse(req.responseText);
this.refreshToken = tokenResponse['refresh_token'];
setToken(tokenResponse['access_token'], successCallback);
kc.refreshToken = tokenResponse['refresh_token'];
kc.setToken(tokenResponse['access_token'], successCallback);
} else {
console.log('error on refresh HTTP invoke: ' + req.status);
errorCallback && errorCallback({ authenticated: false, status: req.status, statusText: req.statusText });
@ -150,7 +150,7 @@ var Keycloak = function (options) {
}
function getRealmUrl() {
kc.getRealmUrl = function() {
return options.url + '/auth/rest/realms/' + encodeURIComponent(options.realm);
}
@ -161,7 +161,7 @@ var Keycloak = function (options) {
if (code) {
var params = 'code=' + code;
var url = getRealmUrl() + '/tokens/access/codes';
var url = kc.getRealmUrl() + '/tokens/access/codes';
var req = new XMLHttpRequest();
req.open('POST', url, true, options.clientId, options.clientSecret);
@ -171,8 +171,8 @@ var Keycloak = function (options) {
if (req.readyState == 4) {
if (req.status == 200) {
var tokenResponse = JSON.parse(req.responseText);
instance.refreshToken = tokenResponse['refresh_token'];
setToken(tokenResponse['access_token'], successCallback);
kc.refreshToken = tokenResponse['refresh_token'];
kc.setToken(tokenResponse['access_token'], successCallback);
} else {
errorCallback && errorCallback({ authenticated: false, status: req.status, statusText: req.statusText });
}
@ -189,33 +189,33 @@ var Keycloak = function (options) {
}
}
function setToken(token, successCallback) {
kc.setToken = function(token, successCallback) {
if (token) {
sessionStorage.oauthToken = token;
window.oauth.token = token;
instance.token = token;
kc.token = token;
instance.tokenParsed = JSON.parse(atob(token.split('.')[1]));
instance.authenticated = true;
instance.username = instance.tokenParsed.sub;
instance.realmAccess = instance.tokenParsed.realm_access;
instance.resourceAccess = instance.tokenParsed.resource_access;
kc.tokenParsed = JSON.parse(atob(token.split('.')[1]));
kc.authenticated = true;
kc.username = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
setTimeout(function() {
successCallback && successCallback({ authenticated: instance.authenticated, username: instance.username });
successCallback && successCallback({ authenticated: kc.authenticated, username: kc.username });
}, 0);
} else {
delete sessionStorage.oauthToken;
delete window.oauth.token;
delete instance.token;
delete kc.token;
}
}
function createLoginUrl(prompt) {
kc.createLoginUrl = function(prompt) {
var state = createUUID();
sessionStorage.oauthState = state;
var url = getRealmUrl()
var url = kc.getRealmUrl()
+ '/tokens/login'
+ '?client_id=' + encodeURIComponent(options.clientId)
+ '&redirect_uri=' + getEncodedRedirectUri()
@ -229,17 +229,22 @@ var Keycloak = function (options) {
return url;
}
function createLogoutUrl() {
var url = getRealmUrl()
kc.createLogoutUrl = function() {
var url = kc.getRealmUrl()
+ '/tokens/logout'
+ '?redirect_uri=' + getEncodedRedirectUri();
return url;
}
function getEncodedRedirectUri() {
var url = (location.protocol + '//' + location.hostname + (location.port && (':' + location.port)) + location.pathname);
if (location.hash) {
url += '?redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
var url;
if (options.redirectUri) {
url = options.redirectUri;
} else {
url = (location.protocol + '//' + location.hostname + (location.port && (':' + location.port)) + location.pathname);
if (location.hash) {
url += '?redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
}
}
return encodeURI(url);
}

View file

@ -11,4 +11,6 @@ public interface Constants {
String INTERNAL_ROLE = "KEYCLOAK_";
String ACCOUNT_MANAGEMENT_APP = "account";
String INSTALLED_APP_URN = "urn:ietf:wg:oauth:2.0:oob";
}

View file

@ -38,6 +38,7 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.TokenService;
import javax.ws.rs.Path;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
@ -79,24 +80,32 @@ public class OAuthFlows {
public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect, boolean rememberMe) {
String code = accessCode.getCode();
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
log.debug("redirectAccessCode: state: {0}", state);
if (state != null)
redirectUri.queryParam("state", state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
rememberMe = rememberMe || remember != null;
location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo, rememberMe));
return location.build();
if (Constants.INSTALLED_APP_URN.equals(redirect)) {
return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), code).createCode();
} else {
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
log.debug("redirectAccessCode: state: {0}", state);
if (state != null)
redirectUri.queryParam("state", state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
rememberMe = rememberMe || remember != null;
location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo, rememberMe));
return location.build();
}
}
public Response redirectError(ClientModel client, String error, String state, String redirect) {
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("error", error);
if (state != null) {
redirectUri.queryParam("state", state);
if (Constants.INSTALLED_APP_URN.equals(redirect)) {
return Flows.forms(realm, request, uriInfo).setError(error).createCode();
} else {
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("error", error);
if (state != null) {
redirectUri.queryParam("state", state);
}
return Response.status(302).location(redirectUri.build()).build();
}
return Response.status(302).location(redirectUri.build()).build();
}
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user) {

View file

@ -38,6 +38,8 @@ import org.json.JSONObject;
import org.junit.Assert;
import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessScope;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.UserRepresentation;
@ -156,6 +158,12 @@ public class OAuthClient {
}
}
public void verifyCode(String code) {
if (!RSAProvider.verify(new JWSInput(code), realmPublicKey)) {
throw new RuntimeException("Failed to verify code");
}
}
public String getClientId() {
return clientId;
}

View file

@ -26,8 +26,8 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.OAuthClient.AuthorizationCodeResponse;
@ -36,6 +36,7 @@ import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import java.io.IOException;
@ -73,6 +74,21 @@ public class AuthorizationCodeTest {
Assert.assertNotNull(response.getCode());
Assert.assertEquals("mystate", response.getState());
Assert.assertNull(response.getError());
oauth.verifyCode(response.getCode());
}
@Test
public void authorizationRequestInstalledApp() throws IOException {
oauth.redirectUri(Constants.INSTALLED_APP_URN);
oauth.doLogin("test-user@localhost", "password");
String title = driver.getTitle();
Assert.assertTrue(title.startsWith("Success code="));
String code = driver.findElement(By.id("code")).getText();
oauth.verifyCode(code);
}
@Test
@ -94,6 +110,8 @@ public class AuthorizationCodeTest {
Assert.assertTrue(response.isRedirected());
Assert.assertNotNull(response.getCode());
oauth.verifyCode(response.getCode());
}
@Test
@ -104,6 +122,8 @@ public class AuthorizationCodeTest {
Assert.assertNotNull(response.getCode());
Assert.assertNull(response.getState());
Assert.assertNull(response.getError());
oauth.verifyCode(response.getCode());
}
}