View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
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.argeo.security.jackrabbit;
17  
18  import java.security.Principal;
19  import java.util.HashSet;
20  import java.util.Properties;
21  import java.util.Set;
22  
23  import javax.jcr.Credentials;
24  import javax.jcr.Repository;
25  import javax.jcr.RepositoryException;
26  import javax.jcr.Session;
27  import javax.security.auth.Subject;
28  import javax.security.auth.callback.CallbackHandler;
29  import javax.security.auth.x500.X500Principal;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.jackrabbit.api.security.user.UserManager;
34  import org.apache.jackrabbit.core.DefaultSecurityManager;
35  import org.apache.jackrabbit.core.security.AMContext;
36  import org.apache.jackrabbit.core.security.AccessManager;
37  import org.apache.jackrabbit.core.security.SecurityConstants;
38  import org.apache.jackrabbit.core.security.SystemPrincipal;
39  import org.apache.jackrabbit.core.security.authentication.AuthContext;
40  import org.apache.jackrabbit.core.security.authentication.CallbackHandlerImpl;
41  import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
42  import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
43  import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
44  import org.argeo.api.NodeConstants;
45  import org.argeo.api.security.AnonymousPrincipal;
46  import org.argeo.api.security.DataAdminPrincipal;
47  import org.argeo.cms.auth.CmsSession;
48  import org.osgi.framework.BundleContext;
49  import org.osgi.framework.FrameworkUtil;
50  
51  /** Customises Jackrabbit security. */
52  public class ArgeoSecurityManager extends DefaultSecurityManager {
53  	private final static Log log = LogFactory.getLog(ArgeoSecurityManager.class);
54  
55  	private BundleContext cmsBundleContext = null;
56  
57  	public ArgeoSecurityManager() {
58  		if (FrameworkUtil.getBundle(CmsSession.class) != null) {
59  			cmsBundleContext = FrameworkUtil.getBundle(CmsSession.class).getBundleContext();
60  		}
61  	}
62  
63  	public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName)
64  			throws RepositoryException {
65  		checkInitialized();
66  
67  		CallbackHandler cbHandler = new CallbackHandlerImpl(creds, getSystemSession(), getPrincipalProviderRegistry(),
68  				adminId, anonymousId);
69  		String appName = "Jackrabbit";
70  		return new ArgeoAuthContext(appName, subject, cbHandler);
71  	}
72  
73  	@Override
74  	public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException {
75  		synchronized (getSystemSession()) {
76  			return super.getAccessManager(session, amContext);
77  		}
78  	}
79  
80  	@Override
81  	public UserManager getUserManager(Session session) throws RepositoryException {
82  		synchronized (getSystemSession()) {
83  			return super.getUserManager(session);
84  		}
85  	}
86  
87  	@Override
88  	protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException {
89  		return super.createDefaultPrincipalProvider(moduleConfig);
90  	}
91  
92  	/** Called once when the session is created */
93  	@Override
94  	public String getUserID(Subject subject, String workspaceName) throws RepositoryException {
95  		boolean isAnonymous = !subject.getPrincipals(AnonymousPrincipal.class).isEmpty();
96  		boolean isDataAdmin = !subject.getPrincipals(DataAdminPrincipal.class).isEmpty();
97  		boolean isJackrabbitSystem = !subject.getPrincipals(SystemPrincipal.class).isEmpty();
98  		Set<X500Principal> userPrincipal = subject.getPrincipals(X500Principal.class);
99  		boolean isRegularUser = !userPrincipal.isEmpty();
100 		CmsSession cmsSession = null;
101 		if (cmsBundleContext != null) {
102 			cmsSession = CmsSession.getCmsSession(cmsBundleContext, subject);
103 			if (log.isTraceEnabled())
104 				log.trace("Opening JCR session for CMS session " + cmsSession);
105 		}
106 
107 		if (isAnonymous) {
108 			if (isDataAdmin || isJackrabbitSystem || isRegularUser)
109 				throw new IllegalStateException("Inconsistent " + subject);
110 			else
111 				return NodeConstants.ROLE_ANONYMOUS;
112 		} else if (isRegularUser) {// must be before DataAdmin
113 			if (isAnonymous || isJackrabbitSystem)
114 				throw new IllegalStateException("Inconsistent " + subject);
115 			else {
116 				if (userPrincipal.size() > 1) {
117 					StringBuilder buf = new StringBuilder();
118 					for (X500Principal principal : userPrincipal)
119 						buf.append(' ').append('\"').append(principal).append('\"');
120 					throw new RuntimeException("Multiple user principals:" + buf);
121 				}
122 				return userPrincipal.iterator().next().getName();
123 			}
124 		} else if (isDataAdmin) {
125 			if (isAnonymous || isJackrabbitSystem || isRegularUser)
126 				throw new IllegalStateException("Inconsistent " + subject);
127 			else {
128 				assert !subject.getPrincipals(AdminPrincipal.class).isEmpty();
129 				return NodeConstants.ROLE_DATA_ADMIN;
130 			}
131 		} else if (isJackrabbitSystem) {
132 			if (isAnonymous || isDataAdmin || isRegularUser)
133 				throw new IllegalStateException("Inconsistent " + subject);
134 			else
135 				return super.getUserID(subject, workspaceName);
136 		} else {
137 			throw new IllegalStateException("Unrecognized subject type: " + subject);
138 		}
139 	}
140 
141 	@Override
142 	protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
143 		WorkspaceAccessManager wam = super.createDefaultWorkspaceAccessManager();
144 		ArgeoWorkspaceAccessManagerImpl workspaceAccessManager = new ArgeoWorkspaceAccessManagerImpl(wam);
145 		if (log.isTraceEnabled())
146 			log.trace("Created workspace access manager");
147 		return workspaceAccessManager;
148 	}
149 
150 	private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager {
151 		private final WorkspaceAccessManager wam;
152 
153 		public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) {
154 			super();
155 			this.wam = wam;
156 		}
157 
158 		public void init(Session systemSession) throws RepositoryException {
159 			wam.init(systemSession);
160 			Repository repository = systemSession.getRepository();
161 			if (log.isTraceEnabled())
162 				log.trace("Initialised workspace access manager on repository " + repository
163 						+ ", systemSession workspace: " + systemSession.getWorkspace().getName());
164 		}
165 
166 		public void close() throws RepositoryException {
167 		}
168 
169 		public boolean grants(Set<Principal> principals, String workspaceName) throws RepositoryException {
170 			// TODO: implements finer access to workspaces
171 			if (log.isTraceEnabled())
172 				log.trace("Grants " + new HashSet<>(principals) + " access to workspace '" + workspaceName + "'");
173 			return true;
174 			// return wam.grants(principals, workspaceName);
175 		}
176 	}
177 
178 }