1 /*
2 * Copyright 2014 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.bremersee.spring.security.ldaptive.authentication;
18
19 import java.io.Serial;
20 import java.io.Serializable;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.stream.Collectors;
27 import java.util.stream.Stream;
28 import lombok.AllArgsConstructor;
29 import lombok.Data;
30 import org.bremersee.spring.security.core.mapping.CaseTransformation;
31 import org.ldaptive.SearchScope;
32 import org.springframework.util.ObjectUtils;
33
34 /**
35 * The ldaptive authentication properties.
36 *
37 * @author Christian Bremer
38 */
39 @Data
40 public class LdaptiveAuthenticationProperties implements Serializable {
41
42 @Serial
43 private static final long serialVersionUID = 1L;
44
45 /**
46 * The user base dn (like 'ou=people,dc=example,dc=org'). This value is always required.
47 */
48 protected String userBaseDn;
49
50 /**
51 * A list with refused usernames.
52 */
53 protected List<String> refusedUsernames;
54
55 /**
56 * The object class of the user (like 'inetOrgPerson'). The selected template contains a default.
57 */
58 protected String userObjectClass;
59
60 /**
61 * The username attribute of the user (like 'uid' or 'sAMAccountName'). The selected template
62 * contains a default.
63 */
64 protected String usernameAttribute;
65
66 /**
67 * The password attribute of the user (like 'userPassword'). If it is empty, a simple user bind
68 * will be done with the credentials of the user for authentication. If it is present, the
69 * connection to the ldap server must be done by a 'global' user and a password encoder that fits
70 * your requirements must be present. The default password encoder only supports SHA, that is
71 * insecure.
72 */
73 protected String passwordAttribute;
74
75 /**
76 * The password last set attribute (like 'pwdLastSet') can be used to activate the remember-me
77 * functionality.
78 */
79 protected String passwordLastSetAttribute;
80
81 /**
82 * The filter to find the user. If it is empty, it will be generated from {@code userObjectClass}
83 * and {@code usernameAttribute} like this {@code (&(objectClass=inetOrgPerson)(uid={0}))}.
84 */
85 protected String userFindOneFilter;
86
87 /**
88 * The scope to find a user. Default is 'one level'.
89 */
90 protected SearchScope userFindOneSearchScope;
91
92 /**
93 * The first name attribute of the user. Default is 'givenName'.
94 */
95 protected String firstNameAttribute;
96
97 /**
98 * The last name attribute of the user. Default is 'sn'.
99 */
100 protected String lastNameAttribute;
101
102 /**
103 * The email attribute of the user. Default is 'mail';
104 */
105 protected String emailAttribute;
106
107 /**
108 * The account control evaluator.
109 */
110 protected AccountControlEvaluatorProperty accountControlEvaluator;
111
112 /**
113 * The group fetch strategy.
114 */
115 protected GroupFetchStrategy groupFetchStrategy;
116
117 /**
118 * The member attribute.
119 */
120 protected String memberAttribute;
121
122 /**
123 * The group base dn (like 'ou=groups,dc=example,dc=org'). It's only required, if
124 * {@code groupFetchStrategy} is set to {@code GROUP_CONTAINS_USERS}.
125 */
126 protected String groupBaseDn;
127
128 /**
129 * The group search scope. It's only required, if {@code groupFetchStrategy} is set to
130 * {@code GROUP_CONTAINS_USERS},
131 */
132 protected SearchScope groupSearchScope;
133
134 /**
135 * The group object class. It's only required, if {@code groupFetchStrategy} is set to
136 * {@code GROUP_CONTAINS_USERS}
137 */
138 protected String groupObjectClass;
139
140 /**
141 * The group id attribute. It's only required, if {@code groupFetchStrategy} is set to
142 * {@code GROUP_CONTAINS_USERS}
143 */
144 protected String groupIdAttribute;
145
146 /**
147 * The group member attribute. It's only required, if {@code groupFetchStrategy} is set to
148 * {@code GROUP_CONTAINS_USERS}
149 */
150 protected String groupMemberAttribute;
151
152 /**
153 * The group member format. It's only required, if {@code groupFetchStrategy} is set to
154 * {@code GROUP_CONTAINS_USERS}
155 */
156 protected String groupMemberFormat;
157
158 /**
159 * The role mappings.
160 */
161 protected List<RoleMapping> roleMapping;
162
163 /**
164 * The default roles.
165 */
166 protected List<String> defaultRoles;
167
168 /**
169 * The role prefix (like 'ROLE_').
170 */
171 protected String rolePrefix;
172
173 /**
174 * The role case transformation.
175 */
176 protected CaseTransformation roleCaseTransformation;
177
178 /**
179 * The string replacements for roles.
180 */
181 protected List<StringReplacement> roleStringReplacements;
182
183 /**
184 * Instantiates new ldaptive authentication properties.
185 */
186 public LdaptiveAuthenticationProperties() {
187 super();
188 }
189
190 /**
191 * To role mappings map.
192 *
193 * @return the map
194 */
195 public Map<String, String> toRoleMappings() {
196 return Stream.ofNullable(getRoleMapping())
197 .flatMap(Collection::stream)
198 .collect(Collectors.toMap(
199 RoleMapping::getSource,
200 RoleMapping::getTarget,
201 (first, second) -> first,
202 LinkedHashMap::new));
203 }
204
205 /**
206 * To role string replacements map.
207 *
208 * @return the map
209 */
210 public Map<String, String> toRoleStringReplacements() {
211 return Stream.ofNullable(getRoleStringReplacements())
212 .flatMap(Collection::stream)
213 .collect(Collectors.toMap(
214 StringReplacement::getRegex,
215 StringReplacement::getReplacement,
216 (first, second) -> first,
217 LinkedHashMap::new));
218 }
219
220 /**
221 * The ldaptive authentication properties with defaults.
222 */
223 public static class WithDefaults extends LdaptiveAuthenticationProperties {
224
225 @Serial
226 private static final long serialVersionUID = 1L;
227
228 /**
229 * Instantiates a new ldaptive authentication properties with defaults.
230 */
231 public WithDefaults() {
232 refusedUsernames = new ArrayList<>();
233
234 userObjectClass = "inetOrgPerson";
235 usernameAttribute = "uid";
236
237 userFindOneSearchScope = SearchScope.ONELEVEL;
238 firstNameAttribute = "givenName";
239 lastNameAttribute = "sn";
240 emailAttribute = "mail";
241 memberAttribute = "memberOf";
242 accountControlEvaluator = AccountControlEvaluatorProperty.NONE;
243
244 groupFetchStrategy = GroupFetchStrategy.USER_CONTAINS_GROUPS;
245 groupObjectClass = "groupOfUniqueNames";
246 groupMemberAttribute = "uniqueMember";
247 groupSearchScope = SearchScope.ONELEVEL;
248 roleMapping = new ArrayList<>();
249 defaultRoles = new ArrayList<>();
250 roleStringReplacements = new ArrayList<>();
251 roleCaseTransformation = CaseTransformation.NONE;
252 }
253
254 /**
255 * Get user find one filter.
256 *
257 * @return the user find one filter
258 */
259 @Override
260 public String getUserFindOneFilter() {
261 if (ObjectUtils.isEmpty(userFindOneFilter)
262 && !ObjectUtils.isEmpty(getUserObjectClass())
263 && !ObjectUtils.isEmpty(getUsernameAttribute())) {
264 return String
265 .format("(&(objectClass=%s)(%s={0}))", getUserObjectClass(), getUsernameAttribute());
266 }
267 return userFindOneFilter;
268 }
269
270 }
271
272 /**
273 * The group fetch strategy.
274 */
275 public enum GroupFetchStrategy {
276
277 /**
278 * Groups will not be fetched.
279 */
280 NONE,
281
282 /**
283 * User contains groups group-fetch strategy.
284 */
285 USER_CONTAINS_GROUPS,
286
287 /**
288 * Group contains users group-fetch strategy.
289 */
290 GROUP_CONTAINS_USERS
291 }
292
293 /**
294 * The string replacement.
295 */
296 @Data
297 @AllArgsConstructor
298 public static class StringReplacement implements Serializable {
299
300 @Serial
301 private static final long serialVersionUID = 1L;
302
303 /**
304 * The regular expression to which the string is to be matched. '{@code [- ]}' for example would
305 * replace every '-' and every space.
306 */
307 private String regex;
308
309 /**
310 * The string to be substituted for each match.
311 */
312 private String replacement;
313
314 /**
315 * Instantiates a new string replacement.
316 */
317 public StringReplacement() {
318 super();
319 }
320 }
321
322 /**
323 * The role mapping.
324 */
325 @Data
326 @AllArgsConstructor
327 public static class RoleMapping implements Serializable {
328
329 @Serial
330 private static final long serialVersionUID = 1L;
331
332 /**
333 * The value from the ldap (like 'developers').
334 */
335 private String source;
336
337 /**
338 * The value in the spring security context (like 'ROLE_DEVELOPER').
339 */
340 private String target;
341
342 /**
343 * Instantiates a new role mapping.
344 */
345 public RoleMapping() {
346 super();
347 }
348 }
349
350 }