1 package org
.argeo
.cms
.internal
.auth
;
3 import java
.io
.Serializable
;
4 import java
.security
.AccessControlContext
;
5 import java
.security
.AccessController
;
6 import java
.security
.PrivilegedAction
;
7 import java
.security
.PrivilegedActionException
;
8 import java
.security
.PrivilegedExceptionAction
;
9 import java
.time
.ZonedDateTime
;
10 import java
.util
.Collection
;
11 import java
.util
.HashMap
;
12 import java
.util
.HashSet
;
13 import java
.util
.Hashtable
;
14 import java
.util
.Locale
;
17 import java
.util
.UUID
;
19 import javax
.crypto
.SecretKey
;
20 import javax
.jcr
.Repository
;
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 import javax
.security
.auth
.x500
.X500Principal
;
29 import org
.apache
.commons
.logging
.Log
;
30 import org
.apache
.commons
.logging
.LogFactory
;
31 import org
.argeo
.api
.NodeConstants
;
32 import org
.argeo
.api
.security
.NodeSecurityUtils
;
33 import org
.argeo
.cms
.auth
.CmsSession
;
34 import org
.argeo
.jcr
.JcrUtils
;
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
;
42 /** Default CMS session implementation. */
43 public class CmsSessionImpl
implements CmsSession
, Serializable
{
44 private static final long serialVersionUID
= 1867719354246307225L;
45 private final static BundleContext bc
= FrameworkUtil
.getBundle(CmsSessionImpl
.class).getBundleContext();
46 private final static Log log
= LogFactory
.getLog(CmsSessionImpl
.class);
48 // private final Subject initialSubject;
49 private transient AccessControlContext accessControlContext
;
50 private final UUID uuid
;
51 private final String localSessionId
;
52 private Authorization authorization
;
53 private final LdapName userDn
;
54 private final boolean anonymous
;
56 private final ZonedDateTime creationTime
;
57 private ZonedDateTime end
;
58 private final Locale locale
;
60 private ServiceRegistration
<CmsSession
> serviceRegistration
;
62 private Map
<String
, Session
> dataSessions
= new HashMap
<>();
63 private Set
<String
> dataSessionsInUse
= new HashSet
<>();
64 private Set
<Session
> additionalDataSessions
= new HashSet
<>();
66 private Map
<String
, Object
> views
= new HashMap
<>();
68 public CmsSessionImpl(Subject initialSubject
, Authorization authorization
, Locale locale
, String localSessionId
) {
69 this.creationTime
= ZonedDateTime
.now();
71 this.accessControlContext
= Subject
.doAs(initialSubject
, new PrivilegedAction
<AccessControlContext
>() {
74 public AccessControlContext
run() {
75 return AccessController
.getContext();
79 // this.initialSubject = initialSubject;
80 this.localSessionId
= localSessionId
;
81 this.authorization
= authorization
;
82 if (authorization
.getName() != null)
84 this.userDn
= new LdapName(authorization
.getName());
85 this.anonymous
= false;
86 } catch (InvalidNameException e
) {
87 throw new IllegalArgumentException("Invalid user name " + authorization
.getName(), e
);
90 this.userDn
= NodeSecurityUtils
.ROLE_ANONYMOUS_NAME
;
91 this.anonymous
= true;
93 this.uuid
= UUID
.randomUUID();
94 // register as service
95 Hashtable
<String
, String
> props
= new Hashtable
<>();
96 props
.put(CmsSession
.USER_DN
, userDn
.toString());
97 props
.put(CmsSession
.SESSION_UUID
, uuid
.toString());
98 props
.put(CmsSession
.SESSION_LOCAL_ID
, localSessionId
);
99 serviceRegistration
= bc
.registerService(CmsSession
.class, this, props
);
102 public void close() {
103 end
= ZonedDateTime
.now();
104 serviceRegistration
.unregister();
106 synchronized (this) {
107 // TODO check data session in use ?
108 for (String path
: dataSessions
.keySet())
109 JcrUtils
.logoutQuietly(dataSessions
.get(path
));
110 for (Session session
: additionalDataSessions
)
111 JcrUtils
.logoutQuietly(session
);
117 lc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_ANONYMOUS
, getSubject());
119 lc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_USER
, getSubject());
122 } catch (LoginException e
) {
123 log
.warn("Could not logout " + getSubject() + ": " + e
);
125 accessControlContext
= null;
127 log
.debug("Closed " + this);
130 private Subject
getSubject() {
131 return Subject
.getSubject(accessControlContext
);
134 public Set
<SecretKey
> getSecretKeys() {
136 return getSubject().getPrivateCredentials(SecretKey
.class);
139 public Session
newDataSession(String cn
, String workspace
, Repository repository
) {
141 return login(repository
, workspace
);
144 public synchronized Session
getDataSession(String cn
, String workspace
, Repository repository
) {
146 // FIXME make it more robust
147 if (workspace
== null)
148 workspace
= NodeConstants
.SYS_WORKSPACE
;
149 String path
= cn
+ '/' + workspace
;
150 if (dataSessionsInUse
.contains(path
)) {
153 if (dataSessionsInUse
.contains(path
)) {
154 Session session
= login(repository
, workspace
);
155 additionalDataSessions
.add(session
);
156 if (log
.isTraceEnabled())
157 log
.trace("Additional data session " + path
+ " for " + userDn
);
160 } catch (InterruptedException e
) {
165 Session session
= null;
166 if (dataSessions
.containsKey(path
)) {
167 session
= dataSessions
.get(path
);
169 session
= login(repository
, workspace
);
170 dataSessions
.put(path
, session
);
171 if (log
.isTraceEnabled())
172 log
.trace("New data session " + path
+ " for " + userDn
);
174 dataSessionsInUse
.add(path
);
178 private Session
login(Repository repository
, String workspace
) {
180 return Subject
.doAs(getSubject(), new PrivilegedExceptionAction
<Session
>() {
182 public Session
run() throws Exception
{
183 return repository
.login(workspace
);
186 } catch (PrivilegedActionException e
) {
187 throw new IllegalStateException("Cannot log in " + userDn
+ " to JCR", e
);
191 public synchronized void releaseDataSession(String cn
, Session session
) {
192 if (additionalDataSessions
.contains(session
)) {
193 JcrUtils
.logoutQuietly(session
);
194 additionalDataSessions
.remove(session
);
195 if (log
.isTraceEnabled())
196 log
.trace("Remove additional data session " + session
);
199 String path
= cn
+ '/' + session
.getWorkspace().getName();
200 if (!dataSessionsInUse
.contains(path
))
201 log
.warn("Data session " + path
+ " was not in use for " + userDn
);
202 dataSessionsInUse
.remove(path
);
203 Session registeredSession
= dataSessions
.get(path
);
204 if (session
!= registeredSession
)
205 log
.warn("Data session " + path
+ " not consistent for " + userDn
);
206 if (log
.isTraceEnabled())
207 log
.trace("Released data session " + session
+ " for " + path
);
212 public boolean isValid() {
216 private void checkValid() {
218 throw new IllegalStateException("CMS session " + uuid
+ " is not valid since " + end
);
221 final protected boolean isClosed() {
222 return getEnd() != null;
226 public Authorization
getAuthorization() {
228 return authorization
;
232 public UUID
getUuid() {
237 public LdapName
getUserDn() {
242 public String
getUserRole() {
243 return new X500Principal(authorization
.getName()).getName();
247 public String
getLocalId() {
248 return localSessionId
;
252 public boolean isAnonymous() {
257 public Locale
getLocale() {
262 public ZonedDateTime
getCreationTime() {
267 public ZonedDateTime
getEnd() {
272 public void registerView(String uid
, Object view
) {
274 if (views
.containsKey(uid
))
275 throw new IllegalArgumentException("View " + uid
+ " is already registered.");
276 views
.put(uid
, view
);
279 public String
toString() {
280 return "CMS Session " + userDn
+ " local=" + localSessionId
+ ", uuid=" + uuid
;
283 public static CmsSessionImpl
getByLocalId(String localId
) {
284 Collection
<ServiceReference
<CmsSession
>> sr
;
286 sr
= bc
.getServiceReferences(CmsSession
.class, "(" + CmsSession
.SESSION_LOCAL_ID
+ "=" + localId
+ ")");
287 } catch (InvalidSyntaxException e
) {
288 throw new IllegalArgumentException("Cannot get CMS session for id " + localId
, e
);
290 ServiceReference
<CmsSession
> cmsSessionRef
;
291 if (sr
.size() == 1) {
292 cmsSessionRef
= sr
.iterator().next();
293 return (CmsSessionImpl
) bc
.getService(cmsSessionRef
);
294 } else if (sr
.size() == 0) {
297 throw new IllegalStateException(sr
.size() + " CMS sessions registered for " + localId
);
301 public static CmsSessionImpl
getByUuid(Object uuid
) {
302 Collection
<ServiceReference
<CmsSession
>> sr
;
304 sr
= bc
.getServiceReferences(CmsSession
.class, "(" + CmsSession
.SESSION_UUID
+ "=" + uuid
+ ")");
305 } catch (InvalidSyntaxException e
) {
306 throw new IllegalArgumentException("Cannot get CMS session for uuid " + uuid
, e
);
308 ServiceReference
<CmsSession
> cmsSessionRef
;
309 if (sr
.size() == 1) {
310 cmsSessionRef
= sr
.iterator().next();
311 return (CmsSessionImpl
) bc
.getService(cmsSessionRef
);
312 } else if (sr
.size() == 0) {
315 throw new IllegalStateException(sr
.size() + " CMS sessions registered for " + uuid
);
319 public static void closeInvalidSessions() {
320 Collection
<ServiceReference
<CmsSession
>> srs
;
322 srs
= bc
.getServiceReferences(CmsSession
.class, null);
323 for (ServiceReference
<CmsSession
> sr
: srs
) {
324 CmsSession cmsSession
= bc
.getService(sr
);
325 if (!cmsSession
.isValid()) {
326 ((CmsSessionImpl
) cmsSession
).close();
327 if (log
.isDebugEnabled())
328 log
.debug("Closed expired CMS session " + cmsSession
);
331 } catch (InvalidSyntaxException e
) {
332 throw new IllegalArgumentException("Cannot get CMS sessions", e
);