keycloak-aplcache
Changes
examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java 11(+4 -7)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html 2(+1 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html 4(+2 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html 12(+6 -6)
Details
diff --git a/core/src/main/java/org/keycloak/representations/RefreshToken.java b/core/src/main/java/org/keycloak/representations/RefreshToken.java
index ff1ce68..39c7c46 100755
--- a/core/src/main/java/org/keycloak/representations/RefreshToken.java
+++ b/core/src/main/java/org/keycloak/representations/RefreshToken.java
@@ -1,7 +1,6 @@
package org.keycloak.representations;
-import org.codehaus.jackson.annotate.JsonProperty;
-import org.keycloak.util.RefreshTokenUtil;
+import org.keycloak.util.TokenUtil;
import java.util.HashMap;
import java.util.Map;
@@ -13,7 +12,7 @@ import java.util.Map;
public class RefreshToken extends AccessToken {
private RefreshToken() {
- type(RefreshTokenUtil.TOKEN_TYPE_REFRESH);
+ type(TokenUtil.TOKEN_TYPE_REFRESH);
}
/**
diff --git a/core/src/main/java/org/keycloak/RSATokenVerifier.java b/core/src/main/java/org/keycloak/RSATokenVerifier.java
index 9bd544d..80a4cd6 100755
--- a/core/src/main/java/org/keycloak/RSATokenVerifier.java
+++ b/core/src/main/java/org/keycloak/RSATokenVerifier.java
@@ -3,6 +3,7 @@ package org.keycloak;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessToken;
+import org.keycloak.util.TokenUtil;
import java.io.IOException;
import java.security.PublicKey;
@@ -13,10 +14,10 @@ import java.security.PublicKey;
*/
public class RSATokenVerifier {
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl) throws VerificationException {
- return verifyToken(tokenString, realmKey, realmUrl, true);
+ return verifyToken(tokenString, realmKey, realmUrl, true, true);
}
- public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive) throws VerificationException {
+ public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
JWSInput input = null;
try {
input = new JWSInput(tokenString);
@@ -42,6 +43,13 @@ public class RSATokenVerifier {
throw new VerificationException("Token audience doesn't match domain. Token issuer is " + token.getIssuer() + ", but URL from configuration is " + realmUrl);
}
+
+ if (checkTokenType) {
+ String type = token.getType();
+ if (type == null || !type.equalsIgnoreCase(TokenUtil.TOKEN_TYPE_BEARER)) {
+ throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + type + "'");
+ }
+ }
if (checkActive && !token.isActive()) {
throw new VerificationException("Token is not active.");
}
diff --git a/core/src/test/java/org/keycloak/RSAVerifierTest.java b/core/src/test/java/org/keycloak/RSAVerifierTest.java
index e1eb846..acdbb32 100755
--- a/core/src/test/java/org/keycloak/RSAVerifierTest.java
+++ b/core/src/test/java/org/keycloak/RSAVerifierTest.java
@@ -10,6 +10,7 @@ import org.junit.Test;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.AccessToken;
import org.keycloak.util.Time;
+import org.keycloak.util.TokenUtil;
import javax.security.auth.x500.X500Principal;
import java.io.IOException;
@@ -71,7 +72,8 @@ public class RSAVerifierTest {
public void initTest() {
token = new AccessToken();
- token.subject("CN=Client")
+ token.type(TokenUtil.TOKEN_TYPE_BEARER)
+ .subject("CN=Client")
.issuer("http://localhost:8080/auth/realm")
.addAccess("service").addRole("admin");
}
diff --git a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
index b233574..354a778 100644
--- a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
+++ b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
@@ -3,7 +3,6 @@ package org.keycloak.example;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import javax.security.cert.X509Certificate;
@@ -22,12 +21,10 @@ import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.ServerRequest;
-import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.JsonSerialization;
-import org.keycloak.util.KeycloakUriBuilder;
-import org.keycloak.util.RefreshTokenUtil;
+import org.keycloak.util.TokenUtil;
import org.keycloak.util.StreamUtil;
import org.keycloak.util.Time;
import org.keycloak.util.UriUtils;
@@ -64,7 +61,7 @@ public class OfflineAccessPortalServlet extends HttpServlet {
refreshTokenInfo = "No token saved in database. Please login first";
savedTokenAvailable = false;
} else {
- RefreshToken refreshTokenDecoded = RefreshTokenUtil.getRefreshToken(refreshToken);
+ RefreshToken refreshTokenDecoded = TokenUtil.getRefreshToken(refreshToken);
String exp = (refreshTokenDecoded.getExpiration() == 0) ? "NEVER" : Time.toDate(refreshTokenDecoded.getExpiration()).toString();
refreshTokenInfo = String.format("<p>Type: %s</p><p>ID: %s</p><p>Expires: %s</p>", refreshTokenDecoded.getType(), refreshTokenDecoded.getId(), exp);
savedTokenAvailable = true;
@@ -89,8 +86,8 @@ public class OfflineAccessPortalServlet extends HttpServlet {
RefreshTokenDAO.saveToken(refreshToken);
- RefreshToken refreshTokenDecoded = RefreshTokenUtil.getRefreshToken(refreshToken);
- Boolean isOfflineToken = refreshTokenDecoded.getType().equals(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
+ RefreshToken refreshTokenDecoded = TokenUtil.getRefreshToken(refreshToken);
+ Boolean isOfflineToken = refreshTokenDecoded.getType().equals(TokenUtil.TOKEN_TYPE_OFFLINE);
req.setAttribute("isOfflineToken", isOfflineToken);
}
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index a965325..8af3ef8 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -1535,12 +1535,13 @@ module.directive('onoffswitchstring', function() {
});
/**
- * Directive for presenting an ON-OFF switch for checkbox.
+ * Directive for presenting an ON-OFF switch for checkbox. The directive expects the true-value or false-value to be string like 'true' or 'false', not boolean true/false.
* This directive provides some additional capabilities to the default onoffswitch such as:
*
- * - Specific scope to specify the value. Instead of just true or false.
+ * - Specific scope to specify the value. Instead of just 'true' or 'false' you can use any other values. For example: true-value="'foo'" false-value="'bar'" .
+ * But 'true'/'false' are defaults if true-value and false-value are not specified
*
- * Usage: <input ng-model="mmm" name="nnn" id="iii" onoffswitchvalue [on-text="ooo" off-text="fff"] />
+ * Usage: <input ng-model="mmm" name="nnn" id="iii" onoffswitchvalue [ true-value="'true'" false-value="'false'" on-text="ooo" off-text="fff"] />
*/
module.directive('onoffswitchvalue', function() {
return {
@@ -1549,7 +1550,8 @@ module.directive('onoffswitchvalue', function() {
scope: {
name: '@',
id: '@',
- value: '=',
+ trueValue: '@',
+ falseValue: '@',
ngModel: '=',
ngDisabled: '=',
kcOnText: '@onText',
@@ -1557,7 +1559,7 @@ module.directive('onoffswitchvalue', function() {
},
// TODO - The same code acts differently when put into the templateURL. Find why and move the code there.
//templateUrl: "templates/kc-switch.html",
- template: "<span><div class='onoffswitch' tabindex='0'><input type='checkbox' ng-true-value='{{value}}' ng-model='ngModel' ng-disabled='ngDisabled' class='onoffswitch-checkbox' name='{{name}}' id='{{id}}'><label for='{{id}}' class='onoffswitch-label'><span class='onoffswitch-inner'><span class='onoffswitch-active'>{{kcOnText}}</span><span class='onoffswitch-inactive'>{{kcOffText}}</span></span><span class='onoffswitch-switch'></span></label></div></span>",
+ template: "<span><div class='onoffswitch' tabindex='0'><input type='checkbox' ng-true-value='{{trueValue}}' ng-false-value='{{falseValue}}' ng-model='ngModel' ng-disabled='ngDisabled' class='onoffswitch-checkbox' name='{{name}}' id='{{id}}'><label for='{{id}}' class='onoffswitch-label'><span class='onoffswitch-inner'><span class='onoffswitch-active'>{{kcOnText}}</span><span class='onoffswitch-inactive'>{{kcOffText}}</span></span><span class='onoffswitch-switch'></span></label></div></span>",
compile: function(element, attrs) {
/*
We don't want to propagate basic attributes to the root element of directive. Id should be passed to the
@@ -1566,6 +1568,9 @@ module.directive('onoffswitchvalue', function() {
element.removeAttr('name');
element.removeAttr('id');
+ if (!attrs.trueValue) { attrs.trueValue = "'true'"; }
+ if (!attrs.falseValue) { attrs.falseValue = "'false'"; }
+
if (!attrs.onText) { attrs.onText = "ON"; }
if (!attrs.offText) { attrs.offText = "OFF"; }
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html
index c8afb02..a5df7b6 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-offline-sessions.html
@@ -14,7 +14,7 @@
<div class="col-md-6">
<input class="form-control" type="text" id="activeSessions" name="activeSessions" data-ng-model="count" ng-disabled="true">
</div>
- <kc-tooltip>Total number of active offline tokens for this client.</kc-tooltip>
+ <kc-tooltip>Total number of offline tokens for this client.</kc-tooltip>
</div>
</fieldset>
</form>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
index 4654a58..f372622 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
@@ -106,7 +106,7 @@
<div class="form-group">
<label class="col-sm-2 control-label" for="backchannelSupported">Backchannel Logout</label>
<div class="col-sm-4">
- <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" value="'true'" onoffswitchvalue />
+ <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" onoffswitchvalue />
</div>
<span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="Does the external IDP support backchannel logout?" class="fa fa-info-circle"></span>
</div>
@@ -166,7 +166,7 @@
<div class="form-group">
<label class="col-md-2 control-label" for="validateSignature">Validate Signatures</label>
<div class="col-md-6">
- <input ng-model="identityProvider.config.validateSignature" id="validateSignature" value="'true'" onoffswitchvalue />
+ <input ng-model="identityProvider.config.validateSignature" id="validateSignature" onoffswitchvalue />
</div>
<kc-tooltip>Enable/disable signature validation of external IDP signatures.</kc-tooltip>
</div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
index 5387826..478137c 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
@@ -100,7 +100,7 @@
<div class="form-group">
<label class="col-sm-2 control-label" for="backchannelSupported">Backchannel Logout</label>
<div class="col-sm-4">
- <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" value="'true'" onoffswitchvalue />
+ <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" onoffswitchvalue />
</div>
<span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="Does the external IDP support backchannel logout?" class="fa fa-info-circle"></span>
</div>
@@ -117,21 +117,21 @@
<div class="form-group">
<label class="col-md-2 control-label" for="postBindingResponse">HTTP-POST Binding Response</label>
<div class="col-md-6">
- <input ng-model="identityProvider.config.postBindingResponse" id="postBindingResponse" value="'true'" onoffswitchvalue />
+ <input ng-model="identityProvider.config.postBindingResponse" id="postBindingResponse" onoffswitchvalue />
</div>
<kc-tooltip>Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="postBindingAuthnRequest">HTTP-POST Binding for AuthnRequest</label>
<div class="col-md-6">
- <input ng-model="identityProvider.config.postBindingAuthnRequest" id="postBindingAuthnRequest" value="'true'" onoffswitchvalue />
+ <input ng-model="identityProvider.config.postBindingAuthnRequest" id="postBindingAuthnRequest" onoffswitchvalue />
</div>
<kc-tooltip>Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="wantAuthnRequestsSigned">Want AuthnRequests Signed</label>
<div class="col-md-6">
- <input ng-model="identityProvider.config.wantAuthnRequestsSigned" id="wantAuthnRequestsSigned" name="wantAuthnRequestsSigned" value="'true'" onoffswitchvalue />
+ <input ng-model="identityProvider.config.wantAuthnRequestsSigned" id="wantAuthnRequestsSigned" name="wantAuthnRequestsSigned" onoffswitchvalue />
</div>
<kc-tooltip> Indicates whether the identity provider expects signed a AuthnRequest.</kc-tooltip>
</div>
@@ -150,14 +150,14 @@
<div class="form-group">
<label class="col-md-2 control-label" for="forceAuthn">Force Authentication</label>
<div class="col-md-6">
- <input ng-model="identityProvider.config.forceAuthn" id="forceAuthn" name="forceAuthn" value="'true'" onoffswitchvalue />
+ <input ng-model="identityProvider.config.forceAuthn" id="forceAuthn" name="forceAuthn" onoffswitchvalue />
</div>
<kc-tooltip> Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="validateSignature">Validate Signature</label>
<div class="col-md-6">
- <input ng-model="identityProvider.config.validateSignature" id="validateSignature" value="'true'" onoffswitchvalue />
+ <input ng-model="identityProvider.config.validateSignature" id="validateSignature" onoffswitchvalue />
</div>
<kc-tooltip>Enable/disable signature validation of SAML responses.</kc-tooltip>
</div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
index f033aac..e16406d 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
@@ -29,7 +29,7 @@
<li ng-class="{active: path[4] == 'offline-access'}" data-ng-show="!client.bearerOnly">
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/offline-access">Offline Access</a>
<kc-tooltip>View offline sessions for this client. Allows you to see which users retrieve offline token and when they retrieve it.
- To revoke all tokens for the client, go to Revocation tab and set new not before value.
+ To revoke all tokens for the client, go to Revocation tab and set not before value to now.
</kc-tooltip>
</li>
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
index 73d58bc..40604a4 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
@@ -54,7 +54,7 @@ public class CookieTokenStore {
try {
// Skip check if token is active now. It's supposed to be done later by the caller
- AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false);
+ AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
IDToken idToken;
if (idTokenString != null && idTokenString.length() > 0) {
JWSInput input = new JWSInput(idTokenString);
diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationProvider.java b/model/api/src/main/java/org/keycloak/migration/MigrationProvider.java
index 7ba3bb7..9c8c678 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationProvider.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationProvider.java
@@ -1,5 +1,6 @@
package org.keycloak.migration;
+import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.provider.Provider;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@@ -18,4 +19,6 @@ public interface MigrationProvider extends Provider {
*/
List<ProtocolMapperRepresentation> getMappersForClaimMask(Long claimMask);
+ List<ProtocolMapperModel> getBuiltinMappers(String protocol);
+
}
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
index ecb5482..6712c08 100644
--- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
@@ -2,13 +2,10 @@ package org.keycloak.migration.migrators;
import java.util.List;
+import org.keycloak.Config;
+import org.keycloak.migration.MigrationProvider;
import org.keycloak.migration.ModelVersion;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.Constants;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserModel;
+import org.keycloak.models.*;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
@@ -19,6 +16,20 @@ public class MigrateTo1_6_0 {
public static final ModelVersion VERSION = new ModelVersion("1.6.0");
public void migrate(KeycloakSession session) {
+ MigrationProvider provider = session.getProvider(MigrationProvider.class);
+
+ List<ProtocolMapperModel> builtinMappers = provider.getBuiltinMappers("openid-connect");
+ ProtocolMapperModel localeMapper = null;
+ for (ProtocolMapperModel m : builtinMappers) {
+ if (m.getName().equals("locale")) {
+ localeMapper = m;
+ }
+ }
+
+ if (localeMapper == null) {
+ throw new RuntimeException("Can't find default locale mapper");
+ }
+
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {
if (realm.getRole(Constants.OFFLINE_ACCESS_ROLE) == null) {
@@ -39,8 +50,12 @@ public class MigrateTo1_6_0 {
user.grantRole(role);
}
}
- }
+ ClientModel adminConsoleClient = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
+ if (adminConsoleClient != null) {
+ adminConsoleClient.addProtocolMapper(localeMapper);
+ }
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
index 71c30e4..096252a 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
@@ -119,7 +119,7 @@ public class UserInfoEndpoint {
AccessToken token = null;
try {
- token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), true);
+ token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), true, true);
} catch (VerificationException e) {
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Token invalid: " + e.getMessage(), Status.FORBIDDEN);
}
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 ee8e9e8..8716d73 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -33,7 +33,7 @@ import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.offline.OfflineTokenUtils;
-import org.keycloak.util.RefreshTokenUtil;
+import org.keycloak.util.TokenUtil;
import org.keycloak.util.Time;
import javax.ws.rs.core.HttpHeaders;
@@ -96,7 +96,7 @@ public class TokenManager {
UserSessionModel userSession = null;
ClientSessionModel clientSession = null;
- if (RefreshTokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
+ if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
clientSession = OfflineTokenUtils.findOfflineClientSession(session, realm, user, oldToken.getClientSession(), oldToken.getSessionState());
if (clientSession != null) {
@@ -168,12 +168,12 @@ public class TokenManager {
.generateIDToken();
// Don't generate refresh token again if refresh was triggered with offline token
- if (!refreshToken.getType().equals(RefreshTokenUtil.TOKEN_TYPE_OFFLINE)) {
+ if (!refreshToken.getType().equals(TokenUtil.TOKEN_TYPE_OFFLINE)) {
responseBuilder.generateRefreshToken();
}
AccessTokenResponse res = responseBuilder.build();
- return new RefreshResult(res, RefreshTokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType()));
+ return new RefreshResult(res, TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType()));
}
public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
@@ -385,6 +385,7 @@ public class TokenManager {
AccessToken token = new AccessToken();
if (clientSession != null) token.clientSession(clientSession.getId());
token.id(KeycloakModelUtils.generateId());
+ token.type(TokenUtil.TOKEN_TYPE_BEARER);
token.subject(user.getId());
token.audience(client.getClientId());
token.issuedNow();
@@ -487,7 +488,7 @@ public class TokenManager {
}
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
- boolean offlineTokenRequested = RefreshTokenUtil.isOfflineTokenRequested(scopeParam);
+ boolean offlineTokenRequested = TokenUtil.isOfflineTokenRequested(scopeParam);
if (offlineTokenRequested) {
if (!OfflineTokenUtils.isOfflineTokenAllowed(realm, clientSession)) {
event.error(Errors.NOT_ALLOWED);
@@ -495,7 +496,7 @@ public class TokenManager {
}
refreshToken = new RefreshToken(accessToken);
- refreshToken.type(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
+ refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE);
OfflineTokenUtils.persistOfflineSession(session, realm, clientSession, userSession);
} else {
refreshToken = new RefreshToken(accessToken);
@@ -512,6 +513,7 @@ public class TokenManager {
}
idToken = new IDToken();
idToken.id(KeycloakModelUtils.generateId());
+ idToken.type(TokenUtil.TOKEN_TYPE_ID);
idToken.subject(accessToken.getSubject());
idToken.audience(client.getClientId());
idToken.issuedNow();
diff --git a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
index 457388b..0156fb6 100755
--- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
@@ -42,7 +42,7 @@ public class AppAuthManager extends AuthenticationManager {
public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
String tokenString = extractAuthorizationHeaderToken(headers);
if (tokenString == null) return null;
- AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, tokenString, headers);
+ AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, true, tokenString, headers);
return authResult;
}
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 5f1f9bf..73d274e 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -68,6 +68,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.keycloak.freemarker.LocaleHelper;
+import org.keycloak.util.TokenUtil;
/**
* Stateless object that manages authentication
@@ -115,7 +116,7 @@ public class AuthenticationManager {
Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
if (cookie == null) return;
String tokenString = cookie.getValue();
- AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), false);
+ AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), false, false);
UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState());
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return;
expireIdentityCookie(realm, uriInfo, connection);
@@ -383,7 +384,7 @@ public class AuthenticationManager {
}
String tokenString = cookie.getValue();
- AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, tokenString, session.getContext().getRequestHeaders());
+ AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, tokenString, session.getContext().getRequestHeaders());
if (authResult == null) {
expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
return null;
@@ -601,9 +602,10 @@ public class AuthenticationManager {
}
- protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString, HttpHeaders headers) {
+ protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
+ String tokenString, HttpHeaders headers) {
try {
- AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), checkActive);
+ AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), checkActive, checkTokenType);
if (checkActive) {
if (!token.isActive() || token.getIssuedAt() < realm.getNotBefore()) {
logger.debug("identity cookie expired");
diff --git a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java
index 22623f8..ad46afe 100644
--- a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java
@@ -54,6 +54,12 @@ public class DefaultMigrationProvider implements MigrationProvider {
}
@Override
+ public List<ProtocolMapperModel> getBuiltinMappers(String protocol) {
+ LoginProtocolFactory providerFactory = (LoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, protocol);
+ return providerFactory.getBuiltinMappers();
+ }
+
+ @Override
public void close() {
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
index 9fa1a47..0b32146 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
@@ -2,6 +2,7 @@ package org.keycloak.testsuite.adapter;
import org.junit.Assert;
import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -44,6 +45,15 @@ public class CustomerServlet extends HttpServlet {
Response response = target.request().get();
Assert.assertEquals(401, response.getStatus());
response.close();
+
+ // Assert not possible to authenticate with refresh token
+ RefreshableKeycloakSecurityContext refreshableContext = (RefreshableKeycloakSecurityContext) context;
+ response = target.request()
+ .header(HttpHeaders.AUTHORIZATION, "Bearer " + refreshableContext.getRefreshToken())
+ .get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
String html = target.request()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + context.getTokenString())
.get(String.class);
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 77b6d19..cda71ac 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -9,7 +9,6 @@ import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
import org.keycloak.Config;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
-import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.constants.ServiceAccountConstants;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.Details;
@@ -24,13 +23,10 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
-import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.KeycloakRule;
-import org.keycloak.util.RefreshTokenUtil;
+import org.keycloak.util.TokenUtil;
import java.util.HashMap;
import java.util.HashSet;
@@ -157,7 +153,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
.detail(Details.CODE_ID, codeId)
.detail(Details.TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_ID, isUUID())
- .detail(Details.REFRESH_TOKEN_TYPE, RefreshTokenUtil.TOKEN_TYPE_REFRESH)
+ .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.session(sessionId);
}
@@ -166,7 +162,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
return expect(EventType.REFRESH_TOKEN)
.detail(Details.TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_ID, refreshTokenId)
- .detail(Details.REFRESH_TOKEN_TYPE, RefreshTokenUtil.TOKEN_TYPE_REFRESH)
+ .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
.detail(Details.UPDATED_REFRESH_TOKEN_ID, isUUID())
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.session(sessionId);
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 8eab300..a8c3d9c 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
@@ -43,7 +43,7 @@ import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.keycloak.util.JsonSerialization;
-import org.keycloak.util.RefreshTokenUtil;
+import org.keycloak.util.TokenUtil;
import org.keycloak.util.Time;
import org.keycloak.util.UriUtils;
import org.openqa.selenium.WebDriver;
@@ -221,10 +221,10 @@ public class OfflineTokenTest {
events.expectCodeToToken(codeId, sessionId)
.client("offline-client")
- .detail(Details.REFRESH_TOKEN_TYPE, RefreshTokenUtil.TOKEN_TYPE_OFFLINE)
+ .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
.assertEvent();
- Assert.assertEquals(RefreshTokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
+ Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
Assert.assertEquals(0, offlineToken.getExpiration());
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, sessionId, userId);
@@ -278,7 +278,7 @@ public class OfflineTokenTest {
.client("offline-client")
.user(userId)
.removeDetail(Details.UPDATED_REFRESH_TOKEN_ID)
- .detail(Details.REFRESH_TOKEN_TYPE, RefreshTokenUtil.TOKEN_TYPE_OFFLINE)
+ .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
.assertEvent();
Assert.assertNotEquals(oldToken.getId(), refreshEvent.getDetails().get(Details.TOKEN_ID));
@@ -302,14 +302,14 @@ public class OfflineTokenTest {
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.TOKEN_ID, token.getId())
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
- .detail(Details.REFRESH_TOKEN_TYPE, RefreshTokenUtil.TOKEN_TYPE_OFFLINE)
+ .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
.detail(Details.USERNAME, "test-user@localhost")
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)
.removeDetail(Details.CONSENT)
.assertEvent();
- Assert.assertEquals(RefreshTokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
+ Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
Assert.assertEquals(0, offlineToken.getExpiration());
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
@@ -333,11 +333,11 @@ public class OfflineTokenTest {
.session(token.getSessionState())
.detail(Details.TOKEN_ID, token.getId())
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
- .detail(Details.REFRESH_TOKEN_TYPE, RefreshTokenUtil.TOKEN_TYPE_OFFLINE)
+ .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
.detail(Details.USERNAME, ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "offline-client")
.assertEvent();
- Assert.assertEquals(RefreshTokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
+ Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
Assert.assertEquals(0, offlineToken.getExpiration());
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
@@ -356,7 +356,7 @@ public class OfflineTokenTest {
.session(token2.getSessionState())
.detail(Details.TOKEN_ID, token2.getId())
.detail(Details.REFRESH_TOKEN_ID, offlineToken2.getId())
- .detail(Details.REFRESH_TOKEN_TYPE, RefreshTokenUtil.TOKEN_TYPE_OFFLINE)
+ .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
.detail(Details.USERNAME, ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "offline-client")
.assertEvent();
@@ -371,7 +371,7 @@ public class OfflineTokenTest {
.user(serviceAccountUserId)
.removeDetail(Details.UPDATED_REFRESH_TOKEN_ID)
.removeDetail(Details.TOKEN_ID)
- .detail(Details.REFRESH_TOKEN_TYPE, RefreshTokenUtil.TOKEN_TYPE_OFFLINE)
+ .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
.assertEvent();
// Refresh with new offline token is ok
@@ -389,7 +389,7 @@ public class OfflineTokenTest {
loginPage.login("test-user@localhost", "password");
Assert.assertTrue(driver.getCurrentUrl().startsWith(offlineClientAppUri));
- Assert.assertEquals(OfflineTokenServlet.tokenInfo.refreshToken.getType(), RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
+ Assert.assertEquals(OfflineTokenServlet.tokenInfo.refreshToken.getType(), TokenUtil.TOKEN_TYPE_OFFLINE);
Assert.assertEquals(OfflineTokenServlet.tokenInfo.refreshToken.getExpiration(), 0);
String accessTokenId = OfflineTokenServlet.tokenInfo.accessToken.getId();
@@ -422,7 +422,7 @@ public class OfflineTokenTest {
loginPage.login("test-user@localhost", "password");
Assert.assertTrue(driver.getCurrentUrl().startsWith(offlineClientAppUri));
- Assert.assertEquals(OfflineTokenServlet.tokenInfo.refreshToken.getType(), RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
+ Assert.assertEquals(OfflineTokenServlet.tokenInfo.refreshToken.getType(), TokenUtil.TOKEN_TYPE_OFFLINE);
// Assert refresh works with increased time
Time.setOffset(9999);
@@ -480,7 +480,7 @@ public class OfflineTokenTest {
oauthGrantPage.accept();
Assert.assertTrue(driver.getCurrentUrl().startsWith(offlineClientAppUri));
- Assert.assertEquals(OfflineTokenServlet.tokenInfo.refreshToken.getType(), RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
+ Assert.assertEquals(OfflineTokenServlet.tokenInfo.refreshToken.getType(), TokenUtil.TOKEN_TYPE_OFFLINE);
accountAppPage.open();
AccountApplicationsPage.AppEntry offlineClient = accountAppPage.getApplications().get("offline-client");