mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-03 20:39:33 -05:00
Fix duplicate address claim in IDToken (#45423)
Closes #45250
Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
(cherry picked from commit db1f75a1cf)
This commit is contained in:
parent
cb1ed34eb8
commit
ed3fc2fcfb
5 changed files with 86 additions and 98 deletions
|
|
@ -18,7 +18,6 @@
|
|||
package org.keycloak.representations;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.keycloak.TokenCategory;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
|
@ -130,9 +129,6 @@ public class IDToken extends JsonWebToken {
|
|||
@JsonProperty(PHONE_NUMBER_VERIFIED)
|
||||
protected Boolean phoneNumberVerified;
|
||||
|
||||
@JsonProperty(ADDRESS)
|
||||
protected Map<String, Object> address;
|
||||
|
||||
@JsonProperty(UPDATED_AT)
|
||||
protected Long updatedAt;
|
||||
|
||||
|
|
@ -332,28 +328,30 @@ public class IDToken extends JsonWebToken {
|
|||
this.phoneNumberVerified = phoneNumberVerified;
|
||||
}
|
||||
|
||||
@JsonProperty("address")
|
||||
@JsonIgnore
|
||||
public Map<String, Object> getAddressClaimsMap() {
|
||||
return address;
|
||||
Object value = getOtherClaims().get(ADDRESS);
|
||||
return value instanceof Map ? (Map<String, Object>) value : null;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public AddressClaimSet getAddress() {
|
||||
return Optional.ofNullable(address).map(a -> {
|
||||
return JsonSerialization.mapper.convertValue(a, AddressClaimSet.class);
|
||||
})
|
||||
.orElse(null);
|
||||
}
|
||||
Object value = getOtherClaims().get(ADDRESS);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAddress(Map<String, Object> address) {
|
||||
this.address = address;
|
||||
return JsonSerialization.mapper.convertValue(value, AddressClaimSet.class);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setAddress(AddressClaimSet address) {
|
||||
this.address = Optional.ofNullable(address)
|
||||
.map(a -> JsonSerialization.mapper.convertValue(a, Map.class))
|
||||
.orElse(null);
|
||||
getOtherClaims().put(ADDRESS, JsonSerialization.mapper.convertValue(address, Map.class));
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setAddress(Map<String, Object> address) {
|
||||
getOtherClaims().put(ADDRESS, address);
|
||||
}
|
||||
|
||||
public Long getUpdatedAt() {
|
||||
|
|
|
|||
|
|
@ -16,10 +16,8 @@
|
|||
*/
|
||||
package org.keycloak.representations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.keycloak.json.StringOrArrayDeserializer;
|
||||
import org.keycloak.json.StringOrArraySerializer;
|
||||
|
|
@ -99,9 +97,6 @@ public class UserInfo {
|
|||
@JsonProperty("phone_number_verified")
|
||||
protected Boolean phoneNumberVerified;
|
||||
|
||||
@JsonProperty("address")
|
||||
protected Map<String, Object> address;
|
||||
|
||||
@JsonProperty("updated_at")
|
||||
protected Long updatedAt;
|
||||
|
||||
|
|
@ -280,28 +275,30 @@ public class UserInfo {
|
|||
this.phoneNumberVerified = phoneNumberVerified;
|
||||
}
|
||||
|
||||
@JsonProperty("address")
|
||||
@JsonIgnore
|
||||
public Map<String, Object> getAddressClaimsMap() {
|
||||
return address;
|
||||
Object value = getOtherClaims().get(IDToken.ADDRESS);
|
||||
return value instanceof Map ? (Map<String, Object>) value : null;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public AddressClaimSet getAddress() {
|
||||
return Optional.ofNullable(address).map(a -> {
|
||||
return JsonSerialization.mapper.convertValue(a, AddressClaimSet.class);
|
||||
})
|
||||
.orElse(null);
|
||||
}
|
||||
Object value = getOtherClaims().get(IDToken.ADDRESS);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAddress(Map<String, Object> address) {
|
||||
this.address = address;
|
||||
return JsonSerialization.mapper.convertValue(value, AddressClaimSet.class);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setAddress(AddressClaimSet address) {
|
||||
this.address = Optional.ofNullable(address)
|
||||
.map(a -> JsonSerialization.mapper.convertValue(a, Map.class))
|
||||
.orElse(null);
|
||||
getOtherClaims().put(IDToken.ADDRESS, JsonSerialization.mapper.convertValue(address, Map.class));
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setAddress(Map<String, Object> address) {
|
||||
getOtherClaims().put(IDToken.ADDRESS, address);
|
||||
}
|
||||
|
||||
public Long getUpdatedAt() {
|
||||
|
|
@ -342,13 +339,4 @@ public class UserInfo {
|
|||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return JsonSerialization.writeValueAsString(this);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,12 +124,10 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc
|
|||
@Override
|
||||
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
|
||||
UserModel user = userSession.getUser();
|
||||
Map<String, Object> addressSet = Optional.ofNullable(token.getAddressClaimsMap()).orElseGet(() -> {
|
||||
return Optional.ofNullable(token.getOtherClaims().get(IDToken.ADDRESS))
|
||||
.filter(Map.class::isInstance)
|
||||
.map(o -> (HashMap<String, Object>) o)
|
||||
.orElseGet(HashMap::new);
|
||||
});
|
||||
Map<String, Object> addressSet = Optional.ofNullable(token.getAddressClaimsMap()).orElseGet(() -> Optional.ofNullable(token.getOtherClaims().get(IDToken.ADDRESS))
|
||||
.filter(Map.class::isInstance)
|
||||
.map(o -> (HashMap<String, Object>) o)
|
||||
.orElseGet(HashMap::new));
|
||||
Optional.ofNullable(getUserModelAttributeValue(user, mappingModel, STREET))
|
||||
.ifPresent(street -> addressSet.put(AddressClaimSet.STREET_ADDRESS, street));
|
||||
Optional.ofNullable(getUserModelAttributeValue(user, mappingModel, AddressClaimSet.LOCALITY))
|
||||
|
|
@ -145,7 +143,6 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc
|
|||
|
||||
if (!addressSet.isEmpty()) {
|
||||
token.setAddress(addressSet);
|
||||
token.getOtherClaims().put(IDToken.ADDRESS, addressSet);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,9 @@ import org.keycloak.testsuite.util.UserInfoClientUtil;
|
|||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse;
|
||||
import org.keycloak.testsuite.util.userprofile.UserProfileUtil;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
|
@ -198,6 +200,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void testAddressMappingWithAdditionalMapper() {
|
||||
//throws an exception if the json contains a duplicate claim
|
||||
JsonSerialization.mapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
|
||||
// prepare test
|
||||
{
|
||||
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
|
||||
|
|
@ -215,13 +219,13 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
|
||||
ProtocolMapperRepresentation addressMapper = createAddressMapper(true, true, true, true);
|
||||
ProtocolMapperRepresentation addressTypeMapper = createClaimMapper("additional-address-field",
|
||||
"address_type",
|
||||
"address.type",
|
||||
"String",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
"address_type",
|
||||
"address.type",
|
||||
"String",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
|
||||
|
||||
ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app");
|
||||
|
|
@ -256,6 +260,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
}
|
||||
|
||||
events.clear();
|
||||
JsonSerialization.mapper.disable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -276,13 +281,13 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
userResource.update(user);
|
||||
|
||||
ProtocolMapperRepresentation addressTypeMapper = createClaimMapper("additional-address-field",
|
||||
"address_type",
|
||||
"address.type",
|
||||
"String",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
"address_type",
|
||||
"address.type",
|
||||
"String",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
ProtocolMapperRepresentation addressMapper = createAddressMapper(true, true, true, true);
|
||||
|
||||
|
||||
|
|
@ -331,13 +336,13 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
userResource.update(user);
|
||||
|
||||
ProtocolMapperRepresentation addressTypeMapper = createClaimMapper("additional-address-field",
|
||||
"address_type",
|
||||
"address.type",
|
||||
"String",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
"address_type",
|
||||
"address.type",
|
||||
"String",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
ProtocolMapperRepresentation addressMapper = createAddressMapper(true, true, true, true);
|
||||
|
||||
ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app");
|
||||
|
|
@ -525,7 +530,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
|| model.getName().equals("hard-app")
|
||||
|| model.getName().equals("test-script-mapper")
|
||||
|| model.getName().equals("json-attribute-mapper")
|
||||
) {
|
||||
) {
|
||||
app.getProtocolMappers().delete(model.getId());
|
||||
}
|
||||
}
|
||||
|
|
@ -746,7 +751,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
for (ProtocolMapperRepresentation model : clientRepresentation.getProtocolMappers()) {
|
||||
if (model.getName().equals("empty")
|
||||
|| model.getName().equals("null")
|
||||
) {
|
||||
) {
|
||||
app.getProtocolMappers().delete(model.getId());
|
||||
}
|
||||
}
|
||||
|
|
@ -1044,17 +1049,17 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
List<String> realmRoleMappings = (List<String>) roleMappings.get("realm");
|
||||
List<String> testAppMappings = (List<String>) roleMappings.get(clientId);
|
||||
assertRolesString(realmRoleMappings,
|
||||
"pref.admin", // from direct assignment to /roleRichGroup/level2group
|
||||
"pref.user", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.customer-user-premium", // from client role customer-admin-composite-role - realm role for test-app
|
||||
"pref.realm-composite-role", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.sample-realm-role" // from realm role realm-composite-role
|
||||
"pref.admin", // from direct assignment to /roleRichGroup/level2group
|
||||
"pref.user", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.customer-user-premium", // from client role customer-admin-composite-role - realm role for test-app
|
||||
"pref.realm-composite-role", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.sample-realm-role" // from realm role realm-composite-role
|
||||
);
|
||||
assertRolesString(testAppMappings,
|
||||
"ta.customer-user", // from direct assignment to /roleRichGroup/level2group
|
||||
"ta.customer-admin-composite-role", // from direct assignment to /roleRichGroup/level2group
|
||||
"ta.customer-admin", // from client role customer-admin-composite-role - client role for test-app
|
||||
"ta.sample-client-role" // from realm role realm-composite-role - client role for test-app
|
||||
"ta.customer-user", // from direct assignment to /roleRichGroup/level2group
|
||||
"ta.customer-admin-composite-role", // from direct assignment to /roleRichGroup/level2group
|
||||
"ta.customer-admin", // from client role customer-admin-composite-role - client role for test-app
|
||||
"ta.sample-client-role" // from realm role realm-composite-role - client role for test-app
|
||||
);
|
||||
|
||||
// Revert
|
||||
|
|
@ -1089,11 +1094,11 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
List<String> realmRoleMappings = (List<String>) roleMappings.get("realm");
|
||||
List<String> testAppAuthzMappings = (List<String>) roleMappings.get(clientId);
|
||||
assertRolesString(realmRoleMappings,
|
||||
"pref.admin", // from direct assignment to /roleRichGroup/level2group
|
||||
"pref.user", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.customer-user-premium", // from client role customer-admin-composite-role - realm role for test-app
|
||||
"pref.realm-composite-role", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.sample-realm-role" // from realm role realm-composite-role
|
||||
"pref.admin", // from direct assignment to /roleRichGroup/level2group
|
||||
"pref.user", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.customer-user-premium", // from client role customer-admin-composite-role - realm role for test-app
|
||||
"pref.realm-composite-role", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.sample-realm-role" // from realm role realm-composite-role
|
||||
);
|
||||
assertNull(testAppAuthzMappings); // There is no client role defined for test-app-authz
|
||||
|
||||
|
|
@ -1122,12 +1127,12 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
List<String> realmRoleMappings = (List<String>) roleMappings.get("realm");
|
||||
List<String> testAppScopeMappings = (List<String>) roleMappings.get(clientId);
|
||||
assertRolesString(realmRoleMappings,
|
||||
"pref.admin", // from direct assignment to /roleRichGroup/level2group
|
||||
"pref.user", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.customer-user-premium"
|
||||
"pref.admin", // from direct assignment to /roleRichGroup/level2group
|
||||
"pref.user", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.customer-user-premium"
|
||||
);
|
||||
assertRolesString(testAppScopeMappings,
|
||||
"test-app-allowed-by-scope", // from direct assignment to roleRichUser, present as scope allows it
|
||||
"test-app-allowed-by-scope", // from direct assignment to roleRichUser, present as scope allows it
|
||||
"test-app-disallowed-by-scope"
|
||||
);
|
||||
|
||||
|
|
@ -1156,15 +1161,15 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
|
|||
List<String> realmRoleMappings = (List<String>) roleMappings.get("realm");
|
||||
List<String> testAppScopeMappings = (List<String>) roleMappings.get(clientId);
|
||||
assertRolesString(realmRoleMappings,
|
||||
"pref.admin", // from direct assignment to /roleRichGroup/level2group
|
||||
"pref.user", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.customer-user-premium"
|
||||
"pref.admin", // from direct assignment to /roleRichGroup/level2group
|
||||
"pref.user", // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
|
||||
"pref.customer-user-premium"
|
||||
);
|
||||
assertRolesString(testAppScopeMappings,
|
||||
"test-app-allowed-by-scope", // from direct assignment to roleRichUser, present as scope allows it
|
||||
"test-app-disallowed-by-scope", // from direct assignment to /roleRichGroup/level2group, present as scope allows it
|
||||
"customer-admin-composite-role", // from the other application
|
||||
"customer-admin"
|
||||
"test-app-allowed-by-scope", // from direct assignment to roleRichUser, present as scope allows it
|
||||
"test-app-disallowed-by-scope", // from direct assignment to /roleRichGroup/level2group, present as scope allows it
|
||||
"customer-admin-composite-role", // from the other application
|
||||
"customer-admin"
|
||||
);
|
||||
|
||||
// Revert
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ public class OIDCScopeTest extends AbstractOIDCScopeTest {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testBuiltinOptionalScopes() throws Exception {
|
||||
// Login. Assert that just 'profile' and 'email' data are there. 'Address' and 'phone' not
|
||||
|
|
|
|||
Loading…
Reference in a new issue