]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.core/src/org/argeo/security/core/SpringLoginModule.java
Improve error handling
[lgpl/argeo-commons.git] / org.argeo.security.core / src / org / argeo / security / core / SpringLoginModule.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.argeo.security.core;
17
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Locale;
21 import java.util.Map;
22 import java.util.UUID;
23
24 import javax.security.auth.Subject;
25 import javax.security.auth.callback.Callback;
26 import javax.security.auth.callback.CallbackHandler;
27 import javax.security.auth.callback.NameCallback;
28 import javax.security.auth.callback.PasswordCallback;
29 import javax.security.auth.login.LoginException;
30 import javax.security.auth.spi.LoginModule;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.argeo.security.NodeAuthenticationToken;
35 import org.argeo.util.LocaleCallback;
36 import org.argeo.util.LocaleUtils;
37 import org.osgi.framework.BundleContext;
38 import org.springframework.security.authentication.AnonymousAuthenticationToken;
39 import org.springframework.security.authentication.AuthenticationManager;
40 import org.springframework.security.authentication.BadCredentialsException;
41 import org.springframework.security.core.Authentication;
42 import org.springframework.security.core.authority.SimpleGrantedAuthority;
43 import org.springframework.security.core.context.SecurityContextHolder;
44
45 /** Login module which caches one subject per thread. */
46 public class SpringLoginModule implements LoginModule {
47 final static String NODE_REPO_URI = "argeo.node.repo.uri";
48
49 private final static Log log = LogFactory.getLog(SpringLoginModule.class);
50
51 private CallbackHandler callbackHandler;
52
53 private Subject subject;
54
55 private Long waitBetweenFailedLoginAttempts = 5 * 1000l;
56
57 private Boolean remote = false;
58 private Boolean anonymous = false;
59 /** Comma separated list of locales */
60 private String availableLocales = "";
61
62 private String key = null;
63 private String anonymousRole = "ROLE_ANONYMOUS";
64
65 public SpringLoginModule() {
66
67 }
68
69 @SuppressWarnings("rawtypes")
70 public void initialize(Subject subject, CallbackHandler callbackHandler,
71 Map sharedState, Map options) {
72 this.callbackHandler = callbackHandler;
73 this.subject = subject;
74 }
75
76 public boolean login() throws LoginException {
77 try {
78 // thread already logged in
79 Authentication currentAuth = SecurityContextHolder.getContext()
80 .getAuthentication();
81 if (currentAuth != null) {
82 if (subject.getPrincipals(Authentication.class).size() == 0) {
83 subject.getPrincipals().add(currentAuth);
84 } else {
85 Authentication principal = subject
86 .getPrincipals(Authentication.class).iterator()
87 .next();
88 if (principal != currentAuth)
89 throw new LoginException(
90 "Already authenticated with a different auth");
91 }
92 return true;
93 }
94
95 if (remote && anonymous)
96 throw new LoginException(
97 "Cannot have a Spring login module which is remote and anonymous");
98
99 // reset all principals and credentials
100 if (log.isTraceEnabled())
101 log.trace("Resetting all principals and credentials of "
102 + subject);
103 subject.getPrincipals().clear();
104 subject.getPrivateCredentials().clear();
105 subject.getPublicCredentials().clear();
106
107 Locale selectedLocale = null;
108 // deals first with public access since it's simple
109 if (anonymous) {
110 // FIXME Is this code still needed?
111 AuthenticationManager authenticationManager = null;
112
113 // multi locale
114 if (callbackHandler != null && availableLocales != null
115 && !availableLocales.trim().equals("")) {
116 LocaleCallback localeCallback = new LocaleCallback(
117 availableLocales);
118 callbackHandler.handle(new Callback[] { localeCallback });
119 selectedLocale = localeCallback.getSelectedLocale();
120 }
121
122 // TODO integrate with JCR?
123 Object principal = UUID.randomUUID().toString();
124 List<SimpleGrantedAuthority> authorities = Collections
125 .singletonList(new SimpleGrantedAuthority(anonymousRole));
126 AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken(
127 key, principal, authorities);
128 Authentication auth = authenticationManager
129 .authenticate(anonymousToken);
130 registerAuthentication(auth);
131 } else {
132 if (callbackHandler == null)
133 throw new LoginException("No call back handler available");
134
135 // ask for username and password
136 NameCallback nameCallback = new NameCallback("User");
137 PasswordCallback passwordCallback = new PasswordCallback(
138 "Password", false);
139 final String defaultNodeUrl = System
140 .getProperty(NODE_REPO_URI,
141 "http://localhost:7070/org.argeo.jcr.webapp/remoting/node");
142 NameCallback urlCallback = new NameCallback("Site URL",
143 defaultNodeUrl);
144 LocaleCallback localeCallback = new LocaleCallback(
145 availableLocales);
146 BundleContextCallback bundleContextCallback = new BundleContextCallback();
147
148 // handle callbacks
149 if (remote)
150 callbackHandler.handle(new Callback[] { nameCallback,
151 passwordCallback, urlCallback, localeCallback,
152 bundleContextCallback });
153 else
154 callbackHandler.handle(new Callback[] { nameCallback,
155 passwordCallback, localeCallback,
156 bundleContextCallback });
157
158 selectedLocale = localeCallback.getSelectedLocale();
159
160 // create credentials
161 final String username = nameCallback.getName();
162 if (username == null || username.trim().equals(""))
163 return false;
164
165 char[] password = {};
166 if (passwordCallback.getPassword() != null)
167 password = passwordCallback.getPassword();
168
169 NodeAuthenticationToken credentials;
170 if (remote) {
171 String url = urlCallback.getName();
172 credentials = new NodeAuthenticationToken(username,
173 password, url);
174 } else {
175 credentials = new NodeAuthenticationToken(username,
176 password);
177 }
178
179 BundleContext bc = bundleContextCallback.getBundleContext();
180 AuthenticationManager authenticationManager = bc.getService(bc
181 .getServiceReference(AuthenticationManager.class));
182
183 Authentication authentication;
184 try {
185 authentication = authenticationManager
186 .authenticate(credentials);
187 } catch (BadCredentialsException e) {
188 // wait between failed login attempts
189 Thread.sleep(waitBetweenFailedLoginAttempts);
190 throw e;
191 }
192 registerAuthentication(authentication);
193 subject.getPrincipals().add(authentication);
194 }
195
196 if (selectedLocale != null)
197 LocaleUtils.threadLocale.set(selectedLocale);
198
199 return true;
200 } catch (LoginException e) {
201 throw e;
202 } catch (ThreadDeath e) {
203 LoginException le = new LoginException(
204 "Spring Security login thread died");
205 le.initCause(e);
206 throw le;
207 } catch (Exception e) {
208 LoginException le = new LoginException(
209 "Spring Security login failed");
210 le.initCause(e);
211 throw le;
212 }
213 }
214
215 @Override
216 public boolean logout() throws LoginException {
217 subject.getPrincipals().clear();
218 return true;
219 }
220
221 @Override
222 public boolean commit() throws LoginException {
223 return true;
224 }
225
226 @Override
227 public boolean abort() throws LoginException {
228 return true;
229 }
230
231 /**
232 * Register an {@link Authentication} in the security context.
233 *
234 * @param authentication
235 * has to implement {@link Authentication}.
236 */
237 protected void registerAuthentication(Object authentication) {
238 SecurityContextHolder.getContext().setAuthentication(
239 (Authentication) authentication);
240 }
241
242 /** Authenticates on a remote node */
243 public void setRemote(Boolean remote) {
244 this.remote = remote;
245 }
246
247 /**
248 * Request anonymous authentication (incompatible with remote)
249 */
250 public void setAnonymous(Boolean anonymous) {
251 this.anonymous = anonymous;
252 }
253
254 /** Role identifying an anonymous user */
255 public void setAnonymousRole(String anonymousRole) {
256 this.anonymousRole = anonymousRole;
257 }
258
259 /** System key */
260 public void setKey(String key) {
261 this.key = key;
262 }
263
264 public void setAvailableLocales(String locales) {
265 this.availableLocales = locales;
266 }
267
268 }