2 * Copyright (C) 2007-2012 Argeo GmbH
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.argeo
.security
.jackrabbit
;
18 import java
.security
.Principal
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Collection
;
21 import java
.util
.Collections
;
22 import java
.util
.HashMap
;
23 import java
.util
.Iterator
;
24 import java
.util
.List
;
28 import javax
.jcr
.RepositoryException
;
29 import javax
.jcr
.Session
;
30 import javax
.security
.auth
.Subject
;
32 import org
.apache
.commons
.logging
.Log
;
33 import org
.apache
.commons
.logging
.LogFactory
;
34 import org
.apache
.jackrabbit
.api
.security
.user
.Group
;
35 import org
.apache
.jackrabbit
.api
.security
.user
.User
;
36 import org
.apache
.jackrabbit
.api
.security
.user
.UserManager
;
37 import org
.apache
.jackrabbit
.core
.DefaultSecurityManager
;
38 import org
.apache
.jackrabbit
.core
.security
.AMContext
;
39 import org
.apache
.jackrabbit
.core
.security
.AccessManager
;
40 import org
.apache
.jackrabbit
.core
.security
.AnonymousPrincipal
;
41 import org
.apache
.jackrabbit
.core
.security
.SecurityConstants
;
42 import org
.apache
.jackrabbit
.core
.security
.authorization
.WorkspaceAccessManager
;
43 import org
.springframework
.security
.core
.Authentication
;
44 import org
.springframework
.security
.core
.GrantedAuthority
;
45 import org
.springframework
.security
.core
.context
.SecurityContextHolder
;
47 /** Integrates Spring Security and Jackrabbit Security users and roles. */
48 public class ArgeoSecurityManager
extends DefaultSecurityManager
{
49 /** Legacy security sync */
50 final static String PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1
= "argeo.jackarabbit.securitySync.1.1";
52 private final static Log log
= LogFactory
53 .getLog(ArgeoSecurityManager
.class);
55 private static Boolean synchronize
= Boolean
.parseBoolean(System
56 .getProperty(PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1
, "false"));
58 /** TODO? use a bounded buffer */
59 private Map
<String
, String
> userRolesCache
= Collections
60 .synchronizedMap(new HashMap
<String
, String
>());
63 public AccessManager
getAccessManager(Session session
, AMContext amContext
)
64 throws RepositoryException
{
65 synchronized (getSystemSession()) {
66 return super.getAccessManager(session
, amContext
);
71 public UserManager
getUserManager(Session session
)
72 throws RepositoryException
{
73 synchronized (getSystemSession()) {
74 return super.getUserManager(session
);
79 * Since this is called once when the session is created, we take the
80 * opportunity to make sure that Jackrabbit users and groups reflect Spring
81 * Security name and authorities.
84 public String
getUserID(Subject subject
, String workspaceName
)
85 throws RepositoryException
{
87 Authentication authentication
= SecurityContextHolder
.getContext()
89 if (authentication
!= null)
90 return authentication
.getName();
92 return super.getUserID(subject
, workspaceName
);
95 if (log
.isTraceEnabled())
97 // skip anonymous user (no rights)
98 if (!subject
.getPrincipals(AnonymousPrincipal
.class).isEmpty())
99 return super.getUserID(subject
, workspaceName
);
100 // skip Jackrabbit system user (all rights)
101 if (!subject
.getPrincipals(ArgeoSystemPrincipal
.class).isEmpty())
102 return super.getUserID(subject
, workspaceName
);
104 // retrieve Spring authentication from JAAS
105 // TODO? use Spring Security context holder
106 Authentication authen
;
107 Set
<Authentication
> authens
= subject
108 .getPrincipals(Authentication
.class);
109 String userId
= super.getUserID(subject
, workspaceName
);
110 if (authens
.size() == 0) {
111 // make sure that logged-in user has a Principal, useful for testing
112 // using an admin user
113 UserManager systemUm
= getSystemUserManager(null);
114 if (systemUm
.getAuthorizable(userId
) == null)
115 systemUm
.createUser(userId
, "");
116 } else {// Spring Security
117 authen
= authens
.iterator().next();
119 if (!userId
.equals(authen
.getName()))
120 log
.warn("User ID is '" + userId
+ "' but authen is "
122 StringBuffer roles
= new StringBuffer("");
123 Collection
<?
extends GrantedAuthority
> authorities
= authen
125 for (GrantedAuthority ga
: authorities
) {
126 roles
.append(ga
.toString());
129 // do not sync if not changed
130 if (userRolesCache
.containsKey(userId
)
131 && userRolesCache
.get(userId
).equals(roles
.toString()))
134 // sync Spring and Jackrabbit
135 // workspace is irrelevant here
136 UserManager systemUm
= getSystemUserManager(null);
137 syncSpringAndJackrabbitSecurity(systemUm
, authen
);
138 userRolesCache
.put(userId
, roles
.toString());
144 * Make sure that the Jackrabbit security model contains this user and its
145 * granted authorities
147 static private void syncSpringAndJackrabbitSecurity(UserManager systemUm
,
148 Authentication authen
) throws RepositoryException
{
149 long begin
= System
.currentTimeMillis();
151 String userId
= authen
.getName();
152 User user
= (User
) systemUm
.getAuthorizable(userId
);
154 user
= systemUm
.createUser(userId
, authen
.getCredentials()
155 .toString(), authen
, null);
156 log
.info(userId
+ " added as " + user
);
160 List
<String
> userGroupIds
= new ArrayList
<String
>();
161 for (GrantedAuthority ga
: authen
.getAuthorities()) {
162 Group group
= (Group
) systemUm
.getAuthorizable(ga
.getAuthority());
164 group
= systemUm
.createGroup(ga
.getAuthority());
165 log
.info(ga
.getAuthority() + " added as " + group
);
167 if (!group
.isMember(user
))
168 group
.addMember(user
);
169 userGroupIds
.add(ga
.getAuthority());
172 // check if user has not been removed from some groups
173 for (Iterator
<Group
> it
= user
.declaredMemberOf(); it
.hasNext();) {
174 Group group
= it
.next();
175 if (!userGroupIds
.contains(group
.getID()))
176 group
.removeMember(user
);
179 if (log
.isTraceEnabled())
180 log
.trace("Spring and Jackrabbit Security synchronized for user "
181 + userId
+ " in " + (System
.currentTimeMillis() - begin
)
186 protected WorkspaceAccessManager
createDefaultWorkspaceAccessManager() {
187 WorkspaceAccessManager wam
= super
188 .createDefaultWorkspaceAccessManager();
189 return new ArgeoWorkspaceAccessManagerImpl(wam
);
192 private class ArgeoWorkspaceAccessManagerImpl
implements SecurityConstants
,
193 WorkspaceAccessManager
{
194 private final WorkspaceAccessManager wam
;
196 public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam
) {
201 public void init(Session systemSession
) throws RepositoryException
{
202 wam
.init(systemSession
);
205 public void close() throws RepositoryException
{
208 public boolean grants(Set
<Principal
> principals
, String workspaceName
)
209 throws RepositoryException
{
210 // TODO: implements finer access to workspaces