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