001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jetspeed.security.impl.ntlm;
018:
019: import java.security.Principal;
020: import java.util.HashSet;
021: import java.util.Set;
022:
023: import javax.security.auth.Subject;
024:
025: import org.apache.commons.lang.StringUtils;
026: import org.apache.jetspeed.administration.PortalAuthenticationConfiguration;
027: import org.apache.jetspeed.pipeline.PipelineException;
028: import org.apache.jetspeed.request.RequestContext;
029: import org.apache.jetspeed.security.SecurityException;
030: import org.apache.jetspeed.security.SecurityHelper;
031: import org.apache.jetspeed.security.User;
032: import org.apache.jetspeed.security.UserManager;
033: import org.apache.jetspeed.security.UserPrincipal;
034: import org.apache.jetspeed.security.impl.AbstractSecurityValve;
035: import org.apache.jetspeed.security.impl.UserPrincipalImpl;
036: import org.apache.jetspeed.statistics.PortalStatistics;
037:
038: /**
039: * NTLMSecurityValve provides Subject creation based on the
040: * NTLM provided request.getRemoteUser() user name. When request.getRemoteUser() holds
041: * a valid value, then this user is authorized. Otherwise the username is retrieved
042: * from the Principal name in the request. In this way you can use NTLM authentication, with
043: * a fallback authentication method in case the user is not properly authenticated / authorized using
044: * NTLM.
045: *
046: * There are basically three authentication scenarios:
047: * <ol>
048: * <li>
049: * <p><b>The user is successfully authenticated and authorized by Ntml authentication</b></p>
050: * <p>A Subject is created, with Principal derived from the remoteUser value from Ntlm authentication</p>
051: * </li>
052: * <li>
053: * <p><b>The user is not authenticated by Ntlm, or the authenticated (can be NTLM or any other method) user cannot be authorized by Jetspeed.</b></p>
054: * <p>An anonymous Subject is created. The user can then be redirected to a login page for example.</p>
055: * </li>
056: * <li>
057: * <p><b>The user is authenticated by a (non-NTLM) authentication method, e.g. container-based form authentication.</b></p>
058: * <p>
059: * A subject is created based on the Principal name in the request.
060: * </p>
061: * </li>
062: * </ol>
063: * @author <a href="mailto:taylor@apache.org">David Sean Taylor </a>
064: * @author <a href="mailto:rwatler@finali.com">Randy Walter </a>
065: * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
066: * @author <a href="mailto:d.dam@hippo.nl">Dennis Dam</a>
067: * @version $Id$
068: */
069: public class NtlmSecurityValve extends AbstractSecurityValve {
070: private UserManager userMgr;
071: private PortalStatistics statistics;
072: private String networkDomain;
073: private boolean ntlmAuthRequired;
074: private boolean omitDomain;
075:
076: /**
077: * @param userMgr A UserManager
078: * @param statistics Portal Statistics
079: * @param networkDomain The network domain is used in combination with the <code>omitDomain</code> flag.
080: * @param omitDomain If <code>true</code>, then the network domain is stripped from the remoteUser name.
081: * @param ntlmAuthRequired if <code>true</code>, then an exception is thrown when there is no valid remoteUser,
082: * or the remoteUser cannot be authorized.
083: *
084: */
085: public NtlmSecurityValve(
086: UserManager userMgr,
087: String networkDomain,
088: boolean omitDomain,
089: boolean ntlmAuthRequired,
090: PortalStatistics statistics,
091: PortalAuthenticationConfiguration authenticationConfiguration) {
092: this .userMgr = userMgr;
093: this .statistics = statistics;
094: this .networkDomain = networkDomain;
095: this .ntlmAuthRequired = ntlmAuthRequired;
096: this .omitDomain = omitDomain;
097: this .authenticationConfiguration = authenticationConfiguration;
098: }
099:
100: public NtlmSecurityValve(UserManager userMgr, String networkDomain,
101: boolean omitDomain, boolean ntlmAuthRequired,
102: PortalStatistics statistics) {
103: this (userMgr, networkDomain, omitDomain, ntlmAuthRequired,
104: statistics, null);
105: }
106:
107: public NtlmSecurityValve(UserManager userMgr, String networkDomain,
108: boolean omitDomain, boolean ntlmAuthRequired) {
109: this (userMgr, networkDomain, omitDomain, ntlmAuthRequired, null);
110: }
111:
112: public String toString() {
113: return "NtlmSecurityValve";
114: }
115:
116: protected Principal getUserPrincipal(RequestContext context)
117: throws Exception {
118: Subject subject = getSubjectFromSession(context);
119: if (subject != null) {
120: return SecurityHelper.getPrincipal(subject,
121: UserPrincipal.class);
122: }
123: // otherwise return anonymous principal
124: return new UserPrincipalImpl(userMgr.getAnonymousUser());
125: }
126:
127: protected Subject getSubject(RequestContext context)
128: throws Exception {
129: Subject subject = getSubjectFromSession(context);
130: // Get remote user name set by web container
131: String userName = context.getRequest().getRemoteUser();
132: if (userName == null) {
133: if (ntlmAuthRequired) {
134: throw new PipelineException("Authorization failed.");
135: } else if (context.getRequest().getUserPrincipal() != null) {
136: userName = context.getRequest().getUserPrincipal()
137: .getName();
138: }
139: } else {
140: if (omitDomain && networkDomain != null) {
141: userName = StringUtils.stripStart(userName,
142: networkDomain + "\\");
143: }
144: }
145:
146: // check whether principal name stored in session subject equals the remote user name passed by the web container
147: if (subject != null) {
148: Principal subjectUserPrincipal = SecurityHelper
149: .getPrincipal(subject, UserPrincipal.class);
150: if ((subjectUserPrincipal == null)
151: || !subjectUserPrincipal.getName().equals(userName)) {
152: subject = null;
153: }
154: }
155: if (subject == null) {
156: if (userName != null) {
157: try {
158: User user = userMgr.getUser(userName);
159: if (user != null) {
160: subject = user.getSubject();
161: }
162: } catch (SecurityException sex) {
163: subject = null;
164: }
165:
166: if (subject == null && this .ntlmAuthRequired) {
167: throw new PipelineException(
168: "Authorization failed for user '"
169: + userName + "'.");
170: }
171: }
172: if (subject == null) {
173: // create anonymous user
174: Principal userPrincipal = getUserPrincipal(context);
175: Set principals = new HashSet();
176: principals.add(userPrincipal);
177: subject = new Subject(true, principals, new HashSet(),
178: new HashSet());
179: }
180:
181: // create a new statistics *user* session
182: if (statistics != null) {
183: statistics.logUserLogin(context, 0);
184: }
185: // put IP address in session for logout
186: context.setSessionAttribute(IP_ADDRESS, context
187: .getRequest().getRemoteAddr());
188: }
189:
190: return subject;
191: }
192: }
|