Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ActiveDirectoryRealm |
|
| 2.7142857142857144;2.714 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one | |
3 | * or more contributor license agreements. See the NOTICE file | |
4 | * distributed with this work for additional information | |
5 | * regarding copyright ownership. The ASF licenses this file | |
6 | * to you under the Apache License, Version 2.0 (the | |
7 | * "License"); you may not use this file except in compliance | |
8 | * with the License. You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, | |
13 | * software distributed under the License is distributed on an | |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | * KIND, either express or implied. See the License for the | |
16 | * specific language governing permissions and limitations | |
17 | * under the License. | |
18 | */ | |
19 | package org.apache.shiro.realm.activedirectory; | |
20 | ||
21 | import org.apache.shiro.authc.AuthenticationInfo; | |
22 | import org.apache.shiro.authc.AuthenticationToken; | |
23 | import org.apache.shiro.authc.SimpleAuthenticationInfo; | |
24 | import org.apache.shiro.authc.UsernamePasswordToken; | |
25 | import org.apache.shiro.authz.AuthorizationInfo; | |
26 | import org.apache.shiro.authz.SimpleAuthorizationInfo; | |
27 | import org.apache.shiro.realm.Realm; | |
28 | import org.apache.shiro.realm.ldap.AbstractLdapRealm; | |
29 | import org.apache.shiro.realm.ldap.LdapContextFactory; | |
30 | import org.apache.shiro.realm.ldap.LdapUtils; | |
31 | import org.apache.shiro.subject.PrincipalCollection; | |
32 | import org.slf4j.Logger; | |
33 | import org.slf4j.LoggerFactory; | |
34 | ||
35 | import javax.naming.NamingEnumeration; | |
36 | import javax.naming.NamingException; | |
37 | import javax.naming.directory.Attribute; | |
38 | import javax.naming.directory.Attributes; | |
39 | import javax.naming.directory.SearchControls; | |
40 | import javax.naming.directory.SearchResult; | |
41 | import javax.naming.ldap.LdapContext; | |
42 | import java.util.*; | |
43 | ||
44 | ||
45 | /** | |
46 | * A {@link Realm} that authenticates with an active directory LDAP | |
47 | * server to determine the roles for a particular user. This implementation | |
48 | * queries for the user's groups and then maps the group names to roles using the | |
49 | * {@link #groupRolesMap}. | |
50 | * | |
51 | * @since 0.1 | |
52 | */ | |
53 | 1 | public class ActiveDirectoryRealm extends AbstractLdapRealm { |
54 | ||
55 | //TODO - complete JavaDoc | |
56 | ||
57 | /*-------------------------------------------- | |
58 | | C O N S T A N T S | | |
59 | ============================================*/ | |
60 | ||
61 | 1 | private static final Logger log = LoggerFactory.getLogger(ActiveDirectoryRealm.class); |
62 | ||
63 | private static final String ROLE_NAMES_DELIMETER = ","; | |
64 | ||
65 | /*-------------------------------------------- | |
66 | | I N S T A N C E V A R I A B L E S | | |
67 | ============================================*/ | |
68 | ||
69 | /** | |
70 | * Mapping from fully qualified active directory | |
71 | * group names (e.g. CN=Group,OU=Company,DC=MyDomain,DC=local) | |
72 | * as returned by the active directory LDAP server to role names. | |
73 | */ | |
74 | private Map<String, String> groupRolesMap; | |
75 | ||
76 | /*-------------------------------------------- | |
77 | | C O N S T R U C T O R S | | |
78 | ============================================*/ | |
79 | ||
80 | public void setGroupRolesMap(Map<String, String> groupRolesMap) { | |
81 | 0 | this.groupRolesMap = groupRolesMap; |
82 | 0 | } |
83 | ||
84 | /*-------------------------------------------- | |
85 | | M E T H O D S | | |
86 | ============================================*/ | |
87 | ||
88 | ||
89 | /** | |
90 | * Builds an {@link AuthenticationInfo} object by querying the active directory LDAP context for the | |
91 | * specified username. This method binds to the LDAP server using the provided username and password - | |
92 | * which if successful, indicates that the password is correct. | |
93 | * <p/> | |
94 | * This method can be overridden by subclasses to query the LDAP server in a more complex way. | |
95 | * | |
96 | * @param token the authentication token provided by the user. | |
97 | * @param ldapContextFactory the factory used to build connections to the LDAP server. | |
98 | * @return an {@link AuthenticationInfo} instance containing information retrieved from LDAP. | |
99 | * @throws NamingException if any LDAP errors occur during the search. | |
100 | */ | |
101 | protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException { | |
102 | ||
103 | 0 | UsernamePasswordToken upToken = (UsernamePasswordToken) token; |
104 | ||
105 | // Binds using the username and password provided by the user. | |
106 | 0 | LdapContext ctx = null; |
107 | try { | |
108 | 0 | ctx = ldapContextFactory.getLdapContext(upToken.getUsername(), String.valueOf(upToken.getPassword())); |
109 | } finally { | |
110 | 0 | LdapUtils.closeContext(ctx); |
111 | 0 | } |
112 | ||
113 | 0 | return buildAuthenticationInfo(upToken.getUsername(), upToken.getPassword()); |
114 | } | |
115 | ||
116 | protected AuthenticationInfo buildAuthenticationInfo(String username, char[] password) { | |
117 | 0 | return new SimpleAuthenticationInfo(username, password, getName()); |
118 | } | |
119 | ||
120 | ||
121 | /** | |
122 | * Builds an {@link org.apache.shiro.authz.AuthorizationInfo} object by querying the active directory LDAP context for the | |
123 | * groups that a user is a member of. The groups are then translated to role names by using the | |
124 | * configured {@link #groupRolesMap}. | |
125 | * <p/> | |
126 | * This implementation expects the <tt>principal</tt> argument to be a String username. | |
127 | * <p/> | |
128 | * Subclasses can override this method to determine authorization data (roles, permissions, etc) in a more | |
129 | * complex way. Note that this default implementation does not support permissions, only roles. | |
130 | * | |
131 | * @param principals the principal of the Subject whose account is being retrieved. | |
132 | * @param ldapContextFactory the factory used to create LDAP connections. | |
133 | * @return the AuthorizationInfo for the given Subject principal. | |
134 | * @throws NamingException if an error occurs when searching the LDAP server. | |
135 | */ | |
136 | protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory) throws NamingException { | |
137 | ||
138 | 0 | String username = (String) getAvailablePrincipal(principals); |
139 | ||
140 | // Perform context search | |
141 | 0 | LdapContext ldapContext = ldapContextFactory.getSystemLdapContext(); |
142 | ||
143 | Set<String> roleNames; | |
144 | ||
145 | try { | |
146 | 0 | roleNames = getRoleNamesForUser(username, ldapContext); |
147 | } finally { | |
148 | 0 | LdapUtils.closeContext(ldapContext); |
149 | 0 | } |
150 | ||
151 | 0 | return buildAuthorizationInfo(roleNames); |
152 | } | |
153 | ||
154 | protected AuthorizationInfo buildAuthorizationInfo(Set<String> roleNames) { | |
155 | 0 | return new SimpleAuthorizationInfo(roleNames); |
156 | } | |
157 | ||
158 | private Set<String> getRoleNamesForUser(String username, LdapContext ldapContext) throws NamingException { | |
159 | Set<String> roleNames; | |
160 | 0 | roleNames = new LinkedHashSet<String>(); |
161 | ||
162 | 0 | SearchControls searchCtls = new SearchControls(); |
163 | 0 | searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); |
164 | ||
165 | 0 | String userPrincipalName = username; |
166 | 0 | if (principalSuffix != null) { |
167 | 0 | userPrincipalName += principalSuffix; |
168 | } | |
169 | ||
170 | //SHIRO-115 - prevent potential code injection: | |
171 | 0 | String searchFilter = "(&(objectClass=*)(userPrincipalName={0}))"; |
172 | 0 | Object[] searchArguments = new Object[]{userPrincipalName}; |
173 | ||
174 | 0 | NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, searchCtls); |
175 | ||
176 | 0 | while (answer.hasMoreElements()) { |
177 | 0 | SearchResult sr = (SearchResult) answer.next(); |
178 | ||
179 | 0 | if (log.isDebugEnabled()) { |
180 | 0 | log.debug("Retrieving group names for user [" + sr.getName() + "]"); |
181 | } | |
182 | ||
183 | 0 | Attributes attrs = sr.getAttributes(); |
184 | ||
185 | 0 | if (attrs != null) { |
186 | 0 | NamingEnumeration ae = attrs.getAll(); |
187 | 0 | while (ae.hasMore()) { |
188 | 0 | Attribute attr = (Attribute) ae.next(); |
189 | ||
190 | 0 | if (attr.getID().equals("memberOf")) { |
191 | ||
192 | 0 | Collection<String> groupNames = LdapUtils.getAllAttributeValues(attr); |
193 | ||
194 | 0 | if (log.isDebugEnabled()) { |
195 | 0 | log.debug("Groups found for user [" + username + "]: " + groupNames); |
196 | } | |
197 | ||
198 | 0 | Collection<String> rolesForGroups = getRoleNamesForGroups(groupNames); |
199 | 0 | roleNames.addAll(rolesForGroups); |
200 | } | |
201 | 0 | } |
202 | } | |
203 | 0 | } |
204 | 0 | return roleNames; |
205 | } | |
206 | ||
207 | /** | |
208 | * This method is called by the default implementation to translate Active Directory group names | |
209 | * to role names. This implementation uses the {@link #groupRolesMap} to map group names to role names. | |
210 | * | |
211 | * @param groupNames the group names that apply to the current user. | |
212 | * @return a collection of roles that are implied by the given role names. | |
213 | */ | |
214 | protected Collection<String> getRoleNamesForGroups(Collection<String> groupNames) { | |
215 | 0 | Set<String> roleNames = new HashSet<String>(groupNames.size()); |
216 | ||
217 | 0 | if (groupRolesMap != null) { |
218 | 0 | for (String groupName : groupNames) { |
219 | 0 | String strRoleNames = groupRolesMap.get(groupName); |
220 | 0 | if (strRoleNames != null) { |
221 | 0 | for (String roleName : strRoleNames.split(ROLE_NAMES_DELIMETER)) { |
222 | ||
223 | 0 | if (log.isDebugEnabled()) { |
224 | 0 | log.debug("User is member of group [" + groupName + "] so adding role [" + roleName + "]"); |
225 | } | |
226 | ||
227 | 0 | roleNames.add(roleName); |
228 | ||
229 | } | |
230 | } | |
231 | 0 | } |
232 | } | |
233 | 0 | return roleNames; |
234 | } | |
235 | ||
236 | } |