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