]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/SpringLoginModule.java
Starts workbench thanks to a fragment
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / 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.cms.internal.kernel;
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
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.argeo.security.NodeAuthenticationToken;
34 import org.argeo.util.LocaleCallback;
35 import org.argeo.util.LocaleUtils;
36 import org.springframework.security.authentication.AnonymousAuthenticationToken;
37 import org.springframework.security.authentication.AuthenticationManager;
38 import org.springframework.security.authentication.BadCredentialsException;
39 import org.springframework.security.authentication.jaas.SecurityContextLoginModule;
40 import org.springframework.security.core.Authentication;
41 import org.springframework.security.core.authority.SimpleGrantedAuthority;
42 import org.springframework.security.core.context.SecurityContextHolder;
43
44 /** Login module which caches one subject per thread. */
45 public class SpringLoginModule extends SecurityContextLoginModule {
46 final static String NODE_REPO_URI = "argeo.node.repo.uri";
47
48 private final static Log log = LogFactory.getLog(SpringLoginModule.class);
49
50 private AuthenticationManager authenticationManager;
51
52 private CallbackHandler callbackHandler;
53
54 private Subject subject;
55
56 private Long waitBetweenFailedLoginAttempts = 5 * 1000l;
57
58 private Boolean remote = false;
59 private Boolean anonymous = false;
60 /** Comma separated list of locales */
61 private String availableLocales = "";
62
63 private String key = null;
64 private String anonymousRole = "ROLE_ANONYMOUS";
65
66 public SpringLoginModule() {
67
68 }
69
70 @SuppressWarnings("rawtypes")
71 public void initialize(Subject subject, CallbackHandler callbackHandler,
72 Map sharedState, Map options) {
73 super.initialize(subject, callbackHandler, sharedState, options);
74 this.callbackHandler = callbackHandler;
75 this.subject = subject;
76 }
77
78 public boolean login() throws LoginException {
79 try {
80 // thread already logged in
81 if (SecurityContextHolder.getContext().getAuthentication() != null)
82 return super.login();
83
84 if (remote && anonymous)
85 throw new LoginException(
86 "Cannot have a Spring login module which is remote and anonymous");
87
88 // reset all principals and credentials
89 if (log.isTraceEnabled())
90 log.trace("Resetting all principals and credentials of "
91 + subject);
92 subject.getPrincipals().clear();
93 subject.getPrivateCredentials().clear();
94 subject.getPublicCredentials().clear();
95
96 Locale selectedLocale = null;
97 // deals first with public access since it's simple
98 if (anonymous) {
99 // multi locale
100 if (callbackHandler != null && availableLocales != null
101 && !availableLocales.trim().equals("")) {
102 LocaleCallback localeCallback = new LocaleCallback(
103 availableLocales);
104 callbackHandler.handle(new Callback[] { localeCallback });
105 selectedLocale = localeCallback.getSelectedLocale();
106 }
107
108 // TODO integrate with JCR?
109 Object principal = UUID.randomUUID().toString();
110 List<SimpleGrantedAuthority> authorities = Collections
111 .singletonList(new SimpleGrantedAuthority(anonymousRole));
112 AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken(
113 key, principal, authorities);
114 Authentication auth = authenticationManager
115 .authenticate(anonymousToken);
116 registerAuthentication(auth);
117 } else {
118 if (callbackHandler == null)
119 throw new LoginException("No call back handler available");
120
121 // ask for username and password
122 NameCallback nameCallback = new NameCallback("User");
123 PasswordCallback passwordCallback = new PasswordCallback(
124 "Password", false);
125 final String defaultNodeUrl = System
126 .getProperty(NODE_REPO_URI,
127 "http://localhost:7070/org.argeo.jcr.webapp/remoting/node");
128 NameCallback urlCallback = new NameCallback("Site URL",
129 defaultNodeUrl);
130 LocaleCallback localeCallback = new LocaleCallback(
131 availableLocales);
132
133 // handle callbacks
134 if (remote)
135 callbackHandler.handle(new Callback[] { nameCallback,
136 passwordCallback, urlCallback, localeCallback });
137 else
138 callbackHandler.handle(new Callback[] { nameCallback,
139 passwordCallback, localeCallback });
140
141 selectedLocale = localeCallback.getSelectedLocale();
142
143 // create credentials
144 final String username = nameCallback.getName();
145 if (username == null || username.trim().equals(""))
146 return false;
147
148 char[] password = {};
149 if (passwordCallback.getPassword() != null)
150 password = passwordCallback.getPassword();
151
152 NodeAuthenticationToken credentials;
153 if (remote) {
154 String url = urlCallback.getName();
155 credentials = new NodeAuthenticationToken(username,
156 password, url);
157 } else {
158 credentials = new NodeAuthenticationToken(username,
159 password);
160 }
161
162 Authentication authentication;
163 try {
164 authentication = authenticationManager
165 .authenticate(credentials);
166 } catch (BadCredentialsException e) {
167 // wait between failed login attempts
168 Thread.sleep(waitBetweenFailedLoginAttempts);
169 throw e;
170 }
171 registerAuthentication(authentication);
172 subject.getPrincipals().add(authentication);
173 }
174
175 if (selectedLocale != null)
176 LocaleUtils.threadLocale.set(selectedLocale);
177
178 return super.login();
179 } catch (LoginException e) {
180 throw e;
181 } catch (ThreadDeath e) {
182 LoginException le = new LoginException(
183 "Spring Security login thread died");
184 le.initCause(e);
185 throw le;
186 } catch (Exception e) {
187 LoginException le = new LoginException(
188 "Spring Security login failed");
189 le.initCause(e);
190 throw le;
191 }
192 }
193
194 @Override
195 public boolean logout() throws LoginException {
196 subject.getPrincipals().clear();
197 return super.logout();
198 }
199
200 /**
201 * Register an {@link Authentication} in the security context.
202 *
203 * @param authentication
204 * has to implement {@link Authentication}.
205 */
206 protected void registerAuthentication(Object authentication) {
207 SecurityContextHolder.getContext().setAuthentication(
208 (Authentication) authentication);
209 }
210
211 public void setAuthenticationManager(
212 AuthenticationManager authenticationManager) {
213 this.authenticationManager = authenticationManager;
214 }
215
216 /** Authenticates on a remote node */
217 public void setRemote(Boolean remote) {
218 this.remote = remote;
219 }
220
221 /**
222 * Request anonymous authentication (incompatible with remote)
223 */
224 public void setAnonymous(Boolean anonymous) {
225 this.anonymous = anonymous;
226 }
227
228 /** Role identifying an anonymous user */
229 public void setAnonymousRole(String anonymousRole) {
230 this.anonymousRole = anonymousRole;
231 }
232
233 /** System key */
234 public void setKey(String key) {
235 this.key = key;
236 }
237
238 public void setAvailableLocales(String locales) {
239 this.availableLocales = locales;
240 }
241
242 }