/*
* Copyright 2000-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jetspeed.modules.actions;
// JDK stuff
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.apache.jetspeed.om.security.JetspeedUser;
import org.apache.jetspeed.services.JetspeedSecurity;
import org.apache.jetspeed.services.customlocalization.CustomLocalizationService;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
import org.apache.jetspeed.services.security.JetspeedSecurityCache;
import org.apache.jetspeed.services.security.LoginException;
import org.apache.jetspeed.services.security.UnknownUserException;
import org.apache.jetspeed.util.ServiceUtil;
import org.apache.turbine.services.localization.LocalizationService;
import org.apache.turbine.services.resources.TurbineResources;
import org.apache.turbine.util.RunData;
/**
* Just like org.apache.turbine.modules.actions.sessionvalidator.
* TemplateSessionValidator except:
* <ul>
* <li>it doesn't check the session_access_counter
* <li>it automatically logs the user in based on currently authenticated nt
* user
* <li>expects a JetspeedRunData object and put there the additionnal jetspeed
* properties
* </ul>
* <B>Usage of this session validator is limited to Windows NT/2000 and MS
* Internet Explorer.</B>
* <P>
* To activate this session validator, set <CODE>action.sessionvalidator</CODE>
* in tr.props to <code>NTLMSessionValidator</code>.
* </P>
* <P>
* When this session validator is active, the following algorithm is used to
* display appropriate psml:
*
* <pre>
* Check authentication status
* If user is not authenticated to machine running the portal
* Pop the standard login box
* If user passes authentication
* Attempt to retrieve matching user profile
* If matching profile found
* Render the profile
* Else
* Retrieve and render anonymous profile
* End
* Else If authentication fails
* Keep prompting for login information
* Else If the user cancels the login box
* Retrieve and render anonymous profile
* End
* Else
* Attempt to retrieve matching user profile
* If matching profile found
* Render the profile
* Else
* Retrieve and render anonymous profile
* End
* End
* </pre>
*
* </P>
* <P>
* Optionally, certain characters may be removed from the username before it's
* passed to the JetspeedSecurity. These characters may be specified by setting
* <CODE>NTLMSessionValidator.chars.to.remove</CODE> property. For example, if
* invalid characters list is '#@$', username '#user1' will be returned as
* 'user1'.
* </P>
*
* @author <a href="mailto:morciuch@apache.org">Mark Orciuch</a>
* @version $Id: NTLMSessionValidator.java,v 1.6 2004/02/23 02:59:06 jford Exp $
* @see org.apache.turbine.modules.actions.sessionvalidator.TemplateSessionValidator
* @see http://www.innovation.ch/java/ntlm.html
* @since 1.4b5
*/
public class NTLMSessionValidator extends TemplateSessionValidator {
private static final String INVALID_CHARS_KEY = "NTLMSessionValidator.chars.to.remove";
private final String invalidChars = org.apache.jetspeed.services.resources.JetspeedResources
.getString(INVALID_CHARS_KEY, null);
private static final byte z = 0;
private static final byte[] msg1 = { (byte) 'N', (byte) 'T', (byte) 'L',
(byte) 'M', (byte) 'S', (byte) 'S', (byte) 'P', z, (byte) 2, z, z, z, z, z,
z, z, (byte) 40, z, z, z, (byte) 1, (byte) 130, z, z, z, (byte) 2,
(byte) 2, (byte) 2, z, z, z, z, z, z, z, z, z, z, z, z };
private static final String encodedMsg1 = "NTLM "
+ new String(Base64.encodeBase64(msg1));
/**
* Static initialization of the logger for this class
*/
private static final JetspeedLogger logger = JetspeedLogFactoryService
.getLogger(NTLMSessionValidator.class.getName());
/**
* Execute the action.
*
* @param data
* Turbine information.
* @exception Exception
* , a generic exception.
*/
@Override
public void doPerform(RunData data) throws Exception {
// first, invoke our superclass action to make sure
// we follow Turbine evolutions
// FIXME: if the user is not found (this can happen, for instance,
// if the anonymous user is not in the DB), it throws a terrible exception
// in the user's face
super.doPerform(data);
JetspeedUser user = (JetspeedUser) data.getUser();
// get remote user from ntlm
String userName = this.getRemoteUser(data);
if ((user == null || !user.hasLoggedIn())) {
if (userName != null && userName.length() > 0) {
byte[] temp = userName.getBytes();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < temp.length; i++) {
if (temp[i] != 0) {
if (invalidChars == null || invalidChars.indexOf(temp[i]) < 0) {
buffer.append((char) temp[i]);
}
}
}
userName = buffer.toString();
try {
user = JetspeedSecurity.getUser(userName);
data.setUser(user);
user.setHasLoggedIn(new Boolean(true));
user.updateLastLogin();
data.save();
if (JetspeedSecurityCache.getAcl(userName) == null) {
JetspeedSecurityCache.load(userName);
}
logger.info("NTLMSessionValidator: automatic login using ["
+ userName + "]");
} catch (LoginException noSuchUser) {
// user not found - ignore it - they will not be logged in
// automatically
} catch (UnknownUserException unknownUser) {
// user not found - ignore it - they will not be logged in
// automatically
if (logger.isWarnEnabled()) {
logger.warn("NTLMSessionValidator: username [" + userName
+ "] does not exist or authentication failed, "
+ "redirecting to anon profile");
}
}
}
}
// now, define Jetspeed specific properties, using the customized
// RunData properties
JetspeedRunData jdata = null;
try {
jdata = (JetspeedRunData) data;
} catch (ClassCastException e) {
logger
.error("The RunData object does not implement the expected interface, "
+ "please verify the RunData factory settings");
return;
}
String language = data.getRequest().getParameter("js_language");
if (null != language) {
user.setPerm("language", language);
}
// Get the locale store it in the user object
CustomLocalizationService locService = (CustomLocalizationService) ServiceUtil
.getServiceByName(LocalizationService.SERVICE_NAME);
Locale locale = locService.getLocale(data);
if (locale == null) {
locale = new Locale(TurbineResources.getString("locale.default.language",
"en"), TurbineResources.getString("locale.default.country", "US"));
}
data.getUser().setTemp("locale", locale);
// if a portlet is referenced in the parameters request, store it
// in the RunData object
String paramPortlet = jdata.getParameters().getString("js_peid");
if (paramPortlet != null && paramPortlet.length() > 0) {
jdata.setJs_peid(paramPortlet);
}
}
/**
* This session validator does not require a new session for each request
*
* @param data
* @return
*/
@Override
public boolean requiresNewSession(RunData data) {
return false;
}
/**
* Extracts user name from http headers
*
* @param data
* @return
* @exception Exception
*/
private String getRemoteUser(RunData data) throws Exception {
HttpServletRequest request = data.getRequest();
HttpServletResponse response = data.getResponse();
if (data.getUser().hasLoggedIn()
&& request.getMethod().equalsIgnoreCase("get")) {
return data.getUser().getUserName();
}
String auth = request.getHeader("Authorization");
if (auth == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "NTLM");
response.flushBuffer();
return null;
}
if (auth.startsWith("NTLM ")) {
byte[] msg = Base64.decodeBase64(auth.substring(5).getBytes());
int off = 0, length, offset;
if (msg[8] == 1) {
response.setHeader("WWW-Authenticate", encodedMsg1);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return null;
} else if (msg[8] == 3) {
if (data.getUser().hasLoggedIn()) {
return data.getUser().getUserName();
}
off = 30;
// length = msg[off + 17] * 256 + msg[off + 16];
// offset = msg[off + 19] * 256 + msg[off + 18];
// String remoteHost = new String(msg, offset, length);
// length = msg[off + 1] * 256 + msg[off];
// offset = msg[off + 3] * 256 + msg[off + 2];
// String domain = new String(msg, offset, length);
length = msg[off + 9] * 256 + msg[off + 8];
offset = msg[off + 11] * 256 + msg[off + 10];
String username = new String(msg, offset, length);
return username;
}
}
return null;
}
}