Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
c925033944
10 changed files with 125 additions and 12 deletions
|
@ -65,6 +65,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||
public static final String LOGIN_HINT_PARAM = "login_hint";
|
||||
public static final String REQUEST_PARAM = "request";
|
||||
public static final String REQUEST_URI_PARAM = "request_uri";
|
||||
public static final String UI_LOCALES_PARAM = OAuth2Constants.UI_LOCALES_PARAM;
|
||||
|
||||
public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
|
||||
public static final String ISSUER = "iss";
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
|||
import org.keycloak.protocol.ProtocolMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.UserInfoTokenMapper;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -505,6 +506,27 @@ public class TokenManager {
|
|||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public AccessToken transformUserInfoAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
|
||||
UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers();
|
||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||
for (ProtocolMapperModel mapping : mappings) {
|
||||
|
||||
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
|
||||
if (mapper == null || !(mapper instanceof OIDCAccessTokenMapper)) continue;
|
||||
|
||||
if(mapper instanceof UserInfoTokenMapper){
|
||||
token = ((UserInfoTokenMapper)mapper).transformUserInfoToken(token, mapping, session, userSession, clientSession);
|
||||
continue;
|
||||
}
|
||||
|
||||
token = ((OIDCAccessTokenMapper)mapper).transformAccessToken(token, mapping, session, userSession, clientSession);
|
||||
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public void transformIDToken(KeycloakSession session, IDToken token, RealmModel realm, ClientModel client, UserModel user,
|
||||
UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers();
|
||||
|
|
|
@ -100,6 +100,9 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
|||
KNOWN_REQ_PARAMS.add(AdapterConstants.KC_IDP_HINT);
|
||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.NONCE_PARAM);
|
||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.MAX_AGE_PARAM);
|
||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
|
||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
|
||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
|
||||
}
|
||||
|
||||
private enum Action {
|
||||
|
|
|
@ -174,7 +174,7 @@ public class UserInfoEndpoint {
|
|||
}
|
||||
|
||||
AccessToken userInfo = new AccessToken();
|
||||
tokenManager.transformAccessToken(session, userInfo, realm, clientModel, userModel, userSession, clientSession);
|
||||
tokenManager.transformUserInfoAccessToken(session, userInfo, realm, clientModel, userModel, userSession, clientSession);
|
||||
|
||||
event.success();
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.protocol.oidc.mappers;
|
||||
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.protocol.ProtocolMapper;
|
||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
@ -48,6 +49,10 @@ public class OIDCAttributeMapperHelper {
|
|||
public static final String INCLUDE_IN_ID_TOKEN_LABEL = "includeInIdToken.label";
|
||||
public static final String INCLUDE_IN_ID_TOKEN_HELP_TEXT = "includeInIdToken.tooltip";
|
||||
|
||||
public static final String INCLUDE_IN_USERINFO = "userinfo.token.claim";
|
||||
public static final String INCLUDE_IN_USERINFO_LABEL = "includeInUserInfo.label";
|
||||
public static final String INCLUDE_IN_USERINFO_HELP_TEXT = "includeInUserInfo.tooltip";
|
||||
|
||||
public static Object mapAttributeValue(ProtocolMapperModel mappingModel, Object attributeValue) {
|
||||
if (attributeValue == null) return null;
|
||||
|
||||
|
@ -114,11 +119,20 @@ public class OIDCAttributeMapperHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static ProtocolMapperModel createClaimMapper(String name,
|
||||
String userAttribute,
|
||||
String tokenClaimName, String claimType,
|
||||
boolean consentRequired, String consentText,
|
||||
boolean accessToken, boolean idToken,
|
||||
String mapperId) {
|
||||
return createClaimMapper(name, userAttribute,tokenClaimName, claimType, consentRequired, consentText, accessToken, idToken, false, mapperId);
|
||||
}
|
||||
|
||||
public static ProtocolMapperModel createClaimMapper(String name,
|
||||
String userAttribute,
|
||||
String tokenClaimName, String claimType,
|
||||
boolean consentRequired, String consentText,
|
||||
boolean accessToken, boolean idToken,
|
||||
boolean accessToken, boolean idToken, boolean userinfo,
|
||||
String mapperId) {
|
||||
ProtocolMapperModel mapper = new ProtocolMapperModel();
|
||||
mapper.setName(name);
|
||||
|
@ -132,6 +146,7 @@ public class OIDCAttributeMapperHelper {
|
|||
config.put(JSON_TYPE, claimType);
|
||||
if (accessToken) config.put(INCLUDE_IN_ACCESS_TOKEN, "true");
|
||||
if (idToken) config.put(INCLUDE_IN_ID_TOKEN, "true");
|
||||
if (userinfo) config.put(INCLUDE_IN_USERINFO, "true");
|
||||
mapper.setConfig(config);
|
||||
return mapper;
|
||||
}
|
||||
|
@ -144,11 +159,14 @@ public class OIDCAttributeMapperHelper {
|
|||
return "true".equals(mappingModel.getConfig().get(INCLUDE_IN_ACCESS_TOKEN));
|
||||
}
|
||||
|
||||
|
||||
public static boolean isMultivalued(ProtocolMapperModel mappingModel) {
|
||||
return "true".equals(mappingModel.getConfig().get(ProtocolMapperUtils.MULTIVALUED));
|
||||
}
|
||||
|
||||
public static boolean includeInUserInfo(ProtocolMapperModel mappingModel){
|
||||
return "true".equals(mappingModel.getConfig().get(INCLUDE_IN_USERINFO));
|
||||
}
|
||||
|
||||
public static void addAttributeConfig(List<ProviderConfigProperty> configProperties) {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
|
@ -183,5 +201,12 @@ public class OIDCAttributeMapperHelper {
|
|||
property.setDefaultValue("true");
|
||||
property.setHelpText(INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
|
||||
configProperties.add(property);
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(INCLUDE_IN_USERINFO);
|
||||
property.setLabel(INCLUDE_IN_USERINFO_LABEL);
|
||||
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||
property.setDefaultValue("false");
|
||||
property.setHelpText(INCLUDE_IN_USERINFO_HELP_TEXT);
|
||||
configProperties.add(property);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ import java.util.List;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
|
||||
public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
|
@ -113,11 +113,22 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
|
|||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
|
||||
if (!OIDCAttributeMapperHelper.includeInUserInfo(mappingModel)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
setClaim(token, mappingModel, userSession);
|
||||
return token;
|
||||
}
|
||||
|
||||
public static ProtocolMapperModel createClaimMapper(String name,
|
||||
String userAttribute,
|
||||
String tokenClaimName, String claimType,
|
||||
boolean consentRequired, String consentText,
|
||||
boolean accessToken, boolean idToken, boolean multivalued) {
|
||||
String userAttribute,
|
||||
String tokenClaimName, String claimType,
|
||||
boolean consentRequired, String consentText,
|
||||
boolean accessToken, boolean idToken, boolean multivalued) {
|
||||
ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute,
|
||||
tokenClaimName, claimType,
|
||||
consentRequired, consentText,
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.protocol.oidc.mappers;
|
||||
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
|
||||
*/
|
||||
public interface UserInfoTokenMapper {
|
||||
|
||||
AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
|
||||
UserSessionModel userSession, ClientSessionModel clientSession);
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.services.util;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -33,7 +34,6 @@ import java.util.Set;
|
|||
public class LocaleHelper {
|
||||
|
||||
private static final String LOCALE_COOKIE = "KEYCLOAK_LOCALE";
|
||||
private static final String UI_LOCALES_PARAM = "ui_locales";
|
||||
private static final String KC_LOCALE_PARAM = "kc_locale";
|
||||
|
||||
public static Locale getLocale(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
|
@ -104,8 +104,8 @@ public class LocaleHelper {
|
|||
}
|
||||
|
||||
// ui_locales query parameter
|
||||
if (uriInfo != null && uriInfo.getQueryParameters().containsKey(UI_LOCALES_PARAM)) {
|
||||
String localeString = uriInfo.getQueryParameters().getFirst(UI_LOCALES_PARAM);
|
||||
if (uriInfo != null && uriInfo.getQueryParameters().containsKey(OAuth2Constants.UI_LOCALES_PARAM)) {
|
||||
String localeString = uriInfo.getQueryParameters().getFirst(OAuth2Constants.UI_LOCALES_PARAM);
|
||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString.split(" "));
|
||||
if (locale != null) {
|
||||
return locale;
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.OAuthErrorException;
|
|||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -323,7 +324,7 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void nonSupportedParams() {
|
||||
driver.navigate().to(oauth.getLoginFormUrl() + "&display=popup&foo=foobar");
|
||||
driver.navigate().to(oauth.getLoginFormUrl() + "&display=popup&foo=foobar&claims_locales=fr");
|
||||
|
||||
loginPage.assertCurrent();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
@ -363,4 +364,19 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest {
|
|||
Assert.assertEquals(OAuthErrorException.REQUEST_URI_NOT_SUPPORTED, resp.getError());
|
||||
}
|
||||
|
||||
// LOGIN_HINT
|
||||
|
||||
@Test
|
||||
public void loginHint() {
|
||||
// Assert need to re-authenticate with prompt=login
|
||||
driver.navigate().to(oauth.getLoginFormUrl() + "&" + OIDCLoginProtocol.LOGIN_HINT_PARAM + "=test-user%40localhost");
|
||||
|
||||
loginPage.assertCurrent();
|
||||
Assert.assertEquals("test-user@localhost", loginPage.getUsername());
|
||||
loginPage.login("password");
|
||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -156,6 +156,8 @@ includeInIdToken.label=Add to ID token
|
|||
includeInIdToken.tooltip=Should the claim be added to the ID token?
|
||||
includeInAccessToken.label=Add to access token
|
||||
includeInAccessToken.tooltip=Should the claim be added to the access token?
|
||||
includeInUserInfo.label=Add to userinfo
|
||||
includeInUserInfo.tooltip=Should the claim be added to the userinfo?
|
||||
usermodel.clientRoleMapping.clientId.label=Client ID
|
||||
usermodel.clientRoleMapping.clientId.tooltip=Client ID for role mappings
|
||||
usermodel.clientRoleMapping.rolePrefix.label=Client Role prefix
|
||||
|
|
Loading…
Reference in a new issue