]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java
Put all JCR projects together.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / auth / CmsSessionImpl.java
1 package org.argeo.cms.internal.auth;
2
3 import java.io.Serializable;
4 import java.security.AccessControlContext;
5 import java.security.AccessController;
6 import java.security.PrivilegedAction;
7 import java.security.PrivilegedActionException;
8 import java.security.PrivilegedExceptionAction;
9 import java.time.ZonedDateTime;
10 import java.util.Collection;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.Hashtable;
14 import java.util.Locale;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.UUID;
18
19 import javax.crypto.SecretKey;
20 import javax.jcr.Repository;
21 import javax.jcr.Session;
22 import javax.naming.InvalidNameException;
23 import javax.naming.ldap.LdapName;
24 import javax.security.auth.Subject;
25 import javax.security.auth.login.LoginContext;
26 import javax.security.auth.login.LoginException;
27 import javax.security.auth.x500.X500Principal;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.argeo.api.NodeConstants;
32 import org.argeo.api.security.NodeSecurityUtils;
33 import org.argeo.cms.auth.CmsSession;
34 import org.argeo.jcr.JcrUtils;
35 import org.osgi.framework.BundleContext;
36 import org.osgi.framework.FrameworkUtil;
37 import org.osgi.framework.InvalidSyntaxException;
38 import org.osgi.framework.ServiceReference;
39 import org.osgi.framework.ServiceRegistration;
40 import org.osgi.service.useradmin.Authorization;
41
42 /** Default CMS session implementation. */
43 public class CmsSessionImpl implements CmsSession, Serializable {
44 private static final long serialVersionUID = 1867719354246307225L;
45 private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionImpl.class).getBundleContext();
46 private final static Log log = LogFactory.getLog(CmsSessionImpl.class);
47
48 // private final Subject initialSubject;
49 private transient AccessControlContext accessControlContext;
50 private final UUID uuid;
51 private final String localSessionId;
52 private Authorization authorization;
53 private final LdapName userDn;
54 private final boolean anonymous;
55
56 private final ZonedDateTime creationTime;
57 private ZonedDateTime end;
58 private final Locale locale;
59
60 private ServiceRegistration<CmsSession> serviceRegistration;
61
62 private Map<String, Session> dataSessions = new HashMap<>();
63 private Set<String> dataSessionsInUse = new HashSet<>();
64 private Set<Session> additionalDataSessions = new HashSet<>();
65
66 private Map<String, Object> views = new HashMap<>();
67
68 public CmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale, String localSessionId) {
69 this.creationTime = ZonedDateTime.now();
70 this.locale = locale;
71 this.accessControlContext = Subject.doAs(initialSubject, new PrivilegedAction<AccessControlContext>() {
72
73 @Override
74 public AccessControlContext run() {
75 return AccessController.getContext();
76 }
77
78 });
79 // this.initialSubject = initialSubject;
80 this.localSessionId = localSessionId;
81 this.authorization = authorization;
82 if (authorization.getName() != null)
83 try {
84 this.userDn = new LdapName(authorization.getName());
85 this.anonymous = false;
86 } catch (InvalidNameException e) {
87 throw new IllegalArgumentException("Invalid user name " + authorization.getName(), e);
88 }
89 else {
90 this.userDn = NodeSecurityUtils.ROLE_ANONYMOUS_NAME;
91 this.anonymous = true;
92 }
93 this.uuid = UUID.randomUUID();
94 // register as service
95 Hashtable<String, String> props = new Hashtable<>();
96 props.put(CmsSession.USER_DN, userDn.toString());
97 props.put(CmsSession.SESSION_UUID, uuid.toString());
98 props.put(CmsSession.SESSION_LOCAL_ID, localSessionId);
99 serviceRegistration = bc.registerService(CmsSession.class, this, props);
100 }
101
102 public void close() {
103 end = ZonedDateTime.now();
104 serviceRegistration.unregister();
105
106 synchronized (this) {
107 // TODO check data session in use ?
108 for (String path : dataSessions.keySet())
109 JcrUtils.logoutQuietly(dataSessions.get(path));
110 for (Session session : additionalDataSessions)
111 JcrUtils.logoutQuietly(session);
112 }
113
114 try {
115 LoginContext lc;
116 if (isAnonymous()) {
117 lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS, getSubject());
118 } else {
119 lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, getSubject());
120 }
121 lc.logout();
122 } catch (LoginException e) {
123 log.warn("Could not logout " + getSubject() + ": " + e);
124 } finally {
125 accessControlContext = null;
126 }
127 log.debug("Closed " + this);
128 }
129
130 private Subject getSubject() {
131 return Subject.getSubject(accessControlContext);
132 }
133
134 public Set<SecretKey> getSecretKeys() {
135 checkValid();
136 return getSubject().getPrivateCredentials(SecretKey.class);
137 }
138
139 public Session newDataSession(String cn, String workspace, Repository repository) {
140 checkValid();
141 return login(repository, workspace);
142 }
143
144 public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
145 checkValid();
146 // FIXME make it more robust
147 if (workspace == null)
148 workspace = NodeConstants.SYS_WORKSPACE;
149 String path = cn + '/' + workspace;
150 if (dataSessionsInUse.contains(path)) {
151 try {
152 wait(1000);
153 if (dataSessionsInUse.contains(path)) {
154 Session session = login(repository, workspace);
155 additionalDataSessions.add(session);
156 if (log.isTraceEnabled())
157 log.trace("Additional data session " + path + " for " + userDn);
158 return session;
159 }
160 } catch (InterruptedException e) {
161 // silent
162 }
163 }
164
165 Session session = null;
166 if (dataSessions.containsKey(path)) {
167 session = dataSessions.get(path);
168 } else {
169 session = login(repository, workspace);
170 dataSessions.put(path, session);
171 if (log.isTraceEnabled())
172 log.trace("New data session " + path + " for " + userDn);
173 }
174 dataSessionsInUse.add(path);
175 return session;
176 }
177
178 private Session login(Repository repository, String workspace) {
179 try {
180 return Subject.doAs(getSubject(), new PrivilegedExceptionAction<Session>() {
181 @Override
182 public Session run() throws Exception {
183 return repository.login(workspace);
184 }
185 });
186 } catch (PrivilegedActionException e) {
187 throw new IllegalStateException("Cannot log in " + userDn + " to JCR", e);
188 }
189 }
190
191 public synchronized void releaseDataSession(String cn, Session session) {
192 if (additionalDataSessions.contains(session)) {
193 JcrUtils.logoutQuietly(session);
194 additionalDataSessions.remove(session);
195 if (log.isTraceEnabled())
196 log.trace("Remove additional data session " + session);
197 return;
198 }
199 String path = cn + '/' + session.getWorkspace().getName();
200 if (!dataSessionsInUse.contains(path))
201 log.warn("Data session " + path + " was not in use for " + userDn);
202 dataSessionsInUse.remove(path);
203 Session registeredSession = dataSessions.get(path);
204 if (session != registeredSession)
205 log.warn("Data session " + path + " not consistent for " + userDn);
206 if (log.isTraceEnabled())
207 log.trace("Released data session " + session + " for " + path);
208 notifyAll();
209 }
210
211 @Override
212 public boolean isValid() {
213 return !isClosed();
214 }
215
216 private void checkValid() {
217 if (!isValid())
218 throw new IllegalStateException("CMS session " + uuid + " is not valid since " + end);
219 }
220
221 final protected boolean isClosed() {
222 return getEnd() != null;
223 }
224
225 @Override
226 public Authorization getAuthorization() {
227 checkValid();
228 return authorization;
229 }
230
231 @Override
232 public UUID getUuid() {
233 return uuid;
234 }
235
236 @Override
237 public LdapName getUserDn() {
238 return userDn;
239 }
240
241 @Override
242 public String getUserRole() {
243 return new X500Principal(authorization.getName()).getName();
244 }
245
246 @Override
247 public String getLocalId() {
248 return localSessionId;
249 }
250
251 @Override
252 public boolean isAnonymous() {
253 return anonymous;
254 }
255
256 @Override
257 public Locale getLocale() {
258 return locale;
259 }
260
261 @Override
262 public ZonedDateTime getCreationTime() {
263 return creationTime;
264 }
265
266 @Override
267 public ZonedDateTime getEnd() {
268 return end;
269 }
270
271 @Override
272 public void registerView(String uid, Object view) {
273 checkValid();
274 if (views.containsKey(uid))
275 throw new IllegalArgumentException("View " + uid + " is already registered.");
276 views.put(uid, view);
277 }
278
279 public String toString() {
280 return "CMS Session " + userDn + " local=" + localSessionId + ", uuid=" + uuid;
281 }
282
283 public static CmsSessionImpl getByLocalId(String localId) {
284 Collection<ServiceReference<CmsSession>> sr;
285 try {
286 sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_LOCAL_ID + "=" + localId + ")");
287 } catch (InvalidSyntaxException e) {
288 throw new IllegalArgumentException("Cannot get CMS session for id " + localId, e);
289 }
290 ServiceReference<CmsSession> cmsSessionRef;
291 if (sr.size() == 1) {
292 cmsSessionRef = sr.iterator().next();
293 return (CmsSessionImpl) bc.getService(cmsSessionRef);
294 } else if (sr.size() == 0) {
295 return null;
296 } else
297 throw new IllegalStateException(sr.size() + " CMS sessions registered for " + localId);
298
299 }
300
301 public static CmsSessionImpl getByUuid(Object uuid) {
302 Collection<ServiceReference<CmsSession>> sr;
303 try {
304 sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
305 } catch (InvalidSyntaxException e) {
306 throw new IllegalArgumentException("Cannot get CMS session for uuid " + uuid, e);
307 }
308 ServiceReference<CmsSession> cmsSessionRef;
309 if (sr.size() == 1) {
310 cmsSessionRef = sr.iterator().next();
311 return (CmsSessionImpl) bc.getService(cmsSessionRef);
312 } else if (sr.size() == 0) {
313 return null;
314 } else
315 throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid);
316
317 }
318
319 public static void closeInvalidSessions() {
320 Collection<ServiceReference<CmsSession>> srs;
321 try {
322 srs = bc.getServiceReferences(CmsSession.class, null);
323 for (ServiceReference<CmsSession> sr : srs) {
324 CmsSession cmsSession = bc.getService(sr);
325 if (!cmsSession.isValid()) {
326 ((CmsSessionImpl) cmsSession).close();
327 if (log.isDebugEnabled())
328 log.debug("Closed expired CMS session " + cmsSession);
329 }
330 }
331 } catch (InvalidSyntaxException e) {
332 throw new IllegalArgumentException("Cannot get CMS sessions", e);
333 }
334 }
335 }