diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
index dee13f666b..bdfd390897 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
@@ -82,6 +82,23 @@
{{:: 'access-token-lifespan.tooltip' | translate}}
{{:: 'client-login-timeout' | translate}}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 68853082ca..ffe8328baa 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -176,7 +176,8 @@ failedLogout=Logout failed
unknownLoginRequesterMessage=Unknown login requester
loginRequesterNotEnabledMessage=Login requester not enabled
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
-directGrantsOnlyMessage=Direct-grants-only clients are not allowed to initiate browser login
+standardFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.
+implicitFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.
invalidRedirectUriMessage=Invalid redirect uri
unsupportedNameIdFormatMessage=Unsupported NameIDFormat
invlidRequesterMessage=Invalid requester
diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js
index 09e0d86cfc..a74e1ccae7 100755
--- a/integration/js/src/main/resources/keycloak.js
+++ b/integration/js/src/main/resources/keycloak.js
@@ -36,6 +36,39 @@
if (initOptions.onLoad === 'login-required') {
kc.loginRequired = true;
}
+
+ if (initOptions.responseMode) {
+ if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') {
+ kc.responseMode = initOptions.responseMode;
+ } else {
+ throw 'Invalid value for responseMode';
+ }
+ }
+
+ if (initOptions.flow) {
+ switch (initOptions.flow) {
+ case 'standard':
+ kc.responseType = 'code';
+ break;
+ case 'implicit':
+ kc.responseType = 'id_token token';
+ break;
+ case 'hybrid':
+ kc.responseType = 'code id_token token';
+ break;
+ default:
+ throw 'Invalid value for flow';
+ }
+ kc.flow = initOptions.flow;
+ }
+ }
+
+ if (!kc.responseMode) {
+ kc.responseMode = 'fragment';
+ }
+ if (!kc.responseType) {
+ kc.responseType = 'code';
+ kc.flow = 'standard';
}
var promise = createPromise();
@@ -95,7 +128,7 @@
return;
} else if (initOptions) {
if (initOptions.token || initOptions.refreshToken) {
- setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
+ setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false);
if (loginIframe.enable) {
setupCheckLoginIframe().success(function() {
@@ -132,13 +165,14 @@
kc.createLoginUrl = function(options) {
var state = createUUID();
+ var nonce = createUUID();
var redirectUri = adapter.redirectUri(options);
if (options && options.prompt) {
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
}
- sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) });
+ sessionStorage.oauthState = JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
var action = 'auth';
if (options && options.action == 'register') {
@@ -150,7 +184,9 @@
+ '?client_id=' + encodeURIComponent(kc.clientId)
+ '&redirect_uri=' + encodeURIComponent(redirectUri)
+ '&state=' + encodeURIComponent(state)
- + '&response_type=code';
+ + '&nonce=' + encodeURIComponent(nonce)
+ + '&response_mode=' + encodeURIComponent(kc.responseMode)
+ + '&response_type=' + encodeURIComponent(kc.responseType);
if (options && options.prompt) {
url += '&prompt=' + encodeURIComponent(options.prompt);
@@ -277,7 +313,7 @@
}
kc.isTokenExpired = function(minValidity) {
- if (!kc.tokenParsed || !kc.refreshToken) {
+ if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != 'implicit' )) {
throw 'Not authenticated';
}
@@ -327,7 +363,7 @@
timeLocal = (timeLocal + new Date().getTime()) / 2;
var tokenResponse = JSON.parse(req.responseText);
- setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
+ setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true);
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
@@ -365,7 +401,7 @@
kc.clearToken = function() {
if (kc.token) {
- setToken(null, null, null);
+ setToken(null, null, null, true);
kc.onAuthLogout && kc.onAuthLogout();
if (kc.loginRequired) {
kc.login();
@@ -394,7 +430,21 @@
var error = oauth.error;
var prompt = oauth.prompt;
- if (code) {
+ var timeLocal = new Date().getTime();
+
+ if (error) {
+ if (prompt != 'none') {
+ kc.onAuthError && kc.onAuthError();
+ promise && promise.setError();
+ } else {
+ promise && promise.setSuccess();
+ }
+ return;
+ } else if ((kc.flow != 'standard') && (oauth.access_token || oauth.id_token)) {
+ authSuccess(oauth.access_token, null, oauth.id_token, true);
+ }
+
+ if ((kc.flow != 'implicit') && code) {
var params = 'code=' + code + '&grant_type=authorization_code';
var url = getRealmUrl() + '/protocol/openid-connect/token';
@@ -412,20 +462,12 @@
req.withCredentials = true;
- var timeLocal = new Date().getTime();
-
req.onreadystatechange = function() {
if (req.readyState == 4) {
if (req.status == 200) {
- timeLocal = (timeLocal + new Date().getTime()) / 2;
var tokenResponse = JSON.parse(req.responseText);
- setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
-
- kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
-
- kc.onAuthSuccess && kc.onAuthSuccess();
- promise && promise.setSuccess();
+ authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], kc.flow === 'standard');
} else {
kc.onAuthError && kc.onAuthError();
promise && promise.setError();
@@ -434,14 +476,30 @@
};
req.send(params);
- } else if (error) {
- if (prompt != 'none') {
- kc.onAuthError && kc.onAuthError();
+ }
+
+ function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
+ timeLocal = (timeLocal + new Date().getTime()) / 2;
+
+ setToken(accessToken, refreshToken, idToken, true);
+
+ if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
+ (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
+ (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
+
+ console.log('invalid nonce!');
+ kc.clearToken();
promise && promise.setError();
} else {
- promise && promise.setSuccess();
+ kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
+
+ if (fulfillPromise) {
+ kc.onAuthSuccess && kc.onAuthSuccess();
+ promise && promise.setSuccess();
+ }
}
}
+
}
function loadConfig(url) {
@@ -507,7 +565,12 @@
return promise.promise;
}
- function setToken(token, refreshToken, idToken) {
+ function setToken(token, refreshToken, idToken, useTokenTime) {
+ if (kc.tokenTimeoutHandle) {
+ clearTimeout(kc.tokenTimeoutHandle);
+ kc.tokenTimeoutHandle = null;
+ }
+
if (token) {
kc.token = token;
kc.tokenParsed = decodeToken(token);
@@ -520,6 +583,13 @@
kc.subject = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
+
+ if (kc.onTokenExpired) {
+ var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000);
+ var expiresIn = kc.tokenParsed.exp - start;
+ kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000);
+ }
+
} else {
delete kc.token;
delete kc.tokenParsed;
@@ -597,53 +667,21 @@
}
function parseCallback(url) {
- if (url.indexOf('?') != -1) {
- var oauth = {};
+ var oauth = new CallbackParser(url, kc.responseMode).parseUri();
- oauth.newUrl = url.split('?')[0];
- var paramString = url.split('?')[1];
- var fragIndex = paramString.indexOf('#');
- if (fragIndex != -1) {
- paramString = paramString.substring(0, fragIndex);
- }
- var params = paramString.split('&');
- for (var i = 0; i < params.length; i++) {
- var p = params[i].split('=');
- switch (decodeURIComponent(p[0])) {
- case 'code':
- oauth.code = p[1];
- break;
- case 'error':
- oauth.error = p[1];
- break;
- case 'state':
- oauth.state = decodeURIComponent(p[1]);
- break;
- case 'redirect_fragment':
- oauth.fragment = decodeURIComponent(p[1]);
- break;
- case 'prompt':
- oauth.prompt = p[1];
- break;
- default:
- oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + p[0] + '=' + p[1];
- break;
- }
+ var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
+
+ if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
+ delete sessionStorage.oauthState;
+
+ oauth.redirectUri = sessionState.redirectUri;
+ oauth.storedNonce = sessionState.nonce;
+
+ if (oauth.fragment) {
+ oauth.newUrl += '#' + oauth.fragment;
}
- var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
-
- if (sessionState && (oauth.code || oauth.error) && oauth.state && oauth.state == sessionState.state) {
- delete sessionStorage.oauthState;
-
- oauth.redirectUri = sessionState.redirectUri;
-
- if (oauth.fragment) {
- oauth.newUrl += '#' + oauth.fragment;
- }
-
- return oauth;
- }
+ return oauth;
}
}
@@ -907,6 +945,103 @@
throw 'invalid adapter type: ' + type;
}
+
+
+ var CallbackParser = function(uriToParse, responseMode) {
+ if (!(this instanceof CallbackParser)) {
+ return new CallbackParser(uriToParse, responseMode);
+ }
+ var parser = this;
+
+ var initialParse = function() {
+ var baseUri = null;
+ var queryString = null;
+ var fragmentString = null;
+
+ var questionMarkIndex = uriToParse.indexOf("?");
+ var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1);
+ if (questionMarkIndex == -1 && fragmentIndex == -1) {
+ baseUri = uriToParse;
+ } else if (questionMarkIndex != -1) {
+ baseUri = uriToParse.substring(0, questionMarkIndex);
+ queryString = uriToParse.substring(questionMarkIndex + 1);
+ if (fragmentIndex != -1) {
+ fragmentIndex = queryString.indexOf("#");
+ fragmentString = queryString.substring(fragmentIndex + 1);
+ queryString = queryString.substring(0, fragmentIndex);
+ }
+ } else {
+ baseUri = uriToParse.substring(0, fragmentIndex);
+ fragmentString = uriToParse.substring(fragmentIndex + 1);
+ }
+
+ return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString };
+ }
+
+ var parseParams = function(paramString) {
+ var result = {};
+ var params = paramString.split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ var paramName = decodeURIComponent(p[0]);
+ var paramValue = decodeURIComponent(p[1]);
+ result[paramName] = paramValue;
+ }
+ return result;
+ }
+
+ var handleQueryParam = function(paramName, paramValue, oauth) {
+ var supportedOAuthParams = [ 'code', 'error', 'state' ];
+
+ for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
+ if (paramName === supportedOAuthParams[i]) {
+ oauth[paramName] = paramValue;
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ parser.parseUri = function() {
+ var parsedUri = initialParse();
+
+ var queryParams = {};
+ if (parsedUri.queryString) {
+ queryParams = parseParams(parsedUri.queryString);
+ }
+
+ var oauth = { newUrl: parsedUri.baseUri };
+ for (var param in queryParams) {
+ switch (param) {
+ case 'redirect_fragment':
+ oauth.fragment = queryParams[param];
+ break;
+ case 'prompt':
+ oauth.prompt = queryParams[param];
+ break;
+ default:
+ if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
+ oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
+ }
+ break;
+ }
+ }
+
+ if (responseMode === 'fragment') {
+ var fragmentParams = {};
+ if (parsedUri.fragmentString) {
+ fragmentParams = parseParams(parsedUri.fragmentString);
+ }
+ for (var param in fragmentParams) {
+ oauth[param] = fragmentParams[param];
+ }
+ }
+
+ return oauth;
+ }
+ }
+
}
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
index e5c94840f7..06df07311c 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -5,6 +5,7 @@ import org.keycloak.migration.migrators.MigrateTo1_3_0;
import org.keycloak.migration.migrators.MigrateTo1_4_0;
import org.keycloak.migration.migrators.MigrateTo1_5_0;
import org.keycloak.migration.migrators.MigrateTo1_6_0;
+import org.keycloak.migration.migrators.MigrateTo1_7_0;
import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
import org.keycloak.models.KeycloakSession;
@@ -54,6 +55,12 @@ public class MigrationModelManager {
}
new MigrateTo1_6_0().migrate(session);
}
+ if (stored == null || stored.lessThan(MigrateTo1_7_0.VERSION)) {
+ if (stored != null) {
+ logger.debug("Migrating older model to 1.7.0 updates");
+ }
+ new MigrateTo1_7_0().migrate(session);
+ }
model.setStoredVersion(MigrationModel.LATEST_VERSION);
}
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
new file mode 100644
index 0000000000..e4ead4dbb2
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
@@ -0,0 +1,23 @@
+package org.keycloak.migration.migrators;
+
+import java.util.List;
+
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author
Marek Posolda
+ */
+public class MigrateTo1_7_0 {
+
+ public static final ModelVersion VERSION = new ModelVersion("1.7.0");
+
+ public void migrate(KeycloakSession session) {
+ List
realms = session.realms().getRealms();
+ for (RealmModel realm : realms) {
+ realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
+ }
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index 051493e90f..492c2e1ef4 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -111,12 +111,18 @@ public interface ClientModel extends RoleContainerModel {
boolean isPublicClient();
void setPublicClient(boolean flag);
- boolean isDirectGrantsOnly();
- void setDirectGrantsOnly(boolean flag);
-
boolean isConsentRequired();
void setConsentRequired(boolean consentRequired);
+ boolean isStandardFlowEnabled();
+ void setStandardFlowEnabled(boolean standardFlowEnabled);
+
+ boolean isImplicitFlowEnabled();
+ void setImplicitFlowEnabled(boolean implicitFlowEnabled);
+
+ boolean isDirectAccessGrantsEnabled();
+ void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled);
+
boolean isServiceAccountsEnabled();
void setServiceAccountsEnabled(boolean serviceAccountsEnabled);
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index 3ecc51c0d7..c50e4cd89b 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -20,6 +20,8 @@ public interface Constants {
String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS;
+ // 15 minutes
+ int DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT = 900;
// 30 days
int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 62f9162e2d..6c2c260421 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -107,6 +107,9 @@ public interface RealmModel extends RoleContainerModel {
void setAccessTokenLifespan(int seconds);
+ int getAccessTokenLifespanForImplicitFlow();
+ void setAccessTokenLifespanForImplicitFlow(int seconds);
+
int getAccessCodeLifespan();
void setAccessCodeLifespan(int seconds);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index d04699f16c..b5edbaf9ee 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
@@ -30,6 +30,9 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private String baseUrl;
private boolean bearerOnly;
private boolean consentRequired;
+ private boolean standardFlowEnabled;
+ private boolean implicitFlowEnabled;
+ private boolean directAccessGrantsEnabled;
private boolean serviceAccountsEnabled;
private boolean directGrantsOnly;
private int nodeReRegistrationTimeout;
@@ -243,6 +246,30 @@ public class ClientEntity extends AbstractIdentifiableEntity {
this.consentRequired = consentRequired;
}
+ public boolean isStandardFlowEnabled() {
+ return standardFlowEnabled;
+ }
+
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ this.standardFlowEnabled = standardFlowEnabled;
+ }
+
+ public boolean isImplicitFlowEnabled() {
+ return implicitFlowEnabled;
+ }
+
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ this.implicitFlowEnabled = implicitFlowEnabled;
+ }
+
+ public boolean isDirectAccessGrantsEnabled() {
+ return directAccessGrantsEnabled;
+ }
+
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ this.directAccessGrantsEnabled = directAccessGrantsEnabled;
+ }
+
public boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 8f5c8440b5..7a08bca7fb 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -44,6 +44,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private int ssoSessionMaxLifespan;
private int offlineSessionIdleTimeout;
private int accessTokenLifespan;
+ private int accessTokenLifespanForImplicitFlow;
private int accessCodeLifespan;
private int accessCodeLifespanUserAction;
private int accessCodeLifespanLogin;
@@ -272,6 +273,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.accessTokenLifespan = accessTokenLifespan;
}
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return accessTokenLifespanForImplicitFlow;
+ }
+
+ public void setAccessTokenLifespanForImplicitFlow(int accessTokenLifespanForImplicitFlow) {
+ this.accessTokenLifespanForImplicitFlow = accessTokenLifespanForImplicitFlow;
+ }
+
public int getAccessCodeLifespan() {
return accessCodeLifespan;
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 723617b7a6..e60d915811 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -205,6 +205,7 @@ public class ModelToRepresentation {
rep.setEditUsernameAllowed(realm.isEditUsernameAllowed());
rep.setRevokeRefreshToken(realm.isRevokeRefreshToken());
rep.setAccessTokenLifespan(realm.getAccessTokenLifespan());
+ rep.setAccessTokenLifespanForImplicitFlow(realm.getAccessTokenLifespanForImplicitFlow());
rep.setSsoSessionIdleTimeout(realm.getSsoSessionIdleTimeout());
rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan());
rep.setOfflineSessionIdleTimeout(realm.getOfflineSessionIdleTimeout());
@@ -418,8 +419,10 @@ public class ModelToRepresentation {
rep.setFullScopeAllowed(clientModel.isFullScopeAllowed());
rep.setBearerOnly(clientModel.isBearerOnly());
rep.setConsentRequired(clientModel.isConsentRequired());
+ rep.setStandardFlowEnabled(clientModel.isStandardFlowEnabled());
+ rep.setImplicitFlowEnabled(clientModel.isImplicitFlowEnabled());
+ rep.setDirectAccessGrantsEnabled(clientModel.isDirectAccessGrantsEnabled());
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
- rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
rep.setRootUrl(clientModel.getRootUrl());
rep.setBaseUrl(clientModel.getBaseUrl());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index dd82098242..8360e48731 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -105,6 +105,9 @@ public class RepresentationToModel {
if (rep.getAccessTokenLifespan() != null) newRealm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
else newRealm.setAccessTokenLifespan(300);
+ if (rep.getAccessTokenLifespanForImplicitFlow() != null) newRealm.setAccessTokenLifespanForImplicitFlow(rep.getAccessTokenLifespanForImplicitFlow());
+ else newRealm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
+
if (rep.getSsoSessionIdleTimeout() != null) newRealm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
else newRealm.setSsoSessionIdleTimeout(1800);
if (rep.getSsoSessionMaxLifespan() != null) newRealm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
@@ -603,6 +606,7 @@ public class RepresentationToModel {
if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore());
if (rep.getRevokeRefreshToken() != null) realm.setRevokeRefreshToken(rep.getRevokeRefreshToken());
if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
+ if (rep.getAccessTokenLifespanForImplicitFlow() != null) realm.setAccessTokenLifespanForImplicitFlow(rep.getAccessTokenLifespanForImplicitFlow());
if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
if (rep.getOfflineSessionIdleTimeout() != null) realm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout());
@@ -772,8 +776,17 @@ public class RepresentationToModel {
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
+ if (resourceRep.isStandardFlowEnabled() != null) client.setStandardFlowEnabled(resourceRep.isStandardFlowEnabled());
+ if (resourceRep.isImplicitFlowEnabled() != null) client.setImplicitFlowEnabled(resourceRep.isImplicitFlowEnabled());
+ if (resourceRep.isDirectAccessGrantsEnabled() != null) client.setDirectAccessGrantsEnabled(resourceRep.isDirectAccessGrantsEnabled());
if (resourceRep.isServiceAccountsEnabled() != null) client.setServiceAccountsEnabled(resourceRep.isServiceAccountsEnabled());
- if (resourceRep.isDirectGrantsOnly() != null) client.setDirectGrantsOnly(resourceRep.isDirectGrantsOnly());
+
+ // Backwards compatibility only
+ if (resourceRep.isDirectGrantsOnly() != null) {
+ logger.warn("Using deprecated 'directGrantsOnly' configuration in JSON representation. It will be removed in future versions");
+ client.setStandardFlowEnabled(!resourceRep.isDirectGrantsOnly());
+ }
+
if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
if (resourceRep.getProtocol() != null) client.setProtocol(resourceRep.getProtocol());
@@ -869,8 +882,10 @@ public class RepresentationToModel {
if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled());
if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
if (rep.isConsentRequired() != null) resource.setConsentRequired(rep.isConsentRequired());
+ if (rep.isStandardFlowEnabled() != null) resource.setStandardFlowEnabled(rep.isStandardFlowEnabled());
+ if (rep.isImplicitFlowEnabled() != null) resource.setImplicitFlowEnabled(rep.isImplicitFlowEnabled());
+ if (rep.isDirectAccessGrantsEnabled() != null) resource.setDirectAccessGrantsEnabled(rep.isDirectAccessGrantsEnabled());
if (rep.isServiceAccountsEnabled() != null) resource.setServiceAccountsEnabled(rep.isServiceAccountsEnabled());
- if (rep.isDirectGrantsOnly() != null) resource.setDirectGrantsOnly(rep.isDirectGrantsOnly());
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
if (rep.isFrontchannelLogout() != null) resource.setFrontchannelLogout(rep.isFrontchannelLogout());
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index e03452ad2b..20c87251a1 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -163,16 +163,6 @@ public class ClientAdapter implements ClientModel {
}
- public boolean isDirectGrantsOnly() {
- if (updated != null) return updated.isDirectGrantsOnly();
- return cached.isDirectGrantsOnly();
- }
-
- public void setDirectGrantsOnly(boolean flag) {
- getDelegateForUpdate();
- updated.setDirectGrantsOnly(flag);
- }
-
public Set getScopeMappings() {
if (updated != null) return updated.getScopeMappings();
Set roles = new HashSet();
@@ -451,6 +441,42 @@ public class ClientAdapter implements ClientModel {
updated.setConsentRequired(consentRequired);
}
+ @Override
+ public boolean isStandardFlowEnabled() {
+ if (updated != null) return updated.isStandardFlowEnabled();
+ return cached.isStandardFlowEnabled();
+ }
+
+ @Override
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ getDelegateForUpdate();
+ updated.setStandardFlowEnabled(standardFlowEnabled);
+ }
+
+ @Override
+ public boolean isImplicitFlowEnabled() {
+ if (updated != null) return updated.isImplicitFlowEnabled();
+ return cached.isImplicitFlowEnabled();
+ }
+
+ @Override
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ getDelegateForUpdate();
+ updated.setImplicitFlowEnabled(implicitFlowEnabled);
+ }
+
+ @Override
+ public boolean isDirectAccessGrantsEnabled() {
+ if (updated != null) return updated.isDirectAccessGrantsEnabled();
+ return cached.isDirectAccessGrantsEnabled();
+ }
+
+ @Override
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ getDelegateForUpdate();
+ updated.setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
+ }
+
@Override
public boolean isServiceAccountsEnabled() {
if (updated != null) return updated.isServiceAccountsEnabled();
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 02abe5e99f..c24f151ecd 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -300,6 +300,18 @@ public class RealmAdapter implements RealmModel {
updated.setAccessTokenLifespan(seconds);
}
+ @Override
+ public int getAccessTokenLifespanForImplicitFlow() {
+ if (updated != null) return updated.getAccessTokenLifespanForImplicitFlow();
+ return cached.getAccessTokenLifespanForImplicitFlow();
+ }
+
+ @Override
+ public void setAccessTokenLifespanForImplicitFlow(int seconds) {
+ getDelegateForUpdate();
+ updated.setAccessTokenLifespanForImplicitFlow(seconds);
+ }
+
@Override
public int getAccessCodeLifespan() {
if (updated != null) return updated.getAccessCodeLifespan();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 7c7b97d796..a68395603c 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -36,7 +36,6 @@ public class CachedClient implements Serializable {
private Map attributes = new HashMap();
private boolean publicClient;
private boolean fullScopeAllowed;
- private boolean directGrantsOnly;
private boolean frontchannelLogout;
private int notBefore;
private Set scope = new HashSet();
@@ -49,6 +48,9 @@ public class CachedClient implements Serializable {
private List defaultRoles = new LinkedList();
private boolean bearerOnly;
private boolean consentRequired;
+ private boolean standardFlowEnabled;
+ private boolean implicitFlowEnabled;
+ private boolean directAccessGrantsEnabled;
private boolean serviceAccountsEnabled;
private Map roles = new HashMap();
private int nodeReRegistrationTimeout;
@@ -67,7 +69,6 @@ public class CachedClient implements Serializable {
protocol = model.getProtocol();
attributes.putAll(model.getAttributes());
notBefore = model.getNotBefore();
- directGrantsOnly = model.isDirectGrantsOnly();
frontchannelLogout = model.isFrontchannelLogout();
publicClient = model.isPublicClient();
fullScopeAllowed = model.isFullScopeAllowed();
@@ -86,6 +87,9 @@ public class CachedClient implements Serializable {
defaultRoles.addAll(model.getDefaultRoles());
bearerOnly = model.isBearerOnly();
consentRequired = model.isConsentRequired();
+ standardFlowEnabled = model.isStandardFlowEnabled();
+ implicitFlowEnabled = model.isImplicitFlowEnabled();
+ directAccessGrantsEnabled = model.isDirectAccessGrantsEnabled();
serviceAccountsEnabled = model.isServiceAccountsEnabled();
for (RoleModel role : model.getRoles()) {
roles.put(role.getName(), role.getId());
@@ -139,10 +143,6 @@ public class CachedClient implements Serializable {
return publicClient;
}
- public boolean isDirectGrantsOnly() {
- return directGrantsOnly;
- }
-
public int getNotBefore() {
return notBefore;
}
@@ -203,6 +203,18 @@ public class CachedClient implements Serializable {
return consentRequired;
}
+ public boolean isStandardFlowEnabled() {
+ return standardFlowEnabled;
+ }
+
+ public boolean isImplicitFlowEnabled() {
+ return implicitFlowEnabled;
+ }
+
+ public boolean isDirectAccessGrantsEnabled() {
+ return directAccessGrantsEnabled;
+ }
+
public boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 4ae07fd4f1..e423562f45 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -61,6 +61,7 @@ public class CachedRealm implements Serializable {
private int ssoSessionMaxLifespan;
private int offlineSessionIdleTimeout;
private int accessTokenLifespan;
+ private int accessTokenLifespanForImplicitFlow;
private int accessCodeLifespan;
private int accessCodeLifespanUserAction;
private int accessCodeLifespanLogin;
@@ -146,6 +147,7 @@ public class CachedRealm implements Serializable {
ssoSessionMaxLifespan = model.getSsoSessionMaxLifespan();
offlineSessionIdleTimeout = model.getOfflineSessionIdleTimeout();
accessTokenLifespan = model.getAccessTokenLifespan();
+ accessTokenLifespanForImplicitFlow = model.getAccessTokenLifespanForImplicitFlow();
accessCodeLifespan = model.getAccessCodeLifespan();
accessCodeLifespanUserAction = model.getAccessCodeLifespanUserAction();
accessCodeLifespanLogin = model.getAccessCodeLifespanLogin();
@@ -347,6 +349,10 @@ public class CachedRealm implements Serializable {
return accessTokenLifespan;
}
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return accessTokenLifespanForImplicitFlow;
+ }
+
public int getAccessCodeLifespan() {
return accessCodeLifespan;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 6cb24f8dce..f6778594c9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -498,6 +498,36 @@ public class ClientAdapter implements ClientModel {
entity.setConsentRequired(consentRequired);
}
+ @Override
+ public boolean isStandardFlowEnabled() {
+ return entity.isStandardFlowEnabled();
+ }
+
+ @Override
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ entity.setStandardFlowEnabled(standardFlowEnabled);
+ }
+
+ @Override
+ public boolean isImplicitFlowEnabled() {
+ return entity.isImplicitFlowEnabled();
+ }
+
+ @Override
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ entity.setImplicitFlowEnabled(implicitFlowEnabled);
+ }
+
+ @Override
+ public boolean isDirectAccessGrantsEnabled() {
+ return entity.isDirectAccessGrantsEnabled();
+ }
+
+ @Override
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ entity.setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
+ }
+
@Override
public boolean isServiceAccountsEnabled() {
return entity.isServiceAccountsEnabled();
@@ -508,16 +538,6 @@ public class ClientAdapter implements ClientModel {
entity.setServiceAccountsEnabled(serviceAccountsEnabled);
}
- @Override
- public boolean isDirectGrantsOnly() {
- return entity.isDirectGrantsOnly();
- }
-
- @Override
- public void setDirectGrantsOnly(boolean flag) {
- entity.setDirectGrantsOnly(flag);
- }
-
@Override
public RoleModel getRole(String name) {
TypedQuery query = em.createNamedQuery("getClientRoleByName", RoleEntity.class);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index acc5182f60..cb69f14d62 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -360,6 +360,16 @@ public class RealmAdapter implements RealmModel {
em.flush();
}
+ @Override
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return realm.getAccessTokenLifespanForImplicitFlow();
+ }
+
+ @Override
+ public void setAccessTokenLifespanForImplicitFlow(int seconds) {
+ realm.setAccessTokenLifespanForImplicitFlow(seconds);
+ }
+
@Override
public int getSsoSessionIdleTimeout() {
return realm.getSsoSessionIdleTimeout();
@@ -715,6 +725,8 @@ public class RealmAdapter implements RealmModel {
entity.setId(id);
entity.setClientId(clientId);
entity.setEnabled(true);
+ entity.setStandardFlowEnabled(true);
+ entity.setDirectAccessGrantsEnabled(true);
entity.setRealm(realm);
realm.getClients().add(entity);
em.persist(entity);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 6218e26d4b..1569875955 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -95,15 +95,21 @@ public class ClientEntity {
@Column(name="MANAGEMENT_URL")
private String managementUrl;
- @Column(name="DIRECT_GRANTS_ONLY")
- protected boolean directGrantsOnly;
-
@Column(name="BEARER_ONLY")
private boolean bearerOnly;
@Column(name="CONSENT_REQUIRED")
private boolean consentRequired;
+ @Column(name="STANDARD_FLOW_ENABLED")
+ private boolean standardFlowEnabled;
+
+ @Column(name="IMPLICIT_FLOW_ENABLED")
+ private boolean implicitFlowEnabled;
+
+ @Column(name="DIRECT_ACCESS_GRANTS_ENABLED")
+ private boolean directAccessGrantsEnabled;
+
@Column(name="SERVICE_ACCOUNTS_ENABLED")
private boolean serviceAccountsEnabled;
@@ -339,6 +345,30 @@ public class ClientEntity {
this.consentRequired = consentRequired;
}
+ public boolean isStandardFlowEnabled() {
+ return standardFlowEnabled;
+ }
+
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ this.standardFlowEnabled = standardFlowEnabled;
+ }
+
+ public boolean isImplicitFlowEnabled() {
+ return implicitFlowEnabled;
+ }
+
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ this.implicitFlowEnabled = implicitFlowEnabled;
+ }
+
+ public boolean isDirectAccessGrantsEnabled() {
+ return directAccessGrantsEnabled;
+ }
+
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ this.directAccessGrantsEnabled = directAccessGrantsEnabled;
+ }
+
public boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
@@ -347,14 +377,6 @@ public class ClientEntity {
this.serviceAccountsEnabled = serviceAccountsEnabled;
}
- public boolean isDirectGrantsOnly() {
- return directGrantsOnly;
- }
-
- public void setDirectGrantsOnly(boolean directGrantsOnly) {
- this.directGrantsOnly = directGrantsOnly;
- }
-
public int getNodeReRegistrationTimeout() {
return nodeReRegistrationTimeout;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 6492b812a1..05af313de4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -86,6 +86,8 @@ public class RealmEntity {
private int offlineSessionIdleTimeout;
@Column(name="ACCESS_TOKEN_LIFESPAN")
protected int accessTokenLifespan;
+ @Column(name="ACCESS_TOKEN_LIFE_IMPLICIT")
+ protected int accessTokenLifespanForImplicitFlow;
@Column(name="ACCESS_CODE_LIFESPAN")
protected int accessCodeLifespan;
@Column(name="USER_ACTION_LIFESPAN")
@@ -336,6 +338,14 @@ public class RealmEntity {
this.accessTokenLifespan = accessTokenLifespan;
}
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return accessTokenLifespanForImplicitFlow;
+ }
+
+ public void setAccessTokenLifespanForImplicitFlow(int accessTokenLifespanForImplicitFlow) {
+ this.accessTokenLifespanForImplicitFlow = accessTokenLifespanForImplicitFlow;
+ }
+
public int getAccessCodeLifespan() {
return accessCodeLifespan;
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index fc1f13defb..eb6bcd9544 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -504,6 +504,39 @@ public class ClientAdapter extends AbstractMongoAdapter imple
updateMongoEntity();
}
+ @Override
+ public boolean isStandardFlowEnabled() {
+ return getMongoEntity().isStandardFlowEnabled();
+ }
+
+ @Override
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ getMongoEntity().setStandardFlowEnabled(standardFlowEnabled);
+ updateMongoEntity();
+ }
+
+ @Override
+ public boolean isImplicitFlowEnabled() {
+ return getMongoEntity().isImplicitFlowEnabled();
+ }
+
+ @Override
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ getMongoEntity().setImplicitFlowEnabled(implicitFlowEnabled);
+ updateMongoEntity();
+ }
+
+ @Override
+ public boolean isDirectAccessGrantsEnabled() {
+ return getMongoEntity().isDirectAccessGrantsEnabled();
+ }
+
+ @Override
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ getMongoEntity().setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
+ updateMongoEntity();
+ }
+
@Override
public boolean isServiceAccountsEnabled() {
return getMongoEntity().isServiceAccountsEnabled();
@@ -515,17 +548,6 @@ public class ClientAdapter extends AbstractMongoAdapter imple
updateMongoEntity();
}
- @Override
- public boolean isDirectGrantsOnly() {
- return getMongoEntity().isDirectGrantsOnly();
- }
-
- @Override
- public void setDirectGrantsOnly(boolean flag) {
- getMongoEntity().setDirectGrantsOnly(flag);
- updateMongoEntity();
- }
-
@Override
public RoleAdapter getRole(String name) {
DBObject query = new QueryBuilder()
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index d31503cf40..c8fc1f0b95 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -368,6 +368,17 @@ public class RealmAdapter extends AbstractMongoAdapter impleme
updateRealm();
}
+ @Override
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return realm.getAccessTokenLifespanForImplicitFlow();
+ }
+
+ @Override
+ public void setAccessTokenLifespanForImplicitFlow(int seconds) {
+ realm.setAccessTokenLifespanForImplicitFlow(seconds);
+ updateRealm();
+ }
+
@Override
public int getAccessCodeLifespan() {
return realm.getAccessCodeLifespan();
@@ -799,6 +810,8 @@ public class RealmAdapter extends AbstractMongoAdapter impleme
clientEntity.setClientId(clientId);
clientEntity.setRealmId(getId());
clientEntity.setEnabled(true);
+ clientEntity.setStandardFlowEnabled(true);
+ clientEntity.setDirectAccessGrantsEnabled(true);
getMongoStore().insertEntity(clientEntity, invocationContext);
final ClientModel model = new ClientAdapter(session, this, clientEntity, invocationContext);
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index ff1275a986..34025933d1 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -142,15 +142,15 @@ public class SamlService extends AuthorizationEndpointBase {
event.error(Errors.CLIENT_DISABLED);
return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
}
- if ((client instanceof ClientModel) && ((ClientModel) client).isBearerOnly()) {
+ if (client.isBearerOnly()) {
event.event(EventType.LOGIN);
event.error(Errors.NOT_ALLOWED);
return ErrorPage.error(session, Messages.BEARER_ONLY);
}
- if (client.isDirectGrantsOnly()) {
+ if (!client.isStandardFlowEnabled()) {
event.event(EventType.LOGIN);
event.error(Errors.NOT_ALLOWED);
- return ErrorPage.error(session, Messages.DIRECT_GRANTS_ONLY);
+ return ErrorPage.error(session, Messages.STANDARD_FLOW_DISABLED);
}
session.getContext().setClient(client);
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index b1d4162296..710c77d0e3 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -470,7 +470,8 @@ public class AuthenticationProcessor {
LoginProtocol protocol = getSession().getProvider(LoginProtocol.class, getClientSession().getAuthMethod());
protocol.setRealm(getRealm())
.setHttpHeaders(getHttpRequest().getHttpHeaders())
- .setUriInfo(getUriInfo());
+ .setUriInfo(getUriInfo())
+ .setEventBuilder(event);
Response response = protocol.sendError(getClientSession(), Error.CANCELLED_BY_USER);
forceChallenge(response);
}
@@ -808,7 +809,7 @@ public class AuthenticationProcessor {
public Response finishAuthentication() {
event.success();
RealmModel realm = clientSession.getRealm();
- return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection);
+ return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection, event);
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index b9d55db316..40bcc67dc2 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -33,6 +33,10 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie;
+import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder;
+import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.ResourceAdminManager;
@@ -62,6 +66,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
public static final String ISSUER = "iss";
+ public static final String RESPONSE_MODE_PARAM = "response_mode";
+
private static final Logger log = Logger.getLogger(OIDCLoginProtocol.class);
protected KeycloakSession session;
@@ -74,6 +80,9 @@ public class OIDCLoginProtocol implements LoginProtocol {
protected EventBuilder event;
+ protected OIDCResponseType responseType;
+ protected OIDCResponseMode responseMode;
+
public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
this.session = session;
this.realm = realm;
@@ -86,6 +95,15 @@ public class OIDCLoginProtocol implements LoginProtocol {
}
+ private void setupResponseTypeAndMode(ClientSessionModel clientSession) {
+ String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+ String responseMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+ this.responseType = OIDCResponseType.parse(responseType);
+ this.responseMode = OIDCResponseMode.parse(responseMode, this.responseType);
+ this.event.detail(Details.RESPONSE_TYPE, responseType);
+ this.event.detail(Details.RESPONSE_MODE, this.responseMode.toString().toLowerCase());
+ }
+
@Override
public OIDCLoginProtocol setSession(KeycloakSession session) {
this.session = session;
@@ -116,32 +134,63 @@ public class OIDCLoginProtocol implements LoginProtocol {
return this;
}
+
@Override
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
ClientSessionModel clientSession = accessCode.getClientSession();
+ setupResponseTypeAndMode(clientSession);
+
String redirect = clientSession.getRedirectUri();
+ OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode);
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
- accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
- UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode());
log.debugv("redirectAccessCode: state: {0}", state);
if (state != null)
- redirectUri.queryParam(OAuth2Constants.STATE, state);
- Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
+ redirectUri.addParam(OAuth2Constants.STATE, state);
- return location.build();
+ // Standard or hybrid flow
+ if (responseType.hasResponseType(OIDCResponseType.CODE)) {
+ accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
+ redirectUri.addParam(OAuth2Constants.CODE, accessCode.getCode());
+ }
+
+ // Implicit or hybrid flow
+ if (responseType.isImplicitOrHybridFlow()) {
+ TokenManager tokenManager = new TokenManager();
+ AccessTokenResponse res = tokenManager.responseBuilder(realm, clientSession.getClient(), event, session, userSession, clientSession)
+ .generateAccessToken()
+ .generateIDToken()
+ .build();
+
+ if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) {
+ redirectUri.addParam("id_token", res.getIdToken());
+ }
+
+ if (responseType.hasResponseType(OIDCResponseType.TOKEN)) {
+ redirectUri.addParam("access_token", res.getToken());
+ redirectUri.addParam("token_type", res.getTokenType());
+ redirectUri.addParam("session-state", res.getSessionState());
+ redirectUri.addParam("expires_in", String.valueOf(res.getExpiresIn()));
+ }
+
+ redirectUri.addParam("not-before-policy", String.valueOf(res.getNotBeforePolicy()));
+ }
+
+ return redirectUri.build();
}
+
@Override
public Response sendError(ClientSessionModel clientSession, Error error) {
+ setupResponseTypeAndMode(clientSession);
+
String redirect = clientSession.getRedirectUri();
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
- UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, translateError(error));
+ OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode).addParam(OAuth2Constants.ERROR, translateError(error));
if (state != null)
- redirectUri.queryParam(OAuth2Constants.STATE, state);
+ redirectUri.addParam(OAuth2Constants.STATE, state);
session.sessions().removeClientSession(realm, clientSession);
RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
- Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
- return location.build();
+ return redirectUri.build();
}
private String translateError(Error error) {
@@ -161,10 +210,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
@Override
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
- if (!(clientSession.getClient() instanceof ClientModel))
- return;
- ClientModel app = clientSession.getClient();
- new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, app, clientSession);
+ ClientModel client = clientSession.getClient();
+ new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, client, clientSession);
}
@Override
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 304e6af282..e76734eb43 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -26,6 +26,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
@@ -52,7 +53,7 @@ import java.util.Map;
import java.util.Set;
/**
- * Stateful object that creates tokens and manages oauth access codes
+ * Stateless object that creates tokens and manages oauth access codes
*
* @author Bill Burke
* @version $Revision: 1 $
@@ -455,8 +456,9 @@ public class TokenManager {
if (session != null) {
token.setSessionState(session.getId());
}
- if (realm.getAccessTokenLifespan() > 0) {
- token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
+ int tokenLifespan = getTokenLifespan(realm, clientSession);
+ if (tokenLifespan > 0) {
+ token.expiration(Time.currentTime() + tokenLifespan);
}
Set allowedOrigins = client.getWebOrigins();
if (allowedOrigins != null) {
@@ -465,6 +467,15 @@ public class TokenManager {
return token;
}
+ private int getTokenLifespan(RealmModel realm, ClientSessionModel clientSession) {
+ boolean implicitFlow = false;
+ String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+ if (responseType != null) {
+ implicitFlow = OIDCResponseType.parse(responseType).isImplicitFlow();
+ }
+ return implicitFlow ? realm.getAccessTokenLifespanForImplicitFlow() : realm.getAccessTokenLifespan();
+ }
+
protected void addComposites(AccessToken token, RoleModel role) {
AccessToken.Access access = null;
if (role.getContainer() instanceof RealmModel) {
@@ -582,9 +593,7 @@ public class TokenManager {
idToken.issuer(accessToken.getIssuer());
idToken.setNonce(accessToken.getNonce());
idToken.setSessionState(accessToken.getSessionState());
- if (realm.getAccessTokenLifespan() > 0) {
- idToken.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
- }
+ idToken.expiration(accessToken.getExpiration());
transformIDToken(session, idToken, realm, client, userSession.getUser(), userSession, clientSession);
return this;
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index c8d56563d0..9ed45de00c 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -1,19 +1,11 @@
package org.keycloak.protocol.oidc.endpoints;
-import java.util.List;
-
import javax.ws.rs.GET;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
-import org.keycloak.common.ClientConnection;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@@ -24,12 +16,12 @@ import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.IdentityProviderModel;
-import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.AuthorizationEndpointBase;
-import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.Urls;
@@ -55,11 +47,13 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
private ClientSessionModel clientSession;
private Action action;
+ private OIDCResponseType parsedResponseType;
private String clientId;
private String redirectUri;
private String redirectUriParam;
private String responseType;
+ private String responseMode;
private String state;
private String scope;
private String loginHint;
@@ -80,6 +74,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+ responseMode = params.getFirst(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
redirectUriParam = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
state = params.getFirst(OIDCLoginProtocol.STATE_PARAM);
scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM);
@@ -90,8 +85,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
checkSsl();
checkRealm();
- checkClient();
checkResponseType();
+ checkClient();
checkRedirectUri();
createClientSession();
@@ -172,9 +167,14 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
throw new ErrorPageException(session, Messages.BEARER_ONLY);
}
- if (client.isDirectGrantsOnly()) {
+ if ((parsedResponseType.hasResponseType(OIDCResponseType.CODE) || parsedResponseType.hasResponseType(OIDCResponseType.NONE)) && !client.isStandardFlowEnabled()) {
event.error(Errors.NOT_ALLOWED);
- throw new ErrorPageException(session, Messages.DIRECT_GRANTS_ONLY);
+ throw new ErrorPageException(session, Messages.STANDARD_FLOW_DISABLED);
+ }
+
+ if (parsedResponseType.isImplicitOrHybridFlow() && !client.isImplicitFlowEnabled()) {
+ event.error(Errors.NOT_ALLOWED);
+ throw new ErrorPageException(session, Messages.IMPLICIT_FLOW_DISABLED);
}
session.getContext().setClient(client);
@@ -192,14 +192,32 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
event.detail(Details.RESPONSE_TYPE, responseType);
- if (responseType.equals(OAuth2Constants.CODE)) {
+ try {
+ parsedResponseType = OIDCResponseType.parse(responseType);
if (action == null) {
action = Action.CODE;
}
- } else {
+ } catch (IllegalArgumentException iae) {
+ logger.error(iae.getMessage());
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
}
+
+ try {
+ OIDCResponseMode parsedResponseMode = OIDCResponseMode.parse(responseMode, parsedResponseType);
+ event.detail(Details.RESPONSE_MODE, parsedResponseMode.toString().toLowerCase());
+
+ // Disallowed by OIDC specs
+ if (parsedResponseType.isImplicitOrHybridFlow() && parsedResponseMode == OIDCResponseMode.QUERY) {
+ logger.error("Response_mode 'query' not allowed for implicit or hybrid flow");
+ event.error(Errors.INVALID_REQUEST);
+ throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+ }
+
+ } catch (IllegalArgumentException iae) {
+ event.error(Errors.INVALID_REQUEST);
+ throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+ }
}
private void checkRedirectUri() {
@@ -228,6 +246,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
if (loginHint != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
if (prompt != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, prompt);
if (idpHint != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, idpHint);
+ if (responseMode != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, responseMode);
}
private Response buildAuthorizationCodeAuthorizationResponse() {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index c790661d3e..fe358e0ba4 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -327,7 +327,7 @@ public class TokenEndpoint {
}
public Response buildResourceOwnerPasswordCredentialsGrant() {
- event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
+ event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD);
if (client.isConsentRequired()) {
event.error(Errors.CONSENT_DENIED);
@@ -393,7 +393,7 @@ public class TokenEndpoint {
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
}
- event.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH);
+ event.detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
UserModel clientUser = session.users().getUserByServiceAccountClient(client);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java
new file mode 100644
index 0000000000..06cc3cc91c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java
@@ -0,0 +1,149 @@
+package org.keycloak.protocol.oidc.utils;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.common.util.Encode;
+import org.keycloak.common.util.KeycloakUriBuilder;
+
+/**
+ * @author Marek Posolda
+ */
+public abstract class OIDCRedirectUriBuilder {
+
+ protected final KeycloakUriBuilder uriBuilder;
+
+ protected OIDCRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+ this.uriBuilder = uriBuilder;
+ }
+
+ public abstract OIDCRedirectUriBuilder addParam(String paramName, String paramValue);
+ public abstract Response build();
+
+
+ public static OIDCRedirectUriBuilder fromUri(String baseUri, OIDCResponseMode responseMode) {
+ KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(baseUri);
+
+ switch (responseMode) {
+ case QUERY: return new QueryRedirectUriBuilder(uriBuilder);
+ case FRAGMENT: return new FragmentRedirectUriBuilder(uriBuilder);
+ case FORM_POST: return new FormPostRedirectUriBuilder(uriBuilder);
+ }
+
+ throw new IllegalStateException("Not possible to end here");
+ }
+
+
+ // Impl subclasses
+
+
+ // http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
+ public static class QueryRedirectUriBuilder extends OIDCRedirectUriBuilder {
+
+ protected QueryRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+ super(uriBuilder);
+ }
+
+ @Override
+ public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
+ uriBuilder.queryParam(paramName, paramValue);
+ return this;
+ }
+
+ @Override
+ public Response build() {
+ URI redirectUri = uriBuilder.build();
+ Response.ResponseBuilder location = Response.status(302).location(redirectUri);
+ return location.build();
+ }
+ }
+
+
+ // http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
+ public static class FragmentRedirectUriBuilder extends OIDCRedirectUriBuilder {
+
+ private StringBuilder fragment;
+
+ protected FragmentRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+ super(uriBuilder);
+ }
+
+ @Override
+ public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
+ String param = paramName + "=" + Encode.encodeQueryParam(paramValue);
+ if (fragment == null) {
+ fragment = new StringBuilder(param);
+ } else {
+ fragment.append("&").append(param);
+ }
+ return this;
+ }
+
+ @Override
+ public Response build() {
+ if (fragment != null) {
+ uriBuilder.encodedFragment(fragment.toString());
+ }
+ URI redirectUri = uriBuilder.build();
+
+ Response.ResponseBuilder location = Response.status(302).location(redirectUri);
+ return location.build();
+ }
+
+ }
+
+
+ // http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
+ public static class FormPostRedirectUriBuilder extends OIDCRedirectUriBuilder {
+
+ private Map params = new HashMap<>();
+
+ protected FormPostRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+ super(uriBuilder);
+ }
+
+ @Override
+ public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
+ params.put(paramName, Encode.encodeQueryParam(paramValue));
+ return this;
+ }
+
+ @Override
+ public Response build() {
+ StringBuilder builder = new StringBuilder();
+ URI redirectUri = uriBuilder.build();
+
+ builder.append("");
+ builder.append(" ");
+ builder.append(" OIDC Form_Post Response ");
+ builder.append(" ");
+ builder.append(" ");
+
+ builder.append(" ");
+ builder.append(" ");
+ builder.append("");
+
+ return Response.status(Response.Status.OK)
+ .type(MediaType.TEXT_HTML_TYPE)
+ .entity(builder.toString()).build();
+ }
+
+ }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseMode.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseMode.java
new file mode 100644
index 0000000000..c255cccd25
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseMode.java
@@ -0,0 +1,25 @@
+package org.keycloak.protocol.oidc.utils;
+
+/**
+ * @author Marek Posolda
+ */
+public enum OIDCResponseMode {
+
+ QUERY, FRAGMENT, FORM_POST;
+
+ public static OIDCResponseMode parse(String responseMode, OIDCResponseType responseType) {
+ if (responseMode == null) {
+ return getDefaultResponseMode(responseType);
+ } else {
+ return Enum.valueOf(OIDCResponseMode.class, responseMode.toUpperCase());
+ }
+ }
+
+ private static OIDCResponseMode getDefaultResponseMode(OIDCResponseType responseType) {
+ if (responseType.isImplicitOrHybridFlow()) {
+ return OIDCResponseMode.FRAGMENT;
+ } else {
+ return OIDCResponseMode.QUERY;
+ }
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java
new file mode 100644
index 0000000000..6377b22a47
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java
@@ -0,0 +1,93 @@
+package org.keycloak.protocol.oidc.utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+
+/**
+ * @author Marek Posolda
+ */
+public class OIDCResponseType {
+
+ public static final String CODE = OIDCLoginProtocol.CODE_PARAM;
+ public static final String TOKEN = "token";
+ public static final String ID_TOKEN = "id_token";
+ public static final String NONE = "none";
+
+ private static final List ALLOWED_RESPONSE_TYPES = Arrays.asList(CODE, TOKEN, ID_TOKEN, NONE);
+
+ private final List responseTypes;
+
+
+ private OIDCResponseType(List responseTypes) {
+ this.responseTypes = responseTypes;
+ }
+
+
+ public static OIDCResponseType parse(String responseTypeParam) {
+ if (responseTypeParam == null) {
+ throw new IllegalArgumentException("response_type is null");
+ }
+
+ String[] responseTypes = responseTypeParam.trim().split(" ");
+ List allowedTypes = new ArrayList<>();
+ for (String current : responseTypes) {
+ if (ALLOWED_RESPONSE_TYPES.contains(current)) {
+ allowedTypes.add(current);
+ } else {
+ throw new IllegalArgumentException("Unsupported response_type: " + responseTypeParam);
+ }
+ }
+
+ validateAllowedTypes(allowedTypes);
+
+ return new OIDCResponseType(allowedTypes);
+ }
+
+ private static void validateAllowedTypes(List responseTypes) {
+ if (responseTypes.size() == 0) {
+ throw new IllegalStateException("No responseType provided");
+ }
+ if (responseTypes.contains(NONE) && responseTypes.size() > 1) {
+ throw new IllegalArgumentException("None not allowed with some other response_type");
+ }
+ if (responseTypes.contains(ID_TOKEN) && responseTypes.size() == 1) {
+ throw new IllegalArgumentException("Not supported to use response_type=id_token alone");
+ }
+ if (responseTypes.contains(TOKEN) && responseTypes.size() == 1) {
+ throw new IllegalArgumentException("Not supported to use response_type=token alone");
+ }
+ }
+
+
+ public boolean hasResponseType(String responseType) {
+ return responseTypes.contains(responseType);
+ }
+
+
+ public boolean isImplicitOrHybridFlow() {
+ return hasResponseType(TOKEN) || hasResponseType(ID_TOKEN);
+ }
+
+ public boolean isImplicitFlow() {
+ return hasResponseType(TOKEN) && hasResponseType(ID_TOKEN) && !hasResponseType(CODE);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (String responseType : responseTypes) {
+ if (!first) {
+ builder.append(" ");
+ } else {
+ first = false;
+ }
+ builder.append(responseType);
+ }
+ return builder.toString();
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 0fa1b92807..a868aa8fee 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -43,6 +43,7 @@ public class ApplianceBootstrap {
realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
realm.setSsoSessionIdleTimeout(1800);
realm.setAccessTokenLifespan(60);
+ realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
realm.setSsoSessionMaxLifespan(36000);
realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
realm.setAccessCodeLifespan(60);
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index c820e4d091..0131b6d206 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -380,7 +380,8 @@ public class AuthenticationManager {
public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
ClientSessionModel clientSession,
- HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection) {
+ HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
+ EventBuilder event) {
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
if (sessionCookie != null) {
@@ -407,7 +408,8 @@ public class AuthenticationManager {
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
protocol.setRealm(realm)
.setHttpHeaders(request.getHttpHeaders())
- .setUriInfo(uriInfo);
+ .setUriInfo(uriInfo)
+ .setEventBuilder(event);
RestartLoginCookie.expireRestartCookie(realm, clientConnection, uriInfo);
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
@@ -429,7 +431,7 @@ public class AuthenticationManager {
}
event.success();
RealmModel realm = clientSession.getRealm();
- return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
+ return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection, event);
}
@@ -522,9 +524,11 @@ public class AuthenticationManager {
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
- .setUriInfo(context.getUriInfo());
+ .setUriInfo(context.getUriInfo())
+ .setEventBuilder(event);
+ Response response = protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
event.error(Errors.REJECTED_BY_USER);
- return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
+ return response;
}
else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
clientSession.setNote(CURRENT_REQUIRED_ACTION, model.getProviderId());
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 5f2eeb3760..ca1db96590 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -110,7 +110,9 @@ public class Messages {
public static final String BEARER_ONLY = "bearerOnlyMessage";
- public static final String DIRECT_GRANTS_ONLY = "directGrantsOnlyMessage";
+ public static final String STANDARD_FLOW_DISABLED = "standardFlowDisabledMessage";
+
+ public static final String IMPLICIT_FLOW_DISABLED = "implicitFlowDisabledMessage";
public static final String INVALID_REDIRECT_URI = "invalidRedirectUriMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index bc83df5288..36e4c6c1d6 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -59,6 +59,8 @@ import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
@@ -546,7 +548,7 @@ public class LoginActionsService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processConsent(final MultivaluedMap formData) {
- event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
+ event.event(EventType.LOGIN);
if (!checkSsl()) {
@@ -561,38 +563,28 @@ public class LoginActionsService {
return ErrorPage.error(session, Messages.INVALID_ACCESS_CODE);
}
ClientSessionModel clientSession = accessCode.getClientSession();
- event.detail(Details.CODE_ID, clientSession.getId());
- String redirect = clientSession.getRedirectUri();
+ initEvent(clientSession);
+
UserSessionModel userSession = clientSession.getUserSession();
UserModel user = userSession.getUser();
ClientModel client = clientSession.getClient();
- event.client(client)
- .user(user)
- .detail(Details.RESPONSE_TYPE, "code")
- .detail(Details.REDIRECT_URI, redirect);
-
- event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
- event.detail(Details.USERNAME, userSession.getLoginUsername());
- if (userSession.isRememberMe()) {
- event.detail(Details.REMEMBER_ME, "true");
- }
-
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
}
- event.session(userSession);
if (formData.containsKey("cancel")) {
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
protocol.setRealm(realm)
.setHttpHeaders(headers)
- .setUriInfo(uriInfo);
+ .setUriInfo(uriInfo)
+ .setEventBuilder(event);
+ Response response = protocol.sendError(clientSession, Error.CONSENT_DENIED);
event.error(Errors.REJECTED_BY_USER);
- return protocol.sendError(clientSession, Error.CONSENT_DENIED);
+ return response;
}
UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
@@ -613,7 +605,7 @@ public class LoginActionsService {
event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
event.success();
- return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
+ return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection, event);
}
@Path("email-verification")
@@ -727,22 +719,27 @@ public class LoginActionsService {
}
private void initEvent(ClientSessionModel clientSession) {
+ UserSessionModel userSession = clientSession.getUserSession();
+
+ String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+ if (responseType == null) {
+ responseType = "code";
+ }
+ String respMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+ OIDCResponseMode responseMode = OIDCResponseMode.parse(respMode, OIDCResponseType.parse(responseType));
+
event.event(EventType.LOGIN).client(clientSession.getClient())
- .user(clientSession.getUserSession().getUser())
- .session(clientSession.getUserSession().getId())
+ .user(userSession.getUser())
+ .session(userSession.getId())
.detail(Details.CODE_ID, clientSession.getId())
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
.detail(Details.USERNAME, clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME))
- .detail(Details.RESPONSE_TYPE, "code");
-
- UserSessionModel userSession = clientSession.getUserSession();
-
- if (userSession != null) {
- event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
- event.detail(Details.USERNAME, userSession.getLoginUsername());
- if (userSession.isRememberMe()) {
- event.detail(Details.REMEMBER_ME, "true");
- }
+ .detail(Details.AUTH_METHOD, userSession.getAuthMethod())
+ .detail(Details.USERNAME, userSession.getLoginUsername())
+ .detail(Details.RESPONSE_TYPE, responseType)
+ .detail(Details.RESPONSE_MODE, responseMode.toString().toLowerCase());
+ if (userSession.isRememberMe()) {
+ event.detail(Details.REMEMBER_ME, "true");
}
}
@@ -827,9 +824,14 @@ public class LoginActionsService {
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
- .setUriInfo(context.getUriInfo());
- event.detail(Details.CUSTOM_REQUIRED_ACTION, action).error(Errors.REJECTED_BY_USER);
- return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
+ .setUriInfo(context.getUriInfo())
+ .setEventBuilder(event);
+
+ event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
+ Response response = protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
+ event.error(Errors.REJECTED_BY_USER);
+ return response;
+
}
throw new RuntimeException("Unreachable");
diff --git a/services/src/test/java/org/keycloak/test/ResponseTypeTest.java b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java
new file mode 100644
index 0000000000..b3f77a7b7f
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java
@@ -0,0 +1,41 @@
+package org.keycloak.test;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+
+/**
+ * @author Marek Posolda
+ */
+public class ResponseTypeTest {
+
+ @Test
+ public void testResponseTypes() {
+ assertFail(null);
+ assertFail("");
+ assertFail("foo");
+ assertSuccess("code");
+ assertSuccess("none");
+ assertFail("id_token");
+ assertFail("token");
+ assertFail("refresh_token");
+ assertSuccess("id_token token");
+ assertSuccess("code token");
+ assertSuccess("code id_token");
+ assertSuccess("code id_token token");
+ assertFail("code none");
+ assertFail("code refresh_token");
+ }
+
+ private void assertSuccess(String responseType) {
+ OIDCResponseType.parse(responseType);
+ }
+
+ private void assertFail(String responseType) {
+ try {
+ OIDCResponseType.parse(responseType);
+ Assert.fail("Not expected to parse '" + responseType + "' with success");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
index 87eadb5dfc..2be0435e7f 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
@@ -32,8 +32,14 @@ public class CreateClientForm extends Form {
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='consentRequired']]")
private OnOffSwitch consentRequiredSwitch;
- @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='directGrantsOnly']]")
- private OnOffSwitch directGrantsOnlySwitch;
+ @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='standardFlowEnabled']]")
+ private OnOffSwitch standardFlowEnabledSwitch;
+
+ @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='implicitFlowEnabled']]")
+ private OnOffSwitch implicitFlowEnabledSwitch;
+
+ @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='directAccessGrantsEnabled']]")
+ private OnOffSwitch directAccessGrantsEnabledSwitch;
@FindBy(id = "protocol")
private Select protocolSelect;
@@ -69,7 +75,9 @@ public class CreateClientForm extends Form {
setName(client.getName());
setEnabled(client.isEnabled());
setConsentRequired(client.isConsentRequired());
- setDirectGrantsOnly(client.isDirectGrantsOnly());
+ setStandardFlowEnabled(client.isStandardFlowEnabled());
+ setImplicitFlowEnabled(client.isImplicitFlowEnabled());
+ setDirectAccessGrantsEnabled(client.isDirectAccessGrantsEnabled());
setProtocol(client.getProtocol());
if (OIDC.equals(client.getProtocol())) {
setAccessType(client);
@@ -88,7 +96,9 @@ public class CreateClientForm extends Form {
values.setName(getName());
values.setEnabled(isEnabled());
values.setConsentRequired(isConsentRequired());
- values.setDirectGrantsOnly(isDirectGrantsOnly());
+ values.setStandardFlowEnabled(isStandardFlowEnabled());
+ values.setImplicitFlowEnabled(isImplicitFlowEnabled());
+ values.setDirectAccessGrantsEnabled(isDirectAccessGrantsEnabled());
values.setProtocol(getProtocol());
if (OIDC.equals(values.getProtocol())) {
values.setBearerOnly(isBearerOnly());
@@ -195,12 +205,28 @@ public class CreateClientForm extends Form {
consentRequiredSwitch.setOn(consentRequired);
}
- public boolean isDirectGrantsOnly() {
- return directGrantsOnlySwitch.isOn();
+ public boolean isStandardFlowEnabled() {
+ return standardFlowEnabledSwitch.isOn();
}
- public void setDirectGrantsOnly(boolean directGrantsOnly) {
- directGrantsOnlySwitch.setOn(directGrantsOnly);
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ standardFlowEnabledSwitch.setOn(standardFlowEnabled);
+ }
+
+ public boolean isImplicitFlowEnabled() {
+ return implicitFlowEnabledSwitch.isOn();
+ }
+
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ implicitFlowEnabledSwitch.setOn(implicitFlowEnabled);
+ }
+
+ public boolean isDirectAccessGrantsEnabled() {
+ return directAccessGrantsEnabledSwitch.isOn();
+ }
+
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ directAccessGrantsEnabledSwitch.setOn(directAccessGrantsEnabled);
}
public String getProtocol() {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java
index 6c40d642f1..94e9b4bed8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java
@@ -56,7 +56,9 @@ public abstract class AbstractClientTest extends AbstractConsoleTest {
client.setClientId(clientId);
client.setEnabled(true);
client.setConsentRequired(false);
- client.setDirectGrantsOnly(false);
+ client.setStandardFlowEnabled(true);
+ client.setImplicitFlowEnabled(false);
+ client.setDirectAccessGrantsEnabled(true);
client.setProtocol(OIDC);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
index 5229527235..d259adf9e2 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
@@ -111,7 +111,9 @@ public class ClientSettingsTest extends AbstractClientTest {
assertEqualsStringAttributes(c1.getName(), c2.getName());
assertEqualsBooleanAttributes(c1.isEnabled(), c2.isEnabled());
assertEqualsBooleanAttributes(c1.isConsentRequired(), c2.isConsentRequired());
- assertEqualsBooleanAttributes(c1.isDirectGrantsOnly(), c2.isDirectGrantsOnly());
+ assertEqualsBooleanAttributes(c1.isStandardFlowEnabled(), c2.isStandardFlowEnabled());
+ assertEqualsBooleanAttributes(c1.isImplicitFlowEnabled(), c2.isImplicitFlowEnabled());
+ assertEqualsBooleanAttributes(c1.isDirectAccessGrantsEnabled(), c2.isDirectAccessGrantsEnabled());
assertEqualsStringAttributes(c1.getProtocol(), c2.getProtocol());
assertEqualsBooleanAttributes(c1.isBearerOnly(), c2.isBearerOnly());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
index b43bfad2ac..5174acf733 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
@@ -55,7 +55,7 @@ public class LoginEventsTest extends AbstractConsoleTest {
List resultList = loginEventsPage.table().rows();
- assertEquals(7, resultList.size());
+ assertEquals(8, resultList.size());
resultList.get(0).findElement(By.xpath("//td[text()='LOGIN']"));
resultList.get(0).findElement(By.xpath("//td[text()='User']/../td[text()='" + testUser.getId() + "']"));
resultList.get(0).findElement(By.xpath("//td[text()='Client']/../td[text()='security-admin-console']"));
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 74c70ea782..84e724b99e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -8,6 +8,7 @@ import org.junit.Assert;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.events.admin.AdminEvent;
@@ -134,7 +135,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
return expect(EventType.CLIENT_LOGIN)
.detail(Details.CODE_ID, isCodeId())
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
- .detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH)
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
.removeDetail(Details.CODE_ID)
.session(isUUID());
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
index 9e51065150..8147cb3a72 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
@@ -255,6 +255,7 @@ public class AdminAPITest {
Assert.assertEquals(rep.getAccessCodeLifespanUserAction(), storedRealm.getAccessCodeLifespanUserAction());
if (rep.getNotBefore() != null) Assert.assertEquals(rep.getNotBefore(), storedRealm.getNotBefore());
if (rep.getAccessTokenLifespan() != null) Assert.assertEquals(rep.getAccessTokenLifespan(), storedRealm.getAccessTokenLifespan());
+ if (rep.getAccessTokenLifespanForImplicitFlow() != null) Assert.assertEquals(rep.getAccessTokenLifespanForImplicitFlow(), storedRealm.getAccessTokenLifespanForImplicitFlow());
if (rep.getSsoSessionIdleTimeout() != null) Assert.assertEquals(rep.getSsoSessionIdleTimeout(), storedRealm.getSsoSessionIdleTimeout());
if (rep.getSsoSessionMaxLifespan() != null) Assert.assertEquals(rep.getSsoSessionMaxLifespan(), storedRealm.getSsoSessionMaxLifespan());
if (rep.getRequiredCredentials() != null) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
index f7045539c6..469030821e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
@@ -218,7 +218,7 @@ public class CustomFlowTest {
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
index 97250c4f10..a5f23883cb 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
@@ -7,6 +7,7 @@ import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.GroupResource;
import org.keycloak.admin.client.resource.RealmResource;
@@ -256,7 +257,7 @@ public class GroupTest {
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index b2d14b9019..99e5b95649 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -74,6 +74,7 @@ public class ImportTest extends AbstractModelTest {
public static void assertDataImportedInRealm(KeycloakSession session, RealmModel realm) {
Assert.assertTrue(realm.isVerifyEmail());
Assert.assertEquals(3600000, realm.getOfflineSessionIdleTimeout());
+ Assert.assertEquals(1500, realm.getAccessTokenLifespanForImplicitFlow());
List creds = realm.getRequiredCredentials();
Assert.assertEquals(1, creds.size());
@@ -327,6 +328,13 @@ public class ImportTest extends AbstractModelTest {
Assert.assertFalse(otherAppAdminConsent.isRoleGranted(application.getRole("app-admin")));
Assert.assertTrue(otherAppAdminConsent.isProtocolMapperGranted(gssCredentialMapper));
+ Assert.assertTrue(application.isStandardFlowEnabled());
+ Assert.assertTrue(application.isImplicitFlowEnabled());
+ Assert.assertTrue(application.isDirectAccessGrantsEnabled());
+ Assert.assertFalse(otherApp.isStandardFlowEnabled());
+ Assert.assertFalse(otherApp.isImplicitFlowEnabled());
+ Assert.assertFalse(otherApp.isDirectAccessGrantsEnabled());
+
// Test service accounts
Assert.assertFalse(application.isServiceAccountsEnabled());
Assert.assertTrue(otherApp.isServiceAccountsEnabled());
@@ -344,6 +352,7 @@ public class ImportTest extends AbstractModelTest {
RealmModel realm =manager.importRealm(rep);
Assert.assertEquals(600, realm.getAccessCodeLifespanUserAction());
+ Assert.assertEquals(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT, realm.getAccessTokenLifespanForImplicitFlow());
Assert.assertEquals(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT, realm.getOfflineSessionIdleTimeout());
verifyRequiredCredentials(realm.getRequiredCredentials(), "password");
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index d3a47e08a9..5d5e0edf00 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -30,6 +30,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
@@ -159,13 +160,22 @@ public class AuthorizationCodeTest {
assertCode(codeId, response.getCode());
}
+ @Test
+ public void authorizationRequestImplicitFlowDisabled() throws IOException {
+ UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
+ b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token id_token");
+ driver.navigate().to(b.build().toURL());
+ assertEquals("Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.", errorPage.getError());
+ events.expectLogin().error(Errors.NOT_ALLOWED).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token id_token").assertEvent();
+ }
+
@Test
public void authorizationRequestInvalidResponseType() throws IOException {
UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token");
driver.navigate().to(b.build().toURL());
assertEquals("Invalid parameter: response_type", errorPage.getError());
- events.expectLogin().error(Errors.INVALID_REQUEST).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token").assertEvent();
+ events.expectLogin().error(Errors.INVALID_REQUEST).client((String) null).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token").assertEvent();
}
private void assertCode(String expectedCodeId, String actualCode) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
index 537a297c34..898066aad4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
@@ -189,7 +189,7 @@ public class ClientAuthSignedJWTTest {
events.expectLogin()
.client("client2")
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, "test-user@localhost")
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
index 6c205adbd2..d333f862c1 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
@@ -40,6 +40,7 @@ import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import java.io.IOException;
+import java.net.URL;
/**
* @author Viliam Rockai
@@ -174,7 +175,10 @@ public class OAuthRedirectUriTest {
OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
Assert.assertNotNull(response.getCode());
- Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?code="));
+ URL url = new URL(driver.getCurrentUrl());
+ Assert.assertTrue(url.toString().startsWith("http://localhost:8081/app"));
+ Assert.assertTrue(url.getQuery().contains("code="));
+ Assert.assertTrue(url.getQuery().contains("state="));
}
@Test
@@ -192,7 +196,11 @@ public class OAuthRedirectUriTest {
OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
Assert.assertNotNull(response.getCode());
- Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?key=value&code="));
+ URL url = new URL(driver.getCurrentUrl());
+ Assert.assertTrue(url.toString().startsWith("http://localhost:8081/app"));
+ Assert.assertTrue(url.getQuery().contains("key=value"));
+ Assert.assertTrue(url.getQuery().contains("state="));
+ Assert.assertTrue(url.getQuery().contains("code="));
}
@Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index 884d9f0efc..a4b2855b0b 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -319,7 +319,7 @@ public class OfflineTokenTest {
.client("offline-client")
.user(userId)
.session(token.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, token.getId())
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
@@ -361,7 +361,7 @@ public class OfflineTokenTest {
.client("offline-client")
.user(userId)
.session(token.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, token.getId())
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index 74e3d38261..b64a683ed9 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -6,6 +6,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@@ -93,7 +94,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)
@@ -129,7 +130,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
events.expectLogin()
.client("resource-owner")
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.removeDetail(Details.CODE_ID)
@@ -285,7 +286,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
events.expectLogin()
.client("resource-owner")
.session((String) null)
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)
.removeDetail(Details.CONSENT)
@@ -307,7 +308,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
.client("resource-owner")
.user((String) null)
.session((String) null)
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.USERNAME, "invalid")
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index 68ec4d3327..af86592664 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -2,6 +2,7 @@
"realm": "test-realm",
"enabled": true,
"accessTokenLifespan": 6000,
+ "accessTokenLifespanForImplicitFlow": 1500,
"accessCodeLifespan": 30,
"accessCodeLifespanUserAction": 600,
"offlineSessionIdleTimeout": 3600000,
@@ -154,6 +155,7 @@
"clientId": "Application",
"name": "Applicationn",
"enabled": true,
+ "implicitFlowEnabled": true,
"nodeReRegistrationTimeout": 50,
"registeredNodes": {
"node1": 10,
@@ -164,6 +166,8 @@
"clientId": "OtherApp",
"name": "Other Application",
"enabled": true,
+ "standardFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"clientAuthenticatorType": "client-jwt",
"protocolMappers" : [