View Javadoc
1   /*
2    * Copyright 2021 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.security.core.userdetails;
18  
19  import static org.bremersee.data.ldaptive.LdaptiveEntryMapper.getAttributeValue;
20  import static org.bremersee.data.ldaptive.LdaptiveEntryMapper.getAttributeValuesAsSet;
21  import static org.bremersee.data.ldaptive.transcoder.UserAccountControlValueTranscoder.isUserAccountEnabled;
22  
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.stream.Collectors;
29  import lombok.AccessLevel;
30  import lombok.Getter;
31  import lombok.ToString;
32  import org.bremersee.data.ldaptive.LdaptiveEntryMapper;
33  import org.bremersee.data.ldaptive.transcoder.UserAccountControlValueTranscoder;
34  import org.ldaptive.AttributeModification;
35  import org.ldaptive.LdapEntry;
36  import org.ldaptive.transcode.AbstractStringValueTranscoder;
37  import org.ldaptive.transcode.ValueTranscoder;
38  import org.springframework.security.core.GrantedAuthority;
39  import org.springframework.security.core.authority.SimpleGrantedAuthority;
40  import org.springframework.security.core.userdetails.User;
41  import org.springframework.security.core.userdetails.UserDetails;
42  import org.springframework.util.StringUtils;
43  
44  /**
45   * The user details mapper.
46   *
47   * @author Christian Bremer
48   */
49  @ToString
50  public class UserDetailsLdapMapper implements LdaptiveEntryMapper<UserDetails> {
51  
52    @Getter(value = AccessLevel.PROTECTED)
53    private final String userName;
54  
55    @Getter(value = AccessLevel.PROTECTED)
56    private final String userAccountControlAttributeName;
57  
58    @Getter(value = AccessLevel.PROTECTED)
59    private final List<String> authorities;
60  
61    @Getter(value = AccessLevel.PROTECTED)
62    private final String authorityAttributeName;
63  
64    @Getter(value = AccessLevel.PROTECTED)
65    private final String authorityPrefix;
66  
67    @Getter(value = AccessLevel.PROTECTED)
68    private final ValueTranscoder<GrantedAuthority> authorityTranscoder;
69  
70    @Getter(value = AccessLevel.PROTECTED)
71    private final UserAccountControlValueTranscoder userAccountControlValueTranscoder;
72  
73    /**
74     * Instantiates a new user details mapper.
75     *
76     * @param userName the user name
77     * @param userAccountControlAttributeName the user account control attribute name
78     * @param authorities the authorities
79     * @param authorityAttributeName the authority attribute name
80     * @param authorityDn the authority dn
81     * @param authorityMap the authority map
82     * @param authorityPrefix the authority prefix
83     */
84    public UserDetailsLdapMapper(
85        String userName,
86        String userAccountControlAttributeName,
87        List<String> authorities,
88        String authorityAttributeName,
89        boolean authorityDn,
90        Map<String, String> authorityMap,
91        String authorityPrefix) {
92  
93      this.userName = userName;
94      this.userAccountControlAttributeName = userAccountControlAttributeName;
95      this.authorities = authorities != null ? authorities : Collections.emptyList();
96      this.authorityAttributeName = authorityAttributeName;
97      this.authorityPrefix = authorityPrefix;
98      this.authorityTranscoder = new GrantedAuthorityValueTranscoder(authorityDn, authorityMap, authorityPrefix);
99      if (StringUtils.hasText(userAccountControlAttributeName)) {
100       userAccountControlValueTranscoder = new UserAccountControlValueTranscoder();
101     } else {
102       userAccountControlValueTranscoder = null;
103     }
104   }
105 
106   @Override
107   public String[] getObjectClasses() {
108     return null;
109   }
110 
111   @Override
112   public String mapDn(UserDetails domainObject) {
113     return null;
114   }
115 
116   @Override
117   public UserDetails map(LdapEntry ldapEntry) {
118     return new User(
119         userName,
120         userName,
121         isAccountEnabled(ldapEntry),
122         isAccountNonExpired(ldapEntry),
123         isCredentialsNonExpired(ldapEntry),
124         isAccountNonLocked(ldapEntry),
125         getGrantedAuthorities(ldapEntry));
126   }
127 
128   @Override
129   public void map(LdapEntry source, UserDetails destination) {
130     throw new UnsupportedOperationException("User details are unmodifiable.");
131   }
132 
133   @Override
134   public AttributeModification[] mapAndComputeModifications(UserDetails source, LdapEntry destination) {
135     return new AttributeModification[0];
136   }
137 
138   /**
139    * Determines whether the account is enabled or not.
140    *
141    * @param ldapEntry the ldap entry
142    * @return the boolean
143    */
144   protected boolean isAccountEnabled(LdapEntry ldapEntry) {
145     return !StringUtils.hasText(getUserAccountControlAttributeName()) || isUserAccountEnabled(getAttributeValue(
146         ldapEntry, getUserAccountControlAttributeName(), getUserAccountControlValueTranscoder(), null));
147   }
148 
149   /**
150    * Determines whether the account is not expired.
151    *
152    * @param ldapEntry the ldap entry
153    * @return the boolean
154    */
155   protected boolean isAccountNonExpired(@SuppressWarnings("unused") LdapEntry ldapEntry) {
156     return true;
157   }
158 
159   /**
160    * Determines whether the account credentials are not expired.
161    *
162    * @param ldapEntry the ldap entry
163    * @return the boolean
164    */
165   protected boolean isCredentialsNonExpired(@SuppressWarnings("unused") LdapEntry ldapEntry) {
166     return true;
167   }
168 
169   /**
170    * Determines whether the account is not locked.
171    *
172    * @param ldapEntry the ldap entry
173    * @return the boolean
174    */
175   protected boolean isAccountNonLocked(@SuppressWarnings("unused") LdapEntry ldapEntry) {
176     return true;
177   }
178 
179   /**
180    * Gets granted authorities.
181    *
182    * @param ldapEntry the ldap entry
183    * @return the granted authorities
184    */
185   protected Collection<? extends GrantedAuthority> getGrantedAuthorities(LdapEntry ldapEntry) {
186     Set<GrantedAuthority> grantedAuthorities = getAuthorities().stream()
187         .map(value -> UserDetailsLdapMapper.prefixAuthority(getAuthorityPrefix(), value))
188         .map(SimpleGrantedAuthority::new)
189         .collect(Collectors.toSet());
190     if (StringUtils.hasText(getAuthorityAttributeName())) {
191       grantedAuthorities.addAll(
192           getAttributeValuesAsSet(ldapEntry, getAuthorityAttributeName(), getAuthorityTranscoder()));
193     }
194     return grantedAuthorities;
195   }
196 
197   /**
198    * Prefix authority.
199    *
200    * @param prefix the prefix
201    * @param value the value
202    * @return the string
203    */
204   protected static String prefixAuthority(String prefix, String value) {
205     return StringUtils.hasText(prefix) && !value.startsWith(prefix) ? prefix + value : value;
206   }
207 
208   /**
209    * The granted authority value transcoder.
210    *
211    * @author Christian Bremer
212    */
213   @ToString
214   protected static class GrantedAuthorityValueTranscoder extends AbstractStringValueTranscoder<GrantedAuthority> {
215 
216     @Getter(value = AccessLevel.PROTECTED)
217     private final boolean authorityDn;
218 
219     @Getter(value = AccessLevel.PROTECTED)
220     private final Map<String, String> authorityMap;
221 
222     @Getter(value = AccessLevel.PROTECTED)
223     private final String authorityPrefix;
224 
225     /**
226      * Instantiates a new granted authority value transcoder.
227      *
228      * @param authorityDn the authority dn
229      * @param authorityMap the authority map
230      * @param authorityPrefix the authority prefix
231      */
232     public GrantedAuthorityValueTranscoder(
233         boolean authorityDn,
234         Map<String, String> authorityMap,
235         String authorityPrefix) {
236       this.authorityDn = authorityDn;
237       this.authorityMap = authorityMap != null ? authorityMap : Collections.emptyMap();
238       this.authorityPrefix = authorityPrefix;
239     }
240 
241     @Override
242     public GrantedAuthority decodeStringValue(String value) {
243       String ldapValue = isAuthorityDn() ? LdaptiveEntryMapper.getRdn(value) : value;
244       String mappedValue = getAuthorityMap().getOrDefault(ldapValue, ldapValue);
245       String authorityValue = UserDetailsLdapMapper.prefixAuthority(getAuthorityPrefix(), mappedValue);
246       return new SimpleGrantedAuthority(authorityValue);
247     }
248 
249     @Override
250     public String encodeStringValue(GrantedAuthority value) {
251       throw new UnsupportedOperationException("Getting ldap attribute value from granted authority is not supported.");
252     }
253 
254     @Override
255     public Class<GrantedAuthority> getType() {
256       return GrantedAuthority.class;
257     }
258   }
259 
260 }