Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
72256fd6ac
21 changed files with 120 additions and 71 deletions
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"; }
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -53,6 +53,12 @@ public class DefaultMigrationProvider implements MigrationProvider {
|
|||
return new ArrayList<ProtocolMapperRepresentation>(allMappers.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProtocolMapperModel> getBuiltinMappers(String protocol) {
|
||||
LoginProtocolFactory providerFactory = (LoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, protocol);
|
||||
return providerFactory.getBuiltinMappers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue