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