1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.jetspeed.modules.actions;
17
18
19 import java.util.Locale;
20 import javax.servlet.http.HttpServletRequest;
21 import javax.servlet.http.HttpServletResponse;
22
23
24 import org.apache.turbine.util.RunData;
25 import org.apache.turbine.services.resources.TurbineResources;
26 import org.apache.turbine.services.localization.LocalizationService;
27
28
29 import org.apache.jetspeed.om.security.JetspeedUser;
30 import org.apache.jetspeed.services.JetspeedSecurity;
31 import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
32 import org.apache.jetspeed.services.logging.JetspeedLogger;
33 import org.apache.jetspeed.services.security.LoginException;
34 import org.apache.jetspeed.services.rundata.JetspeedRunData;
35 import org.apache.jetspeed.util.ServiceUtil;
36 import org.apache.jetspeed.services.customlocalization.CustomLocalizationService;
37 import org.apache.jetspeed.services.security.UnknownUserException;
38 import org.apache.jetspeed.services.security.JetspeedSecurityCache;
39
40 /***
41 * Just like org.apache.turbine.modules.actions.sessionvalidator.TemplateSessionValidator except:
42 * <ul>
43 * <li> it doesn't check the session_access_counter
44 * <li> it automatically logs the user in based on currently authenticated nt user
45 * <li> expects a JetspeedRunData object and put there the additionnal jetspeed properties
46 * </ul>
47 * <B>Usage of this session validator is limited to Windows NT/2000 and MS Internet Explorer.</B>
48 * <P>
49 * To activate this session validator, set <CODE>action.sessionvalidator</CODE> in tr.props to
50 * <code>NTLMSessionValidator</code>.
51 * </P>
52 * <P>
53 * When this session validator is active, the following algorithm is used to display appropriate psml:
54 * <pre>
55 * Check authentication status
56 * If user is not authenticated to machine running the portal
57 * Pop the standard login box
58 * If user passes authentication
59 * Attempt to retrieve matching user profile
60 * If matching profile found
61 * Render the profile
62 * Else
63 * Retrieve and render anonymous profile
64 * End
65 * Else If authentication fails
66 * Keep prompting for login information
67 * Else If the user cancels the login box
68 * Retrieve and render anonymous profile
69 * End
70 * Else
71 * Attempt to retrieve matching user profile
72 * If matching profile found
73 * Render the profile
74 * Else
75 * Retrieve and render anonymous profile
76 * End
77 * End
78 * </pre>
79 * </P>
80 * <P>
81 * Optionally, certain characters may be removed from the username before it's passed to the
82 * JetspeedSecurity. These characters may be specified by setting <CODE>NTLMSessionValidator.chars.to.remove</CODE>
83 * property. For example, if invalid characters list is '#@$', username '#user1' will be returned as 'user1'.
84 * </P>
85 *
86 * @author <a href="mailto:morciuch@apache.org">Mark Orciuch</a>
87 * @version $Id: NTLMSessionValidator.java,v 1.6 2004/02/23 02:59:06 jford Exp $
88 * @see org.apache.turbine.modules.actions.sessionvalidator.TemplateSessionValidator
89 * @see http://www.innovation.ch/java/ntlm.html
90 * @since 1.4b5
91 */
92 public class NTLMSessionValidator extends TemplateSessionValidator
93 {
94
95 private static final String INVALID_CHARS_KEY = "NTLMSessionValidator.chars.to.remove";
96
97 private String invalidChars = org.apache.jetspeed.services.resources.JetspeedResources.getString(INVALID_CHARS_KEY, null);
98
99 private static final byte z = 0;
100 private static final byte[] msg1 = {(byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M', (byte) 'S', (byte) 'S', (byte) 'P',
101 z, (byte) 2, z, z, z, z, z, z, z, (byte) 40, z, z, z,
102 (byte) 1, (byte) 130, z, z, z, (byte) 2, (byte) 2,
103 (byte) 2, z, z, z, z, z, z, z, z, z, z, z, z};
104 private static final String encodedMsg1 = "NTLM " + new sun.misc.BASE64Encoder().encodeBuffer(msg1).trim();
105
106 /***
107 * Static initialization of the logger for this class
108 */
109 private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(NTLMSessionValidator.class.getName());
110
111 /***
112 * Execute the action.
113 *
114 * @param data Turbine information.
115 * @exception Exception, a generic exception.
116 */
117 public void doPerform(RunData data) throws Exception
118 {
119
120
121
122
123
124 super.doPerform(data);
125
126 JetspeedUser user = (JetspeedUser) data.getUser();
127
128
129 String userName = this.getRemoteUser(data);
130
131 if ((user == null || !user.hasLoggedIn()))
132 {
133 if (userName != null && userName.length() > 0)
134 {
135 byte[] temp = userName.getBytes();
136 StringBuffer buffer = new StringBuffer();
137 for (int i = 0; i < temp.length; i++)
138 {
139 if (temp[i] != 0)
140 {
141 if (invalidChars == null || invalidChars.indexOf((int) temp[i]) < 0)
142 {
143 buffer.append((char) temp[i]);
144 }
145 }
146 }
147 userName = buffer.toString();
148 try
149 {
150 user = JetspeedSecurity.getUser(userName);
151 data.setUser(user);
152 user.setHasLoggedIn(new Boolean(true));
153 user.updateLastLogin();
154 data.save();
155 if (JetspeedSecurityCache.getAcl(userName) == null)
156 {
157 JetspeedSecurityCache.load(userName);
158 }
159 logger.info("NTLMSessionValidator: automatic login using [" + userName + "]");
160 }
161 catch (LoginException noSuchUser)
162 {
163
164 }
165 catch (UnknownUserException unknownUser)
166 {
167
168 if (logger.isWarnEnabled())
169 {
170 logger.warn("NTLMSessionValidator: username [" + userName + "] does not exist or authentication failed, "
171 + "redirecting to anon profile");
172 }
173 }
174 }
175 }
176
177
178
179 JetspeedRunData jdata = null;
180
181 try
182 {
183 jdata = (JetspeedRunData) data;
184 }
185 catch (ClassCastException e)
186 {
187 logger.error("The RunData object does not implement the expected interface, "
188 + "please verify the RunData factory settings");
189 return;
190 }
191 String language = (String) data.getRequest().getParameter("js_language");
192
193 if (null != language)
194 {
195 user.setPerm("language", language);
196 }
197
198
199 CustomLocalizationService locService =
200 (CustomLocalizationService) ServiceUtil.getServiceByName(LocalizationService.SERVICE_NAME);
201 Locale locale = locService.getLocale(data);
202 if (locale == null)
203 {
204 locale = new Locale(TurbineResources.getString("locale.default.language", "en"),
205 TurbineResources.getString("locale.default.country", "US"));
206 }
207
208 data.getUser().setTemp("locale", locale);
209
210
211
212 String paramPortlet = jdata.getParameters().getString("js_peid");
213 if (paramPortlet != null && paramPortlet.length() > 0)
214 {
215 jdata.setJs_peid(paramPortlet);
216 }
217
218 }
219
220 /***
221 * This session validator does not require a new session for each request
222 *
223 * @param data
224 * @return
225 */
226 public boolean requiresNewSession(RunData data)
227 {
228 return false;
229 }
230
231 /***
232 * Extracts user name from http headers
233 *
234 * @param data
235 * @return
236 * @exception Exception
237 */
238 private String getRemoteUser(RunData data) throws Exception
239 {
240 HttpServletRequest request = data.getRequest();
241 HttpServletResponse response = data.getResponse();
242
243 if (data.getUser().hasLoggedIn() && request.getMethod().equalsIgnoreCase("get"))
244 {
245 return data.getUser().getUserName();
246 }
247
248 String auth = request.getHeader("Authorization");
249 if (auth == null)
250 {
251 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
252 response.setHeader("WWW-Authenticate", "NTLM");
253 response.flushBuffer();
254
255 return null;
256 }
257 if (auth.startsWith("NTLM "))
258 {
259 byte[] msg = new sun.misc.BASE64Decoder().decodeBuffer(auth.substring(5));
260 int off = 0, length, offset;
261
262 if (msg[8] == 1)
263 {
264 response.setHeader("WWW-Authenticate", encodedMsg1);
265 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
266
267 return null;
268 }
269 else if (msg[8] == 3)
270 {
271 if (data.getUser().hasLoggedIn())
272 {
273 return data.getUser().getUserName();
274 }
275
276 off = 30;
277
278
279
280
281
282
283
284
285
286 length = msg[off + 9] * 256 + msg[off + 8];
287 offset = msg[off + 11] * 256 + msg[off + 10];
288 String username = new String(msg, offset, length);
289
290 return username;
291 }
292
293 }
294
295 return null;
296 }
297 }