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