View Javadoc
1   /*
2    * Copyright 2024 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.boot.autoconfigure.security.authentication;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.LinkedHashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.stream.Collectors;
25  import java.util.stream.Stream;
26  import lombok.Data;
27  import org.springframework.boot.context.properties.ConfigurationProperties;
28  
29  /**
30   * The authentication properties.
31   *
32   * @author Christian Bremer
33   */
34  @ConfigurationProperties(prefix = "bremersee.authentication")
35  @Data
36  public class AuthenticationProperties {
37  
38    /**
39     * The remember-me properties.
40     */
41    private RememberMeProperties rememberMe = new RememberMeProperties();
42  
43    /**
44     * The jwt converter properties.
45     */
46    private JwtConverterProperties jwtConverter = new JwtConverterProperties();
47  
48    /**
49     * The ldaptive properties.
50     */
51    private LdaptiveProperties ldaptive = new LdaptiveProperties();
52  
53    /**
54     * Instantiates new authentication properties.
55     */
56    public AuthenticationProperties() {
57      super();
58    }
59  
60    /**
61     * The remember-me properties.
62     *
63     * @author Christian Bremer
64     */
65    @Data
66    public static class RememberMeProperties {
67  
68      /**
69       * The key.
70       */
71      private String key;
72  
73      /**
74       * Specifies whether remember-me is always activated.
75       */
76      private Boolean alwaysRemember;
77  
78      /**
79       * The cookie name.
80       */
81      private String cookieName;
82  
83      /**
84       * The cookie domain.
85       */
86      private String cookieDomain;
87  
88      /**
89       * Specifies whether to use secure cookie.
90       */
91      private Boolean useSecureCookie;
92  
93      /**
94       * The parameter name (default remember-me).
95       */
96      private String parameterName;
97  
98      /**
99       * The token validity in seconds (default two weeks).
100      */
101     private Integer tokenValiditySeconds;
102 
103     /**
104      * Instantiates new remember-me properties.
105      */
106     public RememberMeProperties() {
107       super();
108     }
109   }
110 
111   /**
112    * The jwt converter properties.
113    */
114   @Data
115   public static class JwtConverterProperties {
116 
117     /**
118      * The json path to the username.
119      */
120     private String nameJsonPath = "$.sub"; // keycloak: $.preferred_username
121 
122     /**
123      * The json path to the first name.
124      */
125     private String firstNameJsonPath = "$.given_name";
126 
127     /**
128      * The json path to the last name.
129      */
130     private String lastNameJsonPath = "$.family_name";
131 
132     /**
133      * The json path to the email.
134      */
135     private String emailJsonPath = "$.email";
136 
137     /**
138      * The json path to the roles.
139      */
140     private String rolesJsonPath = "$.scope"; // keycloak: $.realm_access.roles
141 
142     /**
143      * Specifies whether the roles are represented as a json array or as a list separated by
144      * {@link #getRolesValueSeparator()}.
145      */
146     private boolean rolesValueList = false; // keycloak: true
147 
148     /**
149      * The roles separator to use if {@link #isRolesValueList()} is set to {@code false}.
150      */
151     private String rolesValueSeparator = " ";
152 
153     /**
154      * The default roles.
155      */
156     private List<String> defaultRoles = new ArrayList<>();
157 
158     /**
159      * The role mappings.
160      */
161     private List<RoleMapping> roleMapping = new ArrayList<>();
162 
163     /**
164      * The role prefix (like 'ROLE_' or 'SCOPE_').
165      */
166     private String rolePrefix = "SCOPE_"; // keycloak: ROLE_
167 
168     /**
169      * The role case transformation.
170      */
171     private CaseTransformation roleCaseTransformation;
172 
173     /**
174      * The string replacements for roles.
175      */
176     private List<StringReplacement> roleStringReplacements;
177 
178     /**
179      * Instantiates new jwt converter properties.
180      */
181     public JwtConverterProperties() {
182       super();
183     }
184 
185     /**
186      * To role mappings map.
187      *
188      * @return the map
189      */
190     public Map<String, String> toRoleMappings() {
191       return Stream.ofNullable(getRoleMapping())
192           .flatMap(Collection::stream)
193           .collect(Collectors.toMap(
194               RoleMapping::getSource,
195               RoleMapping::getTarget,
196               (first, second) -> first,
197               LinkedHashMap::new));
198     }
199 
200     /**
201      * To role string replacements map.
202      *
203      * @return the map
204      */
205     public Map<String, String> toRoleStringReplacements() {
206       return Stream.ofNullable(getRoleStringReplacements())
207           .flatMap(Collection::stream)
208           .collect(Collectors.toMap(
209               StringReplacement::getRegex,
210               StringReplacement::getReplacement,
211               (first, second) -> first,
212               LinkedHashMap::new));
213     }
214 
215   }
216 
217   /**
218    * The ldaptive properties.
219    */
220   @Data
221   public static class LdaptiveProperties {
222 
223     /**
224      * The ldap template with default configuration properties.
225      */
226     private Template template = Template.ACTIVE_DIRECTORY;
227 
228     /**
229      * The username to bind dn converter property.
230      */
231     private UsernameToBindDnConverterProperty usernameToBindDnConverter;
232 
233     /**
234      * The user base dn (like 'ou=people,dc=example,dc=org'). This value is always required.
235      */
236     private String userBaseDn;
237 
238     /**
239      * A list with refused usernames.
240      */
241     private List<String> refusedUsernames;
242 
243     /**
244      * The object class of the user (like 'inetOrgPerson'). The selected template contains a
245      * default.
246      */
247     private String userObjectClass;
248 
249     /**
250      * The username attribute of the user (like 'uid' or 'sAMAccountName'). The selected template
251      * contains a default.
252      */
253     private String usernameAttribute;
254 
255     /**
256      * Applies only for simple bind. The rdn attribute of the user. This is normally the same as the
257      * username attribute.
258      */
259     private String userRdnAttribute;
260 
261     /**
262      * The password attribute of the user (like 'userPassword'). If it is empty, a simple user bind
263      * will be done with the credentials of the user for authentication. If it is present, the
264      * connection to the ldap server must be done by a 'global' user and a password encoder that
265      * fits your requirements must be present. The default password encoder only supports SHA, that
266      * is insecure.
267      */
268     private String passwordAttribute;
269 
270     /**
271      * The password last set attribute (like 'pwdLastSet') can be used to activate the remember-me
272      * functionality.
273      */
274     private String passwordLastSetAttribute;
275 
276     /**
277      * The filter to find the user. If it is empty, it will be generated from 'userObjectClass' and
278      * 'usernameAttribute' like this '(&(objectClass=inetOrgPerson)(uid={0}))'.
279      */
280     private String userFindOneFilter;
281 
282     /**
283      * The scope to find a user. Default is 'one level'.
284      */
285     private SearchScope userFindOneSearchScope;
286 
287     /**
288      * The first name attribute of the user. Default is 'givenName'.
289      */
290     protected String firstNameAttribute;
291 
292     /**
293      * The last name attribute of the user. Default is 'sn'.
294      */
295     protected String lastNameAttribute;
296 
297     /**
298      * The email attribute of the user. Default is 'mail';
299      */
300     private String emailAttribute;
301 
302     /**
303      * The account control evaluator.
304      */
305     private AccountControlEvaluatorProperty accountControlEvaluator;
306 
307     /**
308      * The group fetch strategy.
309      */
310     private GroupFetchStrategy groupFetchStrategy;
311 
312     /**
313      * The member attribute.
314      */
315     private String memberAttribute;
316 
317     /**
318      * The group base dn (like 'ou=groups,dc=example,dc=org'). It's only required, if
319      * {@code groupFetchStrategy} is set to {@code GROUP_CONTAINS_USERS}.
320      */
321     private String groupBaseDn;
322 
323     /**
324      * The group search scope. It's only required, if {@code groupFetchStrategy} is set to
325      * {@code GROUP_CONTAINS_USERS},
326      */
327     private SearchScope groupSearchScope;
328 
329     /**
330      * The group object class. It's only required, if {@code groupFetchStrategy} is set to
331      * {@code GROUP_CONTAINS_USERS}
332      */
333     private String groupObjectClass;
334 
335     /**
336      * The group id attribute. It's only required, if {@code groupFetchStrategy} is set to
337      * {@code GROUP_CONTAINS_USERS}
338      */
339     private String groupIdAttribute;
340 
341     /**
342      * The group member attribute. It's only required, if {@code groupFetchStrategy} is set to
343      * {@code GROUP_CONTAINS_USERS}
344      */
345     private String groupMemberAttribute;
346 
347     /**
348      * The group member format. It's only required, if {@code groupFetchStrategy} is set to
349      * {@code GROUP_CONTAINS_USERS}
350      */
351     private String groupMemberFormat;
352 
353     /**
354      * The role mappings.
355      */
356     private List<RoleMapping> roleMapping;
357 
358     /**
359      * The default roles.
360      */
361     private List<String> defaultRoles;
362 
363     /**
364      * The role prefix (like 'ROLE_').
365      */
366     private String rolePrefix;
367 
368     /**
369      * The role case transformation.
370      */
371     private CaseTransformation roleCaseTransformation;
372 
373     /**
374      * The string replacements for roles.
375      */
376     private List<StringReplacement> roleStringReplacements;
377 
378     /**
379      * Instantiates new ldaptive properties.
380      */
381     public LdaptiveProperties() {
382       super();
383     }
384 
385     /**
386      * The search scope.
387      */
388     public enum SearchScope {
389 
390       /**
391        * Base object search.
392        */
393       OBJECT,
394 
395       /**
396        * Single level search.
397        */
398       ONELEVEL,
399 
400       /**
401        * Whole subtree search.
402        */
403       SUBTREE,
404 
405       /**
406        * Subordinate subtree search. See draft-sermersheim-ldap-subordinate-scope.
407        */
408       SUBORDINATE
409     }
410 
411     /**
412      * The username to bind dn converter property.
413      */
414     public enum UsernameToBindDnConverterProperty {
415 
416       /**
417        * By user rdn attribute username to bind dn converter property.
418        */
419       BY_USER_RDN_ATTRIBUTE,
420 
421       /**
422        * By domain email username to bind dn converter property.
423        */
424       BY_DOMAIN_EMAIL
425     }
426 
427     /**
428      * The account control evaluator property.
429      */
430     public enum AccountControlEvaluatorProperty {
431 
432       /**
433        * The None.
434        */
435       NONE,
436 
437       /**
438        * The Active directory.
439        */
440       ACTIVE_DIRECTORY
441     }
442 
443     /**
444      * The group fetch strategy.
445      */
446     public enum GroupFetchStrategy {
447 
448       /**
449        * Groups will not be fetched.
450        */
451       NONE,
452 
453       /**
454        * User contains groups group-fetch strategy.
455        */
456       USER_CONTAINS_GROUPS,
457 
458       /**
459        * Group contains users group-fetch strategy.
460        */
461       GROUP_CONTAINS_USERS
462     }
463 
464     /**
465      * The templates for ldap authentication.
466      *
467      * @author Christian Bremer
468      */
469     public enum Template {
470 
471       /**
472        * Active directory template.
473        */
474       ACTIVE_DIRECTORY,
475 
476       /**
477        * Open ldap template.
478        */
479       OPEN_LDAP,
480 
481       /**
482        * User contains groups template.
483        */
484       USER_CONTAINS_GROUPS,
485 
486       /**
487        * Group contains users template.
488        */
489       GROUP_CONTAINS_USERS
490     }
491   }
492 
493   /**
494    * The role mapping.
495    */
496   @Data
497   public static class RoleMapping {
498 
499     private String source;
500 
501     private String target;
502 
503     /**
504      * Instantiates a new role mapping.
505      */
506     public RoleMapping() {
507       super();
508     }
509   }
510 
511   /**
512    * The case transformation.
513    */
514   public enum CaseTransformation {
515 
516     /**
517      * None case transformation.
518      */
519     NONE,
520 
521     /**
522      * To upper case transformation.
523      */
524     TO_UPPER_CASE,
525 
526     /**
527      * To lower case transformation.
528      */
529     TO_LOWER_CASE
530   }
531 
532   /**
533    * The string replacement.
534    */
535   @Data
536   public static class StringReplacement {
537 
538     /**
539      * The regular expression to which the string is to be matched. '{@code [- ]}' for example would
540      * replace every '-' and every space.
541      */
542     private String regex;
543 
544     /**
545      * The string to be substituted for each match.
546      */
547     private String replacement;
548 
549     /**
550      * Instantiates a new string replacement.
551      */
552     public StringReplacement() {
553       super();
554     }
555   }
556 
557 }