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