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