]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java
Introduce loggedInLabel and loggedInTarget
[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.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 return;
182 }
183 String path = cn + '/' + session.getWorkspace().getName();
184 if (!dataSessionsInUse.contains(path))
185 log.warn("Data session " + path + " was not in use for " + userDn);
186 dataSessionsInUse.remove(path);
187 Session registeredSession = dataSessions.get(path);
188 if (session != registeredSession)
189 log.warn("Data session " + path + " not consistent for " + userDn);
190 notifyAll();
191 }
192
193 @Override
194 public boolean isValid() {
195 return !isClosed();
196 }
197
198 protected boolean isClosed() {
199 return getEnd() != null;
200 }
201
202 @Override
203 public Authorization getAuthorization() {
204 return authorization;
205 }
206
207 @Override
208 public UUID getUuid() {
209 return uuid;
210 }
211
212 @Override
213 public LdapName getUserDn() {
214 return userDn;
215 }
216
217 @Override
218 public String getLocalId() {
219 return localSessionId;
220 }
221
222 @Override
223 public boolean isAnonymous() {
224 return anonymous;
225 }
226
227 @Override
228 public Locale getLocale() {
229 return locale;
230 }
231
232 @Override
233 public ZonedDateTime getCreationTime() {
234 return creationTime;
235 }
236
237 @Override
238 public ZonedDateTime getEnd() {
239 return end;
240 }
241
242 public String toString() {
243 return "CMS Session " + userDn + " local=" + localSessionId + ", uuid=" + uuid;
244 }
245
246 public static CmsSessionImpl getByLocalId(String localId) {
247 Collection<ServiceReference<CmsSession>> sr;
248 try {
249 sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_LOCAL_ID + "=" + localId + ")");
250 } catch (InvalidSyntaxException e) {
251 throw new CmsException("Cannot get CMS session for id " + localId, e);
252 }
253 ServiceReference<CmsSession> cmsSessionRef;
254 if (sr.size() == 1) {
255 cmsSessionRef = sr.iterator().next();
256 return (CmsSessionImpl) bc.getService(cmsSessionRef);
257 } else if (sr.size() == 0) {
258 return null;
259 } else
260 throw new CmsException(sr.size() + " CMS sessions registered for " + localId);
261
262 }
263
264 public static CmsSessionImpl getByUuid(Object uuid) {
265 Collection<ServiceReference<CmsSession>> sr;
266 try {
267 sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
268 } catch (InvalidSyntaxException e) {
269 throw new CmsException("Cannot get CMS session for uuid " + uuid, e);
270 }
271 ServiceReference<CmsSession> cmsSessionRef;
272 if (sr.size() == 1) {
273 cmsSessionRef = sr.iterator().next();
274 return (CmsSessionImpl) bc.getService(cmsSessionRef);
275 } else if (sr.size() == 0) {
276 return null;
277 } else
278 throw new CmsException(sr.size() + " CMS sessions registered for " + uuid);
279
280 }
281
282 public static void closeInvalidSessions() {
283 Collection<ServiceReference<CmsSession>> srs;
284 try {
285 srs = bc.getServiceReferences(CmsSession.class, null);
286 for (ServiceReference<CmsSession> sr : srs) {
287 CmsSession cmsSession = bc.getService(sr);
288 if (!cmsSession.isValid()) {
289 ((CmsSessionImpl) cmsSession).close();
290 if (log.isDebugEnabled())
291 log.debug("Closed expired CMS session " + cmsSession);
292 }
293 }
294 } catch (InvalidSyntaxException e) {
295 throw new CmsException("Cannot get CMS sessions", e);
296 }
297 }
298 }