Merge pull request #1670 from mposolda/master

KEYCLOAK-1856 KEYCLOAK-1860 Fix onoffswitchvalue directive
This commit is contained in:
Marek Posolda 2015-10-02 11:58:04 +02:00
commit af307c695a
18 changed files with 89 additions and 64 deletions

View file

@ -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.");
}

View file

@ -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);
}
/**

View file

@ -9,11 +9,15 @@ import org.keycloak.representations.RefreshToken;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RefreshTokenUtil {
public class TokenUtil {
public static final String TOKEN_TYPE_REFRESH = "REFRESH";
public static final String TOKEN_TYPE_BEARER = "Bearer";
public static final String TOKEN_TYPE_OFFLINE = "OFFLINE";
public static final String TOKEN_TYPE_ID = "ID";
public static final String TOKEN_TYPE_REFRESH = "Refresh";
public static final String TOKEN_TYPE_OFFLINE = "Offline";
public static boolean isOfflineTokenRequested(String scopeParam) {
if (scopeParam == null) {

View file

@ -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");
}

View file

@ -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);
}

View file

@ -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"; }

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -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();

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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");

View file

@ -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);

View file

@ -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);

View file

@ -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");