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