]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java
Clarify naming.
[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.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.api.NodeConstants;
30 import org.argeo.api.security.NodeSecurityUtils;
31 import org.argeo.cms.CmsException;
32 import org.argeo.cms.auth.CmsSession;
33 import org.argeo.jcr.JcrUtils;
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 Session newDataSession(String cn, String workspace, Repository repository) {
132 return login(repository, workspace);
133 }
134
135 public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
136 // FIXME make it more robust
137 if (workspace == null)
138 workspace = "main";
139 String path = cn + '/' + workspace;
140 if (dataSessionsInUse.contains(path)) {
141 try {
142 wait(1000);
143 if (dataSessionsInUse.contains(path)) {
144 Session session = login(repository, workspace);
145 additionalDataSessions.add(session);
146 if (log.isTraceEnabled())
147 log.trace("Additional data session " + path + " for " + userDn);
148 return session;
149 }
150 } catch (InterruptedException e) {
151 // silent
152 }
153 }
154
155 Session session = null;
156 if (dataSessions.containsKey(path)) {
157 session = dataSessions.get(path);
158 } else {
159 session = login(repository, workspace);
160 dataSessions.put(path, session);
161 if (log.isTraceEnabled())
162 log.trace("New data session " + path + " for " + userDn);
163 }
164 dataSessionsInUse.add(path);
165 return session;
166 }
167
168 private Session login(Repository repository, String workspace) {
169 try {
170 return Subject.doAs(getSubject(), new PrivilegedExceptionAction<Session>() {
171 @Override
172 public Session run() throws Exception {
173 return repository.login(workspace);
174 }
175 });
176 } catch (Exception e) {
177 throw new CmsException("Cannot log in " + userDn + " to JCR", e);
178 }
179 }
180
181 public synchronized void releaseDataSession(String cn, Session session) {
182 if (additionalDataSessions.contains(session)) {
183 JcrUtils.logoutQuietly(session);
184 additionalDataSessions.remove(session);
185 if (log.isTraceEnabled())
186 log.trace("Remove additional data session " + session);
187 return;
188 }
189 String path = cn + '/' + session.getWorkspace().getName();
190 if (!dataSessionsInUse.contains(path))
191 log.warn("Data session " + path + " was not in use for " + userDn);
192 dataSessionsInUse.remove(path);
193 Session registeredSession = dataSessions.get(path);
194 if (session != registeredSession)
195 log.warn("Data session " + path + " not consistent for " + userDn);
196 if (log.isTraceEnabled())
197 log.trace("Released data session " + session + " for " + path);
198 notifyAll();
199 }
200
201 @Override
202 public boolean isValid() {
203 return !isClosed();
204 }
205
206 protected boolean isClosed() {
207 return getEnd() != null;
208 }
209
210 @Override
211 public Authorization getAuthorization() {
212 return authorization;
213 }
214
215 @Override
216 public UUID getUuid() {
217 return uuid;
218 }
219
220 @Override
221 public LdapName getUserDn() {
222 return userDn;
223 }
224
225 @Override
226 public String getLocalId() {
227 return localSessionId;
228 }
229
230 @Override
231 public boolean isAnonymous() {
232 return anonymous;
233 }
234
235 @Override
236 public Locale getLocale() {
237 return locale;
238 }
239
240 @Override
241 public ZonedDateTime getCreationTime() {
242 return creationTime;
243 }
244
245 @Override
246 public ZonedDateTime getEnd() {
247 return end;
248 }
249
250 public String toString() {
251 return "CMS Session " + userDn + " local=" + localSessionId + ", uuid=" + uuid;
252 }
253
254 public static CmsSessionImpl getByLocalId(String localId) {
255 Collection<ServiceReference<CmsSession>> sr;
256 try {
257 sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_LOCAL_ID + "=" + localId + ")");
258 } catch (InvalidSyntaxException e) {
259 throw new CmsException("Cannot get CMS session for id " + localId, e);
260 }
261 ServiceReference<CmsSession> cmsSessionRef;
262 if (sr.size() == 1) {
263 cmsSessionRef = sr.iterator().next();
264 return (CmsSessionImpl) bc.getService(cmsSessionRef);
265 } else if (sr.size() == 0) {
266 return null;
267 } else
268 throw new CmsException(sr.size() + " CMS sessions registered for " + localId);
269
270 }
271
272 public static CmsSessionImpl getByUuid(Object uuid) {
273 Collection<ServiceReference<CmsSession>> sr;
274 try {
275 sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
276 } catch (InvalidSyntaxException e) {
277 throw new CmsException("Cannot get CMS session for uuid " + uuid, e);
278 }
279 ServiceReference<CmsSession> cmsSessionRef;
280 if (sr.size() == 1) {
281 cmsSessionRef = sr.iterator().next();
282 return (CmsSessionImpl) bc.getService(cmsSessionRef);
283 } else if (sr.size() == 0) {
284 return null;
285 } else
286 throw new CmsException(sr.size() + " CMS sessions registered for " + uuid);
287
288 }
289
290 public static void closeInvalidSessions() {
291 Collection<ServiceReference<CmsSession>> srs;
292 try {
293 srs = bc.getServiceReferences(CmsSession.class, null);
294 for (ServiceReference<CmsSession> sr : srs) {
295 CmsSession cmsSession = bc.getService(sr);
296 if (!cmsSession.isValid()) {
297 ((CmsSessionImpl) cmsSession).close();
298 if (log.isDebugEnabled())
299 log.debug("Closed expired CMS session " + cmsSession);
300 }
301 }
302 } catch (InvalidSyntaxException e) {
303 throw new CmsException("Cannot get CMS sessions", e);
304 }
305 }
306 }