Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/main/java/org/keycloak/OAuthErrorException.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class OAuthErrorException extends Exception {
public static final String TEMPORARILY_UNAVAILABLE = "temporarily_unavailable";
public static final String INVALID_REQUEST_URI = "invalid_request_uri";
public static final String INVALID_REQUEST_OBJECT = "invalid_request_object";
public static final String INVALID_TARGET ="invalid_target";

// OpenID Connect 1
public static final String INTERACTION_REQUIRED = "interaction_required";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public class RealmRepresentation {
protected Integer oauth2DevicePollingInterval;
protected Boolean enabled;
protected String sslRequired;
protected String defaultAudValueForAccessToken;
@Deprecated
protected Boolean passwordCredentialGrantAllowed;
protected Boolean registrationAllowed;
Expand Down Expand Up @@ -519,6 +520,14 @@ public void setActionTokenGeneratedByUserLifespan(Integer actionTokenGeneratedBy
this.actionTokenGeneratedByUserLifespan = actionTokenGeneratedByUserLifespan;
}

public String getDefaultAudValueForAccessToken() {
return defaultAudValueForAccessToken;
}

public void setDefaultAudValueForAccessToken(String defaultAudValueForAccessToken) {
this.defaultAudValueForAccessToken = defaultAudValueForAccessToken;
}

@Deprecated
public List<String> getDefaultRoles() {
return defaultRoles;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,18 @@ public void setActionTokenGeneratedByUserLifespan(String actionTokenId, Integer
}
}

@Override
public String getDefaultAudValueForAccessToken() {
if (isUpdated()) return updated.getDefaultAudValueForAccessToken();
return cached.getDefaultAudValueForAccessToken();
}

@Override
public void setDefaultAudValueForAccessToken(String defaultAudValueForAccessToken) {
getDelegateForUpdate();
updated.setDefaultAudValueForAccessToken(defaultAudValueForAccessToken);
}

@Override
public Stream<RequiredCredentialModel> getRequiredCredentialsStream() {
if (isUpdated()) return updated.getRequiredCredentialsStream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public class CachedRealm extends AbstractExtendableRevisioned {
protected int accessCodeLifespan;
protected int accessCodeLifespanUserAction;
protected int accessCodeLifespanLogin;
protected String defaultAudValueForAccessToken;
protected LazyLoader<RealmModel, OAuth2DeviceConfig> deviceConfig;
protected LazyLoader<RealmModel, CibaConfig> cibaConfig;
protected LazyLoader<RealmModel, ParConfig> parConfig;
Expand Down Expand Up @@ -227,6 +228,7 @@ public CachedRealm(Long revision, RealmModel model) {
accessCodeLifespanLogin = model.getAccessCodeLifespanLogin();
actionTokenGeneratedByAdminLifespan = model.getActionTokenGeneratedByAdminLifespan();
actionTokenGeneratedByUserLifespan = model.getActionTokenGeneratedByUserLifespan();
defaultAudValueForAccessToken = model.getDefaultAudValueForAccessToken();
notBefore = model.getNotBefore();
passwordPolicy = model.getPasswordPolicy();
otpPolicy = model.getOTPPolicy();
Expand Down Expand Up @@ -499,6 +501,10 @@ public int getAccessCodeLifespanLogin() {
return accessCodeLifespanLogin;
}

public String getDefaultAudValueForAccessToken() {
return defaultAudValueForAccessToken;
}

public OAuth2DeviceConfig getOAuth2DeviceConfig(Supplier<RealmModel> modelSupplier) {
return deviceConfig.get(modelSupplier);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,16 @@ public void setFailureFactor(int failureFactor) {
setAttribute("failureFactor", failureFactor);
}

@Override
public String getDefaultAudValueForAccessToken() {
return getAttribute(RealmAttributes.DEFAULT_AUD_VALUE_FOR_ACCESS_TOKEN);
}

@Override
public void setDefaultAudValueForAccessToken(String defaultAudValueForAccessToken) {
setAttribute(RealmAttributes.DEFAULT_AUD_VALUE_FOR_ACCESS_TOKEN, defaultAudValueForAccessToken);
}

@Override
public boolean isVerifyEmail() {
return realm.isVerifyEmail();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public interface RealmAttributes {
String CLIENT_SESSION_MAX_LIFESPAN = "clientSessionMaxLifespan";
String CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT = "clientOfflineSessionIdleTimeout";
String CLIENT_OFFLINE_SESSION_MAX_LIFESPAN = "clientOfflineSessionMaxLifespan";
String DEFAULT_AUD_VALUE_FOR_ACCESS_TOKEN = "defaultAudValueForAccessToken";
String WEBAUTHN_POLICY_RP_ENTITY_NAME = "webAuthnPolicyRpEntityName";
String WEBAUTHN_POLICY_SIGNATURE_ALGORITHMS = "webAuthnPolicySignatureAlgorithms";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ public void importRealm(RealmRepresentation rep, RealmModel newRealm, boolean sk
if (rep.getActionTokenGeneratedByUserLifespan() != null)
newRealm.setActionTokenGeneratedByUserLifespan(rep.getActionTokenGeneratedByUserLifespan());
else newRealm.setActionTokenGeneratedByUserLifespan(newRealm.getAccessCodeLifespanUserAction());
if (rep.getDefaultAudValueForAccessToken() != null)
newRealm.setDefaultAudValueForAccessToken(rep.getDefaultAudValueForAccessToken());

// OAuth 2.0 Device Authorization Grant
OAuth2DeviceConfig deviceConfig = newRealm.getOAuth2DeviceConfig();
Expand Down Expand Up @@ -726,6 +728,9 @@ public void updateRealm(RealmRepresentation rep, RealmModel realm) {
realm.setClientOfflineSessionIdleTimeout(rep.getClientOfflineSessionIdleTimeout());
if (rep.getClientOfflineSessionMaxLifespan() != null)
realm.setClientOfflineSessionMaxLifespan(rep.getClientOfflineSessionMaxLifespan());
if (rep.getDefaultAudValueForAccessToken() != null)
realm.setDefaultAudValueForAccessToken(rep.getDefaultAudValueForAccessToken
());
if (rep.getRequiredCredentials() != null) {
realm.updateRequiredCredentials(rep.getRequiredCredentials());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ public void importRealm(RealmRepresentation rep, RealmModel newRealm, boolean sk
newRealm.setActionTokenGeneratedByUserLifespan(rep.getActionTokenGeneratedByUserLifespan());
else newRealm.setActionTokenGeneratedByUserLifespan(newRealm.getAccessCodeLifespanUserAction());

if (rep.getDefaultAudValueForAccessToken() != null)
newRealm.setDefaultAudValueForAccessToken(rep.getDefaultAudValueForAccessToken());

// OAuth 2.0 Device Authorization Grant
OAuth2DeviceConfig deviceConfig = newRealm.getOAuth2DeviceConfig();

Expand Down Expand Up @@ -680,6 +683,8 @@ public void updateRealm(RealmRepresentation rep, RealmModel realm) {
realm.setActionTokenGeneratedByAdminLifespan(rep.getActionTokenGeneratedByAdminLifespan());
if (rep.getActionTokenGeneratedByUserLifespan() != null)
realm.setActionTokenGeneratedByUserLifespan(rep.getActionTokenGeneratedByUserLifespan());
if (rep.getDefaultAudValueForAccessToken() != null)
realm.setDefaultAudValueForAccessToken(rep.getDefaultAudValueForAccessToken());

OAuth2DeviceConfig deviceConfig = realm.getOAuth2DeviceConfig();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class MapRealmAdapter extends AbstractRealmModel<MapRealmEntity> implemen
private static final String MINIMUM_QUICK_LOGIN_WAIT_SECONDS = "minimumQuickLoginWaitSeconds";
private static final String MAX_DELTA_SECONDS = "maxDeltaTimeSeconds";
private static final String FAILURE_FACTOR = "failureFactor";
private static final String DEFAULT_AUD_VALUE_FOR_ACCESS_TOKEN = "defaultAudValueForAccessToken";

private PasswordPolicy passwordPolicy;

Expand Down Expand Up @@ -455,6 +456,16 @@ public int getAccessCodeLifespanLogin() {
return i == null ? 0 : i;
}

@Override
public String getDefaultAudValueForAccessToken() {
return getAttribute(DEFAULT_AUD_VALUE_FOR_ACCESS_TOKEN);
}

@Override
public void setDefaultAudValueForAccessToken(String defaultAudValueForAccessToken) {
setAttribute(DEFAULT_AUD_VALUE_FOR_ACCESS_TOKEN, defaultAudValueForAccessToken);
}

@Override
public void setAccessCodeLifespanLogin(int seconds) {
entity.setAccessCodeLifespanLogin(seconds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public interface Errors {
String INVALID_CODE = "invalid_code";
String INVALID_TOKEN = "invalid_token";
String INVALID_TOKEN_TYPE = "invalid_token_type";
String INVALID_TARGET ="invalid_target";
String INVALID_SAML_RESPONSE = "invalid_saml_response";
String INVALID_SAML_AUTHN_REQUEST = "invalid_authn_request";
String INVALID_SAML_LOGOUT_REQUEST = "invalid_logout_request";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ public static RealmRepresentation toRepresentation(KeycloakSession session, Real
rep.setAccessCodeLifespanLogin(realm.getAccessCodeLifespanLogin());
rep.setActionTokenGeneratedByAdminLifespan(realm.getActionTokenGeneratedByAdminLifespan());
rep.setActionTokenGeneratedByUserLifespan(realm.getActionTokenGeneratedByUserLifespan());
rep.setDefaultAudValueForAccessToken(realm.getDefaultAudValueForAccessToken());
rep.setOAuth2DeviceCodeLifespan(realm.getOAuth2DeviceConfig().getLifespan());
rep.setOAuth2DevicePollingInterval(realm.getOAuth2DeviceConfig().getPoolingInterval());
rep.setSmtpServer(new HashMap<>(realm.getSmtpConfig()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,27 @@ public boolean validateUri(URI uri, String inputHint, ValidationContext context,
return valid;
}

public static void validateUri(String url, Set<String> blockedSchemes, boolean allowFragment, boolean requireValidUrl)
throws MalformedURLException, URISyntaxException {
URI uri = new URI(url);

ValidationContext context = new ValidationContext();
if (uri.getScheme() != null && blockedSchemes.contains(uri.getScheme())) {
throw new MalformedURLException("Not valid URI scheme");
}

if (!allowFragment && uri.getFragment() != null) {
throw new MalformedURLException("URI consists fragment");
}

// Don't check if URL is valid if there are other problems with it; otherwise it could lead to duplicate errors.
// This cannot be moved higher because it acts on differently based on environment (e.g. sometimes it checks
// scheme, sometimes it doesn't).
if (requireValidUrl) {
URL ignored = uri.toURL(); // throws an exception
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: For validation, you can just call uri.toURL() without creating any variable?

}
}

@Override
public String getHelpText() {
return "Uri Validator";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ default Boolean getAttribute(String name, Boolean defaultValue) {

void setAccessCodeLifespanUserAction(int seconds);

String getDefaultAudValueForAccessToken();

void setDefaultAudValueForAccessToken(String defaultAudValueForAccessToken);

OAuth2DeviceConfig getOAuth2DeviceConfig();

CibaConfig getCibaPolicy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public TokenValidation(UserModel user, UserSessionModel userSession, ClientSessi
}

public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm,
RefreshToken oldToken, HttpHeaders headers) throws OAuthErrorException {
RefreshToken oldToken, HttpHeaders headers, List<String> resourceList) throws OAuthErrorException {
UserSessionModel userSession = null;
boolean offline = TokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType());

Expand Down Expand Up @@ -219,7 +219,7 @@ public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, C
clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, oldToken.getNonce());

// recreate token.
AccessToken newToken = createClientAccessToken(session, realm, client, user, userSession, clientSessionCtx);
AccessToken newToken = createClientAccessToken(session, realm, client, user, userSession, clientSessionCtx, resourceList);

return new TokenValidation(user, userSession, clientSessionCtx, newToken);
}
Expand Down Expand Up @@ -359,14 +359,14 @@ public static UserModel lookupUserFromStatelessToken(KeycloakSession session, Re


public AccessTokenResponseBuilder refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient,
String encodedRefreshToken, EventBuilder event, HttpHeaders headers, HttpRequest request) throws OAuthErrorException {
String encodedRefreshToken, EventBuilder event, HttpHeaders headers, HttpRequest request, List<String> resourceList) throws OAuthErrorException {
RefreshToken refreshToken = verifyRefreshToken(session, realm, authorizedClient, request, encodedRefreshToken, true);

event.user(refreshToken.getSubject()).session(refreshToken.getSessionState())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.REFRESH_TOKEN_TYPE, refreshToken.getType());

TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken, headers);
TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken, headers, resourceList);
AuthenticatedClientSessionModel clientSession = validation.clientSessionCtx.getClientSession();

// validate authorizedClient is same as validated client
Expand Down Expand Up @@ -532,6 +532,18 @@ public AccessToken createClientAccessToken(KeycloakSession session, RealmModel r
return token;
}

public AccessToken createClientAccessToken(KeycloakSession session, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession,
ClientSessionContext clientSessionCtx, List<String> resourceList) {
AccessToken token = initToken(realm, client, user, userSession, clientSessionCtx, session.getContext().getUri());
if (resourceList !=null) {
token.audience(resourceList.toArray(new String[resourceList.size()]));
} else if (realm.getDefaultAudValueForAccessToken() != null && !realm.getDefaultAudValueForAccessToken().isEmpty()) {
token.audience(realm.getDefaultAudValueForAccessToken());
}
token = transformAccessToken(session, token, userSession, clientSessionCtx);
return token;
}


public static ClientSessionContext attachAuthenticationSession(KeycloakSession session, UserSessionModel userSession, AuthenticationSessionModel authSession) {
ClientModel client = authSession.getClient();
Expand Down Expand Up @@ -1036,6 +1048,12 @@ public AccessTokenResponseBuilder generateAccessToken() {
return this;
}

public AccessTokenResponseBuilder generateAccessToken(List<String> resourceList) {
UserModel user = userSession.getUser();
accessToken = createClientAccessToken(session, realm, client, user, userSession, clientSessionCtx, resourceList);
return this;
}

public AccessTokenResponseBuilder generateRefreshToken() {
if (accessToken == null) {
throw new IllegalStateException("accessToken not set");
Expand Down
Loading