View Javadoc
1   package org.argeo.cms.auth;
2   
3   import java.security.Principal;
4   import java.util.Collection;
5   import java.util.Locale;
6   import java.util.Set;
7   import java.util.UUID;
8   
9   import javax.naming.InvalidNameException;
10  import javax.naming.ldap.LdapName;
11  import javax.security.auth.Subject;
12  import javax.security.auth.x500.X500Principal;
13  import javax.servlet.http.HttpServletRequest;
14  import javax.servlet.http.HttpSession;
15  
16  import org.argeo.api.NodeConstants;
17  import org.argeo.api.security.AnonymousPrincipal;
18  import org.argeo.api.security.DataAdminPrincipal;
19  import org.argeo.api.security.NodeSecurityUtils;
20  //import org.apache.jackrabbit.core.security.AnonymousPrincipal;
21  //import org.apache.jackrabbit.core.security.SecurityConstants;
22  //import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
23  import org.argeo.cms.CmsException;
24  import org.argeo.cms.internal.auth.CmsSessionImpl;
25  import org.argeo.cms.internal.auth.ImpliedByPrincipal;
26  import org.argeo.cms.internal.http.WebCmsSessionImpl;
27  import org.argeo.cms.internal.kernel.Activator;
28  import org.argeo.osgi.useradmin.AuthenticatingUser;
29  import org.osgi.framework.BundleContext;
30  import org.osgi.framework.InvalidSyntaxException;
31  import org.osgi.framework.ServiceReference;
32  import org.osgi.service.http.HttpContext;
33  import org.osgi.service.useradmin.Authorization;
34  
35  class CmsAuthUtils {
36  	// Standard
37  	final static String SHARED_STATE_NAME = AuthenticatingUser.SHARED_STATE_NAME;
38  	final static String SHARED_STATE_PWD = AuthenticatingUser.SHARED_STATE_PWD;
39  	final static String HEADER_AUTHORIZATION = "Authorization";
40  	final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
41  
42  	// Argeo specific
43  	final static String SHARED_STATE_HTTP_REQUEST = "org.argeo.cms.auth.http.request";
44  	final static String SHARED_STATE_SPNEGO_TOKEN = "org.argeo.cms.auth.spnegoToken";
45  	final static String SHARED_STATE_SPNEGO_OUT_TOKEN = "org.argeo.cms.auth.spnegoOutToken";
46  	final static String SHARED_STATE_CERTIFICATE_CHAIN = "org.argeo.cms.auth.certificateChain";
47  	final static String SHARED_STATE_REMOTE_ADDR = "org.argeo.cms.auth.remote.addr";
48  	final static String SHARED_STATE_REMOTE_PORT = "org.argeo.cms.auth.remote.port";
49  
50  	static void addAuthorization(Subject subject, Authorization authorization) {
51  		assert subject != null;
52  		checkSubjectEmpty(subject);
53  		assert authorization != null;
54  
55  		// required for display name:
56  		subject.getPrivateCredentials().add(authorization);
57  
58  		if (Activator.isSingleUser()) {
59  			subject.getPrincipals().add(new DataAdminPrincipal());
60  		}
61  
62  		Set<Principal> principals = subject.getPrincipals();
63  		try {
64  			String authName = authorization.getName();
65  
66  			// determine user's principal
67  			final LdapName name;
68  			final Principal userPrincipal;
69  			if (authName == null) {
70  				name = NodeSecurityUtils.ROLE_ANONYMOUS_NAME;
71  				userPrincipal = new AnonymousPrincipal();
72  				principals.add(userPrincipal);
73  			} else {
74  				name = new LdapName(authName);
75  				NodeSecurityUtils.checkUserName(name);
76  				userPrincipal = new X500Principal(name.toString());
77  				principals.add(userPrincipal);
78  				// principals.add(new ImpliedByPrincipal(NodeSecurityUtils.ROLE_USER_NAME,
79  				// userPrincipal));
80  
81  				if (Activator.isSingleUser()) {
82  					principals.add(new ImpliedByPrincipal(NodeSecurityUtils.ROLE_ADMIN_NAME, userPrincipal));
83  				}
84  			}
85  
86  			// Add roles provided by authorization
87  			for (String role : authorization.getRoles()) {
88  				LdapName roleName = new LdapName(role);
89  				if (roleName.equals(name)) {
90  					// skip
91  				} else if (roleName.equals(NodeSecurityUtils.ROLE_ANONYMOUS_NAME)) {
92  					// skip
93  				} else {
94  					NodeSecurityUtils.checkImpliedPrincipalName(roleName);
95  					principals.add(new ImpliedByPrincipal(roleName.toString(), userPrincipal));
96  					if (roleName.equals(NodeSecurityUtils.ROLE_ADMIN_NAME))
97  						principals.add(new DataAdminPrincipal());
98  				}
99  			}
100 
101 		} catch (InvalidNameException e) {
102 			throw new CmsException("Cannot commit", e);
103 		}
104 
105 		// registerSessionAuthorization(request, subject, authorization, locale);
106 	}
107 
108 	private static void checkSubjectEmpty(Subject subject) {
109 		if (!subject.getPrincipals(AnonymousPrincipal.class).isEmpty())
110 			throw new IllegalStateException("Already logged in as anonymous: " + subject);
111 		if (!subject.getPrincipals(X500Principal.class).isEmpty())
112 			throw new IllegalStateException("Already logged in as user: " + subject);
113 		if (!subject.getPrincipals(DataAdminPrincipal.class).isEmpty())
114 			throw new IllegalStateException("Already logged in as data admin: " + subject);
115 		if (!subject.getPrincipals(ImpliedByPrincipal.class).isEmpty())
116 			throw new IllegalStateException("Already authorized: " + subject);
117 	}
118 
119 	static void cleanUp(Subject subject) {
120 		// Argeo
121 		subject.getPrincipals().removeAll(subject.getPrincipals(X500Principal.class));
122 		subject.getPrincipals().removeAll(subject.getPrincipals(ImpliedByPrincipal.class));
123 		subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class));
124 		subject.getPrincipals().removeAll(subject.getPrincipals(DataAdminPrincipal.class));
125 
126 		subject.getPrivateCredentials().removeAll(subject.getPrivateCredentials(CmsSessionId.class));
127 		subject.getPrivateCredentials().removeAll(subject.getPrivateCredentials(Authorization.class));
128 		// Jackrabbit
129 		// subject.getPrincipals().removeAll(subject.getPrincipals(AdminPrincipal.class));
130 		// subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class));
131 	}
132 
133 	synchronized static void registerSessionAuthorization(HttpServletRequest request, Subject subject,
134 			Authorization authorization, Locale locale) {
135 		// synchronized in order to avoid multiple registrations
136 		// TODO move it to a service in order to avoid static synchronization
137 		if (request != null) {
138 			HttpSession httpSession = request.getSession(false);
139 			assert httpSession != null;
140 			String httpSessId = httpSession.getId();
141 			String remoteUser = authorization.getName() != null ? authorization.getName()
142 					: NodeConstants.ROLE_ANONYMOUS;
143 			request.setAttribute(HttpContext.REMOTE_USER, remoteUser);
144 			request.setAttribute(HttpContext.AUTHORIZATION, authorization);
145 
146 			CmsSessionImpl cmsSession = CmsSessionImpl.getByLocalId(httpSessId);
147 			if (cmsSession != null) {
148 				if (authorization.getName() != null) {
149 					if (cmsSession.getAuthorization().getName() == null) {
150 						cmsSession.close();
151 						cmsSession = null;
152 					} else if (!authorization.getName().equals(cmsSession.getAuthorization().getName())) {
153 						throw new CmsException("Inconsistent user " + authorization.getName()
154 								+ " for existing CMS session " + cmsSession);
155 					}
156 					// keyring
157 					if (cmsSession != null)
158 						subject.getPrivateCredentials().addAll(cmsSession.getSecretKeys());
159 				} else {// anonymous
160 					if (cmsSession.getAuthorization().getName() != null) {
161 						cmsSession.close();
162 						// TODO rather throw an exception ? log a warning ?
163 						cmsSession = null;
164 					}
165 				}
166 			} else if (cmsSession == null) {
167 				cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request);
168 			}
169 			// request.setAttribute(CmsSession.class.getName(), cmsSession);
170 			if (cmsSession != null) {
171 				CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid());
172 				if (subject.getPrivateCredentials(CmsSessionId.class).size() == 0)
173 					subject.getPrivateCredentials().add(nodeSessionId);
174 				else {
175 					UUID storedSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next()
176 							.getUuid();
177 					// if (storedSessionId.equals(httpSessionId.getValue()))
178 					throw new CmsException(
179 							"Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")");
180 				}
181 			}
182 		} else {
183 			// TODO desktop, CLI
184 		}
185 	}
186 
187 	public static CmsSession cmsSessionFromHttpSession(BundleContext bc, String httpSessionId) {
188 		Authorization authorization = null;
189 		Collection<ServiceReference<CmsSession>> sr;
190 		try {
191 			sr = bc.getServiceReferences(CmsSession.class,
192 					"(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessionId + ")");
193 		} catch (InvalidSyntaxException e) {
194 			throw new CmsException("Cannot get CMS session for id " + httpSessionId, e);
195 		}
196 		CmsSession cmsSession;
197 		if (sr.size() == 1) {
198 			cmsSession = bc.getService(sr.iterator().next());
199 //			locale = cmsSession.getLocale();
200 			authorization = cmsSession.getAuthorization();
201 			if (authorization.getName() == null)
202 				return null;// anonymous is not sufficient
203 		} else if (sr.size() == 0)
204 			return null;
205 		else
206 			throw new CmsException(sr.size() + ">1 web sessions detected for http session " + httpSessionId);
207 		return cmsSession;
208 	}
209 
210 	public static <T extends Principal> T getSinglePrincipal(Subject subject, Class<T> clss) {
211 		Set<T> principals = subject.getPrincipals(clss);
212 		if (principals.isEmpty())
213 			return null;
214 		if (principals.size() > 1)
215 			throw new IllegalStateException("Only one " + clss + " principal expected in " + subject);
216 		return principals.iterator().next();
217 	}
218 
219 	private CmsAuthUtils() {
220 
221 	}
222 
223 }