View Javadoc
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 }