Skip to content

Commit 597b4c1

Browse files
committed
Update to Tailormap 12.2.1 session serialization format where user/group properties are always saved in the principal (whether username/password or OIDC)
1 parent e3e34e2 commit 597b4c1

5 files changed

Lines changed: 160 additions & 114 deletions

File tree

src/main/java/nl/b3p/planmonitorwonen/api/security/PlanmonitorAuthenticationService.java

Lines changed: 15 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,10 @@
99
import static org.springframework.http.HttpStatus.FORBIDDEN;
1010
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
1111

12-
import com.fasterxml.jackson.core.JsonProcessingException;
13-
import com.fasterxml.jackson.databind.ObjectMapper;
14-
import java.util.Collections;
15-
import java.util.List;
16-
import java.util.Map;
1712
import java.util.Set;
18-
import java.util.function.Function;
1913
import java.util.stream.Collectors;
20-
import java.util.stream.Stream;
21-
import org.postgresql.util.PGobject;
22-
import org.springframework.beans.factory.annotation.Qualifier;
2314
import org.springframework.context.annotation.Profile;
24-
import org.springframework.jdbc.core.simple.JdbcClient;
2515
import org.springframework.security.core.Authentication;
26-
import org.springframework.security.core.AuthenticationException;
27-
import org.springframework.security.core.GrantedAuthority;
2816
import org.springframework.security.core.context.SecurityContextHolder;
2917
import org.springframework.stereotype.Service;
3018
import org.springframework.web.server.ResponseStatusException;
@@ -36,70 +24,31 @@ public class PlanmonitorAuthenticationService {
3624
public record PlanmonitorAuthentication(
3725
Authentication authentication, boolean isProvincie, Set<String> gemeentes) {}
3826

39-
private final JdbcClient jdbcClient;
40-
private final ObjectMapper objectMapper = new ObjectMapper();
41-
42-
public PlanmonitorAuthenticationService(@Qualifier("tailormapJdbcClient") JdbcClient jdbcClient) {
43-
this.jdbcClient = jdbcClient;
44-
}
45-
4627
public PlanmonitorAuthentication getFromSecurityContext() throws ResponseStatusException {
4728
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
4829
if (authentication == null) {
4930
throw new ResponseStatusException(UNAUTHORIZED);
5031
}
5132

52-
Map<String, Set<TailormapUserDetails.UDAdditionalProperty>> groupProperties = getGroupProperties();
33+
if (authentication.getPrincipal() instanceof TailormapUserDetails userDetails) {
34+
// these keys have been registered in the TM API, see:
35+
// https://github.com/Tailormap/tailormap-api/blob/main/src/main/java/org/tailormap/api/persistence/helper/AdminAdditionalPropertyHelper.java#L18-L24
5336

54-
// these keys have been registered in the TM API, see:
55-
// https://github.com/Tailormap/tailormap-api/blob/d4be62bc4d1bf8ed8cdb52f0887590f1fed337f0/src/main/java/org/tailormap/api/persistence/helper/AdminAdditionalPropertyHelper.java#L18-L24
56-
boolean isProvincie = groupProperties.getOrDefault("typeGebruiker", Collections.emptySet()).stream()
57-
.map(TailormapUserDetails.UDAdditionalProperty::value)
58-
.anyMatch("provincie"::equals);
37+
boolean isProvincie =
38+
userDetails.streamAllPropertiesForKey("typeGebruiker").anyMatch("provincie"::equals);
5939

60-
Set<String> gemeentes = groupProperties.getOrDefault("gemeente", Collections.emptySet()).stream()
61-
.map(TailormapUserDetails.UDAdditionalProperty::value)
62-
.map(Object::toString)
63-
.collect(Collectors.toSet());
40+
Set<String> gemeentes = userDetails
41+
.streamAllPropertiesForKey("gemeente")
42+
.map(Object::toString)
43+
.collect(Collectors.toSet());
44+
PlanmonitorAuthentication result = new PlanmonitorAuthentication(authentication, isProvincie, gemeentes);
6445

65-
PlanmonitorAuthentication result = new PlanmonitorAuthentication(authentication, isProvincie, gemeentes);
66-
67-
if (!result.isProvincie && result.gemeentes.isEmpty()) {
46+
if (!result.isProvincie && result.gemeentes.isEmpty()) {
47+
throw new ResponseStatusException(FORBIDDEN);
48+
}
49+
return result;
50+
} else {
6851
throw new ResponseStatusException(FORBIDDEN);
6952
}
70-
71-
return result;
72-
}
73-
74-
public Map<String, Set<TailormapUserDetails.UDAdditionalProperty>> getGroupProperties()
75-
throws AuthenticationException {
76-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
77-
78-
Function<Object, TailormapUserDetails.UDAdditionalProperty[]> converter = (Object o) -> {
79-
try {
80-
return objectMapper.readValue(
81-
((PGobject) o).getValue(), TailormapUserDetails.UDAdditionalProperty[].class);
82-
} catch (JsonProcessingException e) {
83-
throw new RuntimeException(e);
84-
}
85-
};
86-
87-
List<TailormapUserDetails.UDAdditionalProperty> properties = jdbcClient
88-
.sql(
89-
"select additional_properties from groups where name in (:names) and additional_properties is not null")
90-
.params(Collections.singletonMap(
91-
"names",
92-
authentication.getAuthorities().stream()
93-
.map(GrantedAuthority::getAuthority)
94-
.toList()))
95-
.query()
96-
.singleColumn()
97-
.stream()
98-
.map(converter)
99-
.flatMap(Stream::of)
100-
.toList();
101-
102-
return properties.stream()
103-
.collect(Collectors.groupingBy(TailormapUserDetails.UDAdditionalProperty::key, Collectors.toSet()));
10453
}
10554
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright (C) 2025 B3Partners B.V.
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
package org.tailormap.api.security;
8+
9+
import java.io.Serializable;
10+
11+
public record TailormapAdditionalProperty(String key, Boolean isPublic, Object value) implements Serializable {}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (C) 2025 B3Partners B.V.
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
package org.tailormap.api.security;
8+
9+
import java.io.Serial;
10+
import java.util.Collection;
11+
import java.util.Collections;
12+
import java.util.List;
13+
import org.springframework.security.core.GrantedAuthority;
14+
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
15+
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
16+
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
17+
18+
public class TailormapOidcUser extends DefaultOidcUser implements TailormapUserDetails {
19+
@Serial
20+
private static final long serialVersionUID = 1L;
21+
22+
private final Collection<TailormapAdditionalProperty> additionalGroupProperties;
23+
24+
public TailormapOidcUser(
25+
Collection<? extends GrantedAuthority> authorities,
26+
OidcIdToken idToken,
27+
OidcUserInfo userInfo,
28+
String nameAttributeKey,
29+
Collection<TailormapAdditionalProperty> additionalGroupProperties) {
30+
super(authorities, idToken, userInfo, nameAttributeKey);
31+
this.additionalGroupProperties = Collections.unmodifiableCollection(additionalGroupProperties);
32+
}
33+
34+
@Override
35+
public Collection<TailormapAdditionalProperty> getAdditionalProperties() {
36+
return List.of();
37+
}
38+
39+
@Override
40+
public Collection<TailormapAdditionalProperty> getAdditionalGroupProperties() {
41+
return additionalGroupProperties;
42+
}
43+
44+
@Override
45+
public String getPassword() {
46+
return null;
47+
}
48+
49+
@Override
50+
public String getUsername() {
51+
return super.getName();
52+
}
53+
}
Lines changed: 18 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,36 @@
11
/*
2-
* Copyright (C) 2024 Provincie Zeeland
2+
* Copyright (C) 2025 B3Partners B.V.
33
*
44
* SPDX-License-Identifier: MIT
55
*/
66

77
package org.tailormap.api.security;
88

9-
import java.io.Serial;
109
import java.io.Serializable;
11-
import java.time.ZoneId;
12-
import java.time.ZonedDateTime;
13-
import java.util.ArrayList;
1410
import java.util.Collection;
15-
import java.util.List;
16-
import org.springframework.security.core.GrantedAuthority;
11+
import java.util.stream.Stream;
1712
import org.springframework.security.core.userdetails.UserDetails;
1813

19-
public class TailormapUserDetails implements UserDetails {
20-
public record UDAdditionalProperty(String key, Boolean isPublic, Object value) implements Serializable {}
14+
public interface TailormapUserDetails extends Serializable, UserDetails {
2115

22-
@Serial
23-
private static final long serialVersionUID = 2L;
16+
Collection<TailormapAdditionalProperty> getAdditionalProperties();
2417

25-
public Collection<GrantedAuthority> authorities;
26-
public String username;
27-
public String password;
28-
public ZonedDateTime validUntil;
29-
public boolean enabled;
18+
Collection<TailormapAdditionalProperty> getAdditionalGroupProperties();
3019

31-
private final List<UDAdditionalProperty> additionalProperties = new ArrayList<>();
32-
private final List<UDAdditionalProperty> additionalGroupProperties = new ArrayList<>();
33-
34-
@Override
35-
public Collection<? extends GrantedAuthority> getAuthorities() {
36-
return authorities;
37-
}
38-
39-
@Override
40-
public String getPassword() {
41-
return password;
42-
}
43-
44-
@Override
45-
public String getUsername() {
46-
return username;
47-
}
48-
49-
@Override
50-
public boolean isAccountNonExpired() {
51-
return validUntil == null || validUntil.isAfter(ZonedDateTime.now(ZoneId.systemDefault()));
52-
}
53-
54-
@Override
55-
public boolean isEnabled() {
56-
return enabled;
57-
}
58-
59-
public List<UDAdditionalProperty> getAdditionalProperties() {
60-
return additionalProperties;
20+
/**
21+
* Returns true if any user or group Boolean property with the given key is true. If beside a true value, there are
22+
* also properties with the same key but with any other value than true, the true value has precedence.
23+
*
24+
* @param key the key to look for
25+
* @return true if a Boolean property with the key is present with a true value
26+
*/
27+
default boolean hasTruePropertyForKey(String key) {
28+
return streamAllPropertiesForKey(key).anyMatch(Boolean.TRUE::equals);
6129
}
6230

63-
public List<UDAdditionalProperty> getAdditionalGroupProperties() {
64-
return additionalGroupProperties;
31+
default Stream<Object> streamAllPropertiesForKey(String key) {
32+
return Stream.concat(getAdditionalProperties().stream(), getAdditionalGroupProperties().stream())
33+
.filter(p -> p.key().equals(key))
34+
.map(TailormapAdditionalProperty::value);
6535
}
6636
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (C) 2022 B3Partners B.V.
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
package org.tailormap.api.security;
7+
8+
import java.io.Serial;
9+
import java.time.ZoneId;
10+
import java.time.ZonedDateTime;
11+
import java.util.ArrayList;
12+
import java.util.Collection;
13+
import org.springframework.security.core.GrantedAuthority;
14+
15+
class TailormapUserDetailsImpl implements TailormapUserDetails {
16+
17+
@Serial
18+
private static final long serialVersionUID = 1L;
19+
20+
private Collection<GrantedAuthority> authorities;
21+
private String username;
22+
private String password;
23+
private ZonedDateTime validUntil;
24+
private boolean enabled;
25+
26+
private final Collection<TailormapAdditionalProperty> additionalProperties = new ArrayList<>();
27+
private final Collection<TailormapAdditionalProperty> additionalGroupProperties = new ArrayList<>();
28+
29+
@Override
30+
public Collection<? extends GrantedAuthority> getAuthorities() {
31+
return authorities;
32+
}
33+
34+
@Override
35+
public String getPassword() {
36+
return password;
37+
}
38+
39+
@Override
40+
public String getUsername() {
41+
return username;
42+
}
43+
44+
@Override
45+
public boolean isAccountNonExpired() {
46+
return validUntil == null || validUntil.isAfter(ZonedDateTime.now(ZoneId.systemDefault()));
47+
}
48+
49+
@Override
50+
public boolean isEnabled() {
51+
return enabled;
52+
}
53+
54+
@Override
55+
public Collection<TailormapAdditionalProperty> getAdditionalProperties() {
56+
return additionalProperties;
57+
}
58+
59+
@Override
60+
public Collection<TailormapAdditionalProperty> getAdditionalGroupProperties() {
61+
return additionalGroupProperties;
62+
}
63+
}

0 commit comments

Comments
 (0)