1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.spring.security.ldaptive.userdetails;
18
19 import static java.util.Objects.nonNull;
20 import static org.springframework.util.ObjectUtils.isEmpty;
21
22 import java.util.Collection;
23 import java.util.Objects;
24 import java.util.Optional;
25 import java.util.Set;
26 import java.util.function.Predicate;
27 import java.util.regex.Pattern;
28 import java.util.stream.Collectors;
29 import java.util.stream.Stream;
30 import lombok.AccessLevel;
31 import lombok.Getter;
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.bremersee.ldaptive.LdaptiveAttribute;
35 import org.bremersee.ldaptive.LdaptiveTemplate;
36 import org.bremersee.spring.security.ldaptive.authentication.AccountControlEvaluator;
37 import org.bremersee.spring.security.ldaptive.authentication.LdaptiveAuthenticationProperties;
38 import org.bremersee.spring.security.ldaptive.authentication.provider.NoAccountControlEvaluator;
39 import org.ldaptive.FilterTemplate;
40 import org.ldaptive.LdapAttribute;
41 import org.ldaptive.LdapEntry;
42 import org.ldaptive.SearchRequest;
43 import org.ldaptive.dn.Dn;
44 import org.ldaptive.dn.NameValue;
45 import org.ldaptive.dn.RDn;
46 import org.springframework.security.core.GrantedAuthority;
47 import org.springframework.security.core.authority.SimpleGrantedAuthority;
48 import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
49 import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
50 import org.springframework.security.core.userdetails.UserDetailsService;
51 import org.springframework.security.core.userdetails.UsernameNotFoundException;
52 import org.springframework.util.Assert;
53
54
55
56
57
58
59 @Getter(AccessLevel.PROTECTED)
60 public class LdaptiveUserDetailsService implements UserDetailsService {
61
62
63
64
65 protected static final String USERNAME_PLACEHOLDER = "${username}";
66
67
68
69
70 private final Log logger = LogFactory.getLog(this.getClass());
71
72
73
74
75 private final LdaptiveAuthenticationProperties authenticationProperties;
76
77
78
79
80 private final LdaptiveTemplate ldaptiveTemplate;
81
82
83
84
85 private GrantedAuthoritiesMapper grantedAuthoritiesMapper;
86
87
88
89
90 private AccountControlEvaluator accountControlEvaluator = new NoAccountControlEvaluator();
91
92
93
94
95 private LdaptiveRememberMeTokenProvider rememberMeTokenProvider;
96
97
98
99
100
101
102
103 public LdaptiveUserDetailsService(
104 LdaptiveAuthenticationProperties authenticationProperties,
105 LdaptiveTemplate ldaptiveTemplate) {
106
107 this.authenticationProperties = authenticationProperties;
108 this.ldaptiveTemplate = ldaptiveTemplate;
109 Assert.notNull(getAuthenticationProperties(), "Authentication properties are required.");
110 Assert.notNull(getLdaptiveTemplate(), "Ldaptive template is required.");
111 setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
112 if (nonNull(authenticationProperties.getAccountControlEvaluator())) {
113 setAccountControlEvaluator(authenticationProperties.getAccountControlEvaluator().get());
114 }
115 if (isEmpty(getAuthenticationProperties().getPasswordLastSetAttribute())) {
116 setRememberMeTokenProvider(
117 new LdaptiveEvaluatedRememberMeTokenProvider(getAccountControlEvaluator()));
118 } else {
119 setRememberMeTokenProvider(
120 new LdaptivePwdLastSetRememberMeTokenProvider(getAccountControlEvaluator(),
121 getAuthenticationProperties().getPasswordLastSetAttribute()));
122 }
123 }
124
125
126
127
128
129
130 public void setGrantedAuthoritiesMapper(
131 GrantedAuthoritiesMapper grantedAuthoritiesMapper) {
132 if (nonNull(grantedAuthoritiesMapper)) {
133 this.grantedAuthoritiesMapper = grantedAuthoritiesMapper;
134 }
135 }
136
137
138
139
140
141
142 public void setAccountControlEvaluator(
143 AccountControlEvaluator accountControlEvaluator) {
144 if (nonNull(accountControlEvaluator)) {
145 this.accountControlEvaluator = accountControlEvaluator;
146 }
147 }
148
149
150
151
152
153
154 public void setRememberMeTokenProvider(LdaptiveRememberMeTokenProvider rememberMeTokenProvider) {
155 if (nonNull(rememberMeTokenProvider)) {
156 this.rememberMeTokenProvider = rememberMeTokenProvider;
157 }
158 }
159
160 @Override
161 public LdaptiveUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
162 logger.debug(String.format("Loading user '%s' ...", username));
163 LdapEntry ldapEntry = findUser(username)
164 .orElseThrow(() -> new UsernameNotFoundException(String
165 .format("User '%s' was not found.", username)));
166 Collection<? extends GrantedAuthority> authorities = getAuthorities(ldapEntry);
167 var userDetailsBuilder = LdaptiveUserDetails.builder()
168 .dn(ldapEntry.getDn())
169 .username(LdaptiveAttribute.define(getAuthenticationProperties().getUsernameAttribute())
170 .getValue(ldapEntry)
171 .orElse(username))
172 .password(getRememberMeTokenProvider().getRememberMeToken(ldapEntry))
173 .accountNonExpired(getAccountControlEvaluator().isAccountNonExpired(ldapEntry))
174 .accountNonLocked(getAccountControlEvaluator().isAccountNonLocked(ldapEntry))
175 .credentialsNonExpired(getAccountControlEvaluator().isCredentialsNonExpired(ldapEntry))
176 .enabled(getAccountControlEvaluator().isEnabled(ldapEntry))
177 .authorities(authorities);
178 LdaptiveAttribute.define(getAuthenticationProperties().getFirstNameAttribute())
179 .getValue(ldapEntry)
180 .ifPresent(userDetailsBuilder::firstName);
181 LdaptiveAttribute.define(getAuthenticationProperties().getLastNameAttribute())
182 .getValue(ldapEntry)
183 .ifPresent(userDetailsBuilder::lastName);
184 LdaptiveAttribute.define(getAuthenticationProperties().getEmailAttribute())
185 .getValue(ldapEntry)
186 .ifPresent(userDetailsBuilder::email);
187 return userDetailsBuilder.build();
188 }
189
190
191
192
193
194
195
196 protected boolean isDn(String username) {
197 try {
198 Dn baseDn = new Dn(getAuthenticationProperties().getUserBaseDn());
199 return Optional.ofNullable(username)
200 .map(Dn::new)
201 .filter(baseDn::isAncestor)
202 .isPresent();
203
204 } catch (RuntimeException e) {
205 return false;
206 }
207 }
208
209
210
211
212
213
214
215 public Optional<LdapEntry> findUser(String username) {
216 SearchRequest searchRequest = isDn(username)
217 ? SearchRequest.objectScopeSearchRequest(username)
218 : SearchRequest.builder()
219 .dn(getAuthenticationProperties().getUserBaseDn())
220 .filter(FilterTemplate.builder()
221 .filter(getAuthenticationProperties().getUserFindOneFilter())
222 .parameters(username)
223 .build())
224 .scope(getAuthenticationProperties().getUserFindOneSearchScope())
225 .sizeLimit(1)
226 .build();
227 return getLdaptiveTemplate().findOne(searchRequest);
228 }
229
230
231
232
233
234
235
236 public Collection<GrantedAuthority> getAuthorities(LdapEntry user) {
237
238 return switch (getAuthenticationProperties().getGroupFetchStrategy()) {
239 case NONE -> Set.of();
240 case USER_CONTAINS_GROUPS -> getAuthoritiesByGroupsInUser(user);
241 case GROUP_CONTAINS_USERS -> getAuthoritiesByGroupsWithUser(user);
242 };
243 }
244
245
246
247
248
249
250
251 protected Collection<GrantedAuthority> getAuthoritiesByGroupsInUser(LdapEntry user) {
252 Collection<GrantedAuthority> authorities = Stream.ofNullable(user)
253 .map(entry -> entry.getAttribute(getAuthenticationProperties().getMemberAttribute()))
254 .filter(Objects::nonNull)
255 .map(LdapAttribute::getStringValues)
256 .filter(Objects::nonNull)
257 .flatMap(Collection::stream)
258 .flatMap(memberOfValue -> getGroupName(memberOfValue).stream())
259 .map(SimpleGrantedAuthority::new)
260 .collect(Collectors.toSet());
261 return Set.copyOf(getGrantedAuthoritiesMapper().mapAuthorities(authorities));
262 }
263
264 private static Optional<String> getGroupName(String groupNameOrGroupDn) {
265 return getDn(groupNameOrGroupDn)
266 .map(Dn::getRDn)
267 .map(RDn::getNameValue)
268 .map(NameValue::getStringValue)
269 .or(() -> Optional.ofNullable(groupNameOrGroupDn))
270 .filter(Predicate.not(String::isBlank));
271 }
272
273 private static Optional<Dn> getDn(String dn) {
274 if (isEmpty(dn)) {
275 return Optional.empty();
276 }
277 try {
278 return Optional.of(new Dn(dn));
279 } catch (RuntimeException e) {
280 return Optional.empty();
281 }
282 }
283
284
285
286
287
288
289
290 protected Collection<GrantedAuthority> getAuthoritiesByGroupsWithUser(LdapEntry user) {
291 Collection<GrantedAuthority> authorities = getLdaptiveTemplate()
292 .findAll(
293 SearchRequest.builder()
294 .dn(getAuthenticationProperties().getGroupBaseDn())
295 .filter(FilterTemplate.builder()
296 .filter(getAuthorityFilter(user))
297 .build())
298 .scope(getAuthenticationProperties().getGroupSearchScope())
299 .build())
300 .stream()
301 .map(this::getAuthorityName)
302 .map(SimpleGrantedAuthority::new)
303 .collect(Collectors.toSet());
304 return Set.copyOf(getGrantedAuthoritiesMapper().mapAuthorities(authorities));
305 }
306
307
308
309
310
311
312
313 protected String getAuthorityFilter(LdapEntry user) {
314 String groupObjectClass = getAuthenticationProperties().getGroupObjectClass();
315 String groupMemberAttribute = getAuthenticationProperties().getGroupMemberAttribute();
316 String groupMemberValue;
317 String groupMemberFormat = getAuthenticationProperties().getGroupMemberFormat();
318 if (isEmpty(groupMemberFormat)) {
319 groupMemberValue = user.getDn();
320 } else {
321 String username = user.getAttribute(getAuthenticationProperties().getUsernameAttribute())
322 .getStringValue();
323 groupMemberValue = groupMemberFormat
324 .replaceFirst(Pattern.quote(USERNAME_PLACEHOLDER), username);
325 }
326 return String.format("(&(objectClass=%s)(%s=%s))",
327 groupObjectClass, groupMemberAttribute, groupMemberValue);
328 }
329
330
331
332
333
334
335
336 protected String getAuthorityName(LdapEntry group) {
337 String groupIdAttribute = getAuthenticationProperties().getGroupIdAttribute();
338 String fallback = getGroupName(group.getDn()).orElse(null);
339 if (isEmpty(groupIdAttribute)) {
340 return fallback;
341 }
342 return Optional.ofNullable(group.getAttribute(groupIdAttribute))
343 .map(LdapAttribute::getStringValue)
344 .orElse(fallback);
345 }
346
347 }