View Javadoc

1   /*
2    * Copyright 2014-2014 Hippo B.V. (http://www.onehippo.com)
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  package org.onehippo.forge.security.support.shiro.realm;
17  
18  import java.text.MessageFormat;
19  import java.util.HashSet;
20  import java.util.Set;
21  
22  import javax.jcr.Credentials;
23  import javax.jcr.LoginException;
24  import javax.jcr.Node;
25  import javax.jcr.NodeIterator;
26  import javax.jcr.Repository;
27  import javax.jcr.RepositoryException;
28  import javax.jcr.Session;
29  import javax.jcr.SimpleCredentials;
30  import javax.jcr.query.Query;
31  import javax.jcr.query.QueryResult;
32  
33  import org.apache.shiro.authc.AccountException;
34  import org.apache.shiro.authc.AuthenticationException;
35  import org.apache.shiro.authc.AuthenticationInfo;
36  import org.apache.shiro.authc.AuthenticationToken;
37  import org.apache.shiro.authc.SimpleAuthenticationInfo;
38  import org.apache.shiro.authc.UnknownAccountException;
39  import org.apache.shiro.authc.UsernamePasswordToken;
40  import org.apache.shiro.authz.AuthorizationException;
41  import org.apache.shiro.authz.AuthorizationInfo;
42  import org.apache.shiro.authz.SimpleAuthorizationInfo;
43  import org.apache.shiro.realm.AuthorizingRealm;
44  import org.apache.shiro.subject.PrincipalCollection;
45  import org.hippoecm.hst.site.HstServices;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  /**
50   * Realm that allows authentication and authorization against Hippo Repository security data store.
51   * <p>
52   * This realm supports caching by extending from {@link org.apache.shiro.realm.AuthorizingRealm}.
53   * </p>
54   */
55  public class HippoRepositoryRealm extends AuthorizingRealm {
56  
57      private static final Logger log = LoggerFactory.getLogger(HippoRepositoryRealm.class);
58  
59      private static final String DEFAULT_USER_QUERY = "//hippo:configuration/hippo:users/{0}";
60  
61      private static final String DEFAULT_GROUPS_OF_USER_QUERY = "//element(*, hipposys:group)[(@hipposys:members = ''{0}'' or @hipposys:members = ''*'') and @hipposys:securityprovider = ''internal'']";
62  
63      private static final String DEFAULT_ROLES_OF_USER_AND_GROUP_QUERY = "//hippo:configuration/hippo:domains//element(*, hipposys:authrole)[ @hipposys:users = ''{0}'' {1} ]";
64  
65      private Repository systemRepository;
66  
67      private Credentials systemCreds;
68  
69      private String queryLanguage = Query.XPATH;
70  
71      private String userQuery = DEFAULT_USER_QUERY;
72  
73      private String groupsOfUserQuery = DEFAULT_GROUPS_OF_USER_QUERY;
74  
75      private String rolesOfUserAndGroupQuery = DEFAULT_ROLES_OF_USER_AND_GROUP_QUERY;
76  
77      private String defaultRoleName;
78  
79      private String rolePrefix;
80  
81      private boolean permissionsLookupEnabled;
82  
83      private String defaultPermission;
84  
85      public void setSystemRepository(Repository systemRepository) {
86          this.systemRepository = systemRepository;
87      }
88  
89      public Repository getSystemRepository() {
90          if (systemRepository == null) {
91              systemRepository = HstServices.getComponentManager().getComponent(Repository.class.getName());
92          }
93  
94          return systemRepository;
95      }
96  
97      public void setSystemCredentials(Credentials systemCreds) {
98          this.systemCreds = systemCreds;
99      }
100 
101     public Credentials getSystemCredentials() {
102         if (systemCreds == null) {
103             systemCreds = HstServices.getComponentManager().getComponent(
104                     Credentials.class.getName() + ".hstconfigreader");
105         }
106 
107         return systemCreds;
108     }
109 
110     public void setQueryLanguage(String queryLanguage) {
111         this.queryLanguage = queryLanguage;
112     }
113 
114     public String getQueryLanguage() {
115         return queryLanguage;
116     }
117 
118     public String getUserQuery() {
119         return userQuery;
120     }
121 
122     public void setUserQuery(String userQuery) {
123         this.userQuery = userQuery;
124     }
125 
126     public void setGroupsOfUserQuery(String groupsOfUserQuery) {
127         this.groupsOfUserQuery = groupsOfUserQuery;
128     }
129 
130     public String getGroupsOfUserQuery() {
131         return groupsOfUserQuery;
132     }
133 
134     public void setRolesOfUserAndGroupQuery(String rolesOfUserAndGroupQuery) {
135         this.rolesOfUserAndGroupQuery = rolesOfUserAndGroupQuery;
136     }
137 
138     public String getRolesOfUserAndGroupQuery() {
139         return rolesOfUserAndGroupQuery;
140     }
141 
142     public void setDefaultRoleName(String defaultRoleName) {
143         this.defaultRoleName = defaultRoleName;
144     }
145 
146     public String getDefaultRoleName() {
147         return defaultRoleName;
148     }
149 
150     public String getRolePrefix() {
151         return rolePrefix;
152     }
153 
154     public void setRolePrefix(String rolePrefix) {
155         this.rolePrefix = rolePrefix;
156     }
157 
158     public boolean isPermissionsLookupEnabled() {
159         return permissionsLookupEnabled;
160     }
161 
162     public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) {
163         this.permissionsLookupEnabled = permissionsLookupEnabled;
164     }
165 
166     public String getDefaultPermission() {
167         return defaultPermission;
168     }
169 
170     public void setDefaultPermission(String defaultPermission) {
171         this.defaultPermission = defaultPermission;
172     }
173 
174     @Override
175     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
176         Repository sysRepo = getSystemRepository();
177 
178         if (sysRepo == null) {
179             throw new UnknownAccountException("Hippo Repository is not available now.");
180         }
181 
182         UsernamePasswordToken upToken = (UsernamePasswordToken) token;
183         String username = upToken.getUsername();
184 
185         // Null username is invalid
186         if (username == null) {
187             throw new AccountException("Null usernames are not allowed by this realm.");
188         }
189 
190         char [] passwordChars = upToken.getPassword();
191 
192         SimpleAuthenticationInfo info = null;
193         Session session = null;
194 
195         try {
196             session = sysRepo.login(new SimpleCredentials(username, passwordChars));
197             info = new SimpleAuthenticationInfo(username, passwordChars, getName());
198         } catch (LoginException e) {
199             throw new UnknownAccountException("No account found for user [" + username + "]", e);
200         } catch (RepositoryException e) {
201             throw new UnknownAccountException("No account found for user [" + username + "]", e);
202         } finally {
203             try {
204                 session.logout();
205             } catch (Exception e) {
206                 log.error("Failed to logout jcr session. {}", e);
207             }
208         }
209 
210         return info;
211     }
212 
213     @Override
214     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
215         //null usernames are invalid
216         if (principals == null) {
217             throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
218         }
219 
220         String username = (String) getAvailablePrincipal(principals);
221 
222         Set<String> roleNames = getRoleNames(username);
223         Set<String> permissions = null;
224 
225         if (isPermissionsLookupEnabled()) {
226             permissions = getPermissions(username, roleNames);
227         }
228 
229         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
230 
231         if (permissions != null) {
232             info.setStringPermissions(permissions);
233         }
234 
235         return info;
236     }
237 
238     protected Set<String> getRoleNames(String username) throws AuthorizationException {
239         Set<String> roleNames = new HashSet<String>();
240         Session session = null;
241 
242         try {
243             if (getSystemCredentials() != null) {
244                 session = getSystemRepository().login(getSystemCredentials());
245             } else {
246                 session = getSystemRepository().login();
247             }
248 
249             String statement = MessageFormat.format(getGroupsOfUserQuery(), username);
250 
251             if (log.isDebugEnabled()) {
252                 log.debug("Searching groups of user with query: " + statement);
253             }
254 
255             Query q = session.getWorkspace().getQueryManager().createQuery(statement, getQueryLanguage());
256             QueryResult result = q.execute();
257             NodeIterator nodeIt = result.getNodes();
258 
259             boolean defaultRoleAdded = false;
260             Node node;
261 
262             while (nodeIt.hasNext()) {
263                 node = nodeIt.nextNode();
264                 String roleName = node.getName();
265                 String prefixedRoleName = (rolePrefix != null ? rolePrefix + roleName : roleName);
266                 roleNames.add(prefixedRoleName);
267 
268                 if (defaultRoleName != null && !defaultRoleAdded && roleName.equals(defaultRoleName)) {
269                     defaultRoleAdded = true;
270                 }
271             }
272 
273             if (defaultRoleName != null && !defaultRoleAdded) {
274                 String prefixedRoleName = (rolePrefix != null ? rolePrefix + defaultRoleName : defaultRoleName);
275                 roleNames.add(prefixedRoleName);
276             }
277         } catch (RepositoryException e) {
278             final String message = "There was a repository exception while authorizing user [" + username + "]";
279 
280             if (log.isErrorEnabled()) {
281                 log.error(message, e);
282             }
283 
284             // Rethrow any SQL errors as an authorization exception
285             throw new AuthorizationException(message, e);
286         } finally {
287             if (session != null) {
288                 try {
289                     session.logout();
290                 } catch (Exception e) {
291                     log.error("Failed to logout jcr session. {}", e);
292                 }
293             }
294         }
295 
296         return roleNames;
297     }
298 
299     protected Set<String> getPermissions(String username, Set<String> roleNames) throws AuthorizationException {
300         Set<String> permissions = new HashSet<String>();
301 
302         Session session = null;
303 
304         try {
305             if (getSystemCredentials() != null) {
306                 session = getSystemRepository().login(getSystemCredentials());
307             } else {
308                 session = getSystemRepository().login();
309             }
310 
311             StringBuilder groupsConstraintsBuilder = new StringBuilder(100);
312 
313             for (String roleName : roleNames) {
314                 String groupName = roleName;
315                 groupsConstraintsBuilder.append("or @hipposys:groups = '").append(groupName).append("' ");
316             }
317 
318             String statement = MessageFormat.format(getRolesOfUserAndGroupQuery(), username,
319                     groupsConstraintsBuilder.toString());
320 
321             Query q = session.getWorkspace().getQueryManager().createQuery(statement, getQueryLanguage());
322             QueryResult result = q.execute();
323             NodeIterator nodeIt = result.getNodes();
324 
325             Node node;
326             Node parentNode;
327 
328             String domain;
329             String authority;
330             String permission;
331 
332             boolean defaultPermissionAdded = false;
333 
334             while (nodeIt.hasNext()) {
335                 node = nodeIt.nextNode();
336                 parentNode = node.getParent();
337 
338                 domain = parentNode.getName();
339                 authority = node.getProperty("hipposys:role").getString();
340 
341                 permission = new StringBuilder(20).append(domain).append(':').append(authority).toString();
342                 permissions.add(permission);
343 
344                 if (defaultPermission != null && !defaultPermissionAdded && defaultPermission.equals(permission)) {
345                     defaultPermissionAdded = true;
346                 }
347             }
348 
349             if (!defaultPermissionAdded && defaultPermission != null) {
350                 permissions.add(defaultPermission);
351             }
352         } catch (RepositoryException e) {
353             final String message = "There was a repository exception while authorizing user [" + username + "]";
354 
355             if (log.isErrorEnabled()) {
356                 log.error(message, e);
357             }
358 
359             // Rethrow any SQL errors as an authorization exception
360             throw new AuthorizationException(message, e);
361         } finally {
362             if (session != null) {
363                 try {
364                     session.logout();
365                 } catch (Exception e) {
366                     log.error("Failed to logout jcr session. {}", e);
367                 }
368             }
369         }
370 
371         return permissions;
372     }
373 }