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:
018: package org.apache.catalina.realm;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.security.Principal;
023: import java.util.ArrayList;
024: import java.util.HashMap;
025: import java.util.Map;
026:
027: import javax.security.auth.Subject;
028: import javax.security.auth.callback.Callback;
029: import javax.security.auth.callback.CallbackHandler;
030: import javax.security.auth.callback.NameCallback;
031: import javax.security.auth.callback.PasswordCallback;
032: import javax.security.auth.callback.UnsupportedCallbackException;
033: import javax.security.auth.login.FailedLoginException;
034: import javax.security.auth.login.LoginException;
035: import javax.security.auth.spi.LoginModule;
036:
037: import org.apache.catalina.Context;
038: import org.apache.catalina.Realm;
039: import org.apache.catalina.connector.Request;
040: import org.apache.catalina.deploy.SecurityConstraint;
041: import org.apache.catalina.util.RequestUtil;
042: import org.apache.catalina.util.StringManager;
043: import org.apache.juli.logging.Log;
044: import org.apache.juli.logging.LogFactory;
045: import org.apache.tomcat.util.digester.Digester;
046:
047: /**
048: * <p>Implementation of the JAAS <strong>LoginModule</strong> interface,
049: * primarily for use in testing <code>JAASRealm</code>. It utilizes an
050: * XML-format data file of username/password/role information identical to
051: * that supported by <code>org.apache.catalina.realm.MemoryRealm</code>
052: * (except that digested passwords are not supported).</p>
053: *
054: * <p>This class recognizes the following string-valued options, which are
055: * specified in the configuration file (and passed to our constructor in
056: * the <code>options</code> argument:</p>
057: * <ul>
058: * <li><strong>debug</strong> - Set to "true" to get debugging messages
059: * generated to System.out. The default value is <code>false</code>.</li>
060: * <li><strong>pathname</strong> - Relative (to the pathname specified by the
061: * "catalina.base" system property) or absolute pahtname to the
062: * XML file containing our user information, in the format supported by
063: * {@link MemoryRealm}. The default value matches the MemoryRealm
064: * default.</li>
065: * </ul>
066: *
067: * <p><strong>IMPLEMENTATION NOTE</strong> - This class implements
068: * <code>Realm</code> only to satisfy the calling requirements of the
069: * <code>GenericPrincipal</code> constructor. It does not actually perform
070: * the functionality required of a <code>Realm</code> implementation.</p>
071: *
072: * @author Craig R. McClanahan
073: * @version $Revision: 543691 $ $Date: 2007-06-02 03:37:08 +0200 (sam., 02 juin 2007) $
074: */
075:
076: public class JAASMemoryLoginModule extends MemoryRealm implements
077: LoginModule, Realm {
078: // We need to extend MemoryRealm to avoid class cast
079:
080: private static Log log = LogFactory
081: .getLog(JAASMemoryLoginModule.class);
082:
083: // ----------------------------------------------------- Instance Variables
084:
085: /**
086: * The callback handler responsible for answering our requests.
087: */
088: protected CallbackHandler callbackHandler = null;
089:
090: /**
091: * Has our own <code>commit()</code> returned successfully?
092: */
093: protected boolean committed = false;
094:
095: /**
096: * The configuration information for this <code>LoginModule</code>.
097: */
098: protected Map options = null;
099:
100: /**
101: * The absolute or relative pathname to the XML configuration file.
102: */
103: protected String pathname = "conf/tomcat-users.xml";
104:
105: /**
106: * The <code>Principal</code> identified by our validation, or
107: * <code>null</code> if validation falied.
108: */
109: protected Principal principal = null;
110:
111: /**
112: * The set of <code>Principals</code> loaded from our configuration file.
113: */
114: protected HashMap principals = new HashMap();
115:
116: /**
117: * The string manager for this package.
118: */
119: protected static StringManager sm = StringManager
120: .getManager(Constants.Package);
121:
122: /**
123: * The state information that is shared with other configured
124: * <code>LoginModule</code> instances.
125: */
126: protected Map sharedState = null;
127:
128: /**
129: * The subject for which we are performing authentication.
130: */
131: protected Subject subject = null;
132:
133: // --------------------------------------------------------- Public Methods
134:
135: public JAASMemoryLoginModule() {
136: log.debug("MEMORY LOGIN MODULE");
137: }
138:
139: /**
140: * Phase 2 of authenticating a <code>Subject</code> when Phase 1
141: * fails. This method is called if the <code>LoginContext</code>
142: * failed somewhere in the overall authentication chain.
143: *
144: * @return <code>true</code> if this method succeeded, or
145: * <code>false</code> if this <code>LoginModule</code> should be
146: * ignored
147: *
148: * @exception LoginException if the abort fails
149: */
150: public boolean abort() throws LoginException {
151:
152: // If our authentication was not successful, just return false
153: if (principal == null)
154: return (false);
155:
156: // Clean up if overall authentication failed
157: if (committed)
158: logout();
159: else {
160: committed = false;
161: principal = null;
162: }
163: log.debug("Abort");
164: return (true);
165:
166: }
167:
168: /**
169: * Phase 2 of authenticating a <code>Subject</code> when Phase 1
170: * was successful. This method is called if the <code>LoginContext</code>
171: * succeeded in the overall authentication chain.
172: *
173: * @return <code>true</code> if the authentication succeeded, or
174: * <code>false</code> if this <code>LoginModule</code> should be
175: * ignored
176: *
177: * @exception LoginException if the commit fails
178: */
179: public boolean commit() throws LoginException {
180: log.debug("commit " + principal);
181:
182: // If authentication was not successful, just return false
183: if (principal == null)
184: return (false);
185:
186: // Add our Principal to the Subject if needed
187: if (!subject.getPrincipals().contains(principal))
188: subject.getPrincipals().add(principal);
189:
190: committed = true;
191: return (true);
192:
193: }
194:
195: /**
196: * Return the SecurityConstraints configured to guard the request URI for
197: * this request, or <code>null</code> if there is no such constraint.
198: *
199: * @param request Request we are processing
200: * @param context Context the Request is mapped to
201: */
202: public SecurityConstraint[] findSecurityConstraints(
203: Request request, Context context) {
204: ArrayList<SecurityConstraint> results = null;
205: // Are there any defined security constraints?
206: SecurityConstraint constraints[] = context.findConstraints();
207: if ((constraints == null) || (constraints.length == 0)) {
208: if (context.getLogger().isDebugEnabled())
209: context.getLogger().debug(
210: " No applicable constraints defined");
211: return (null);
212: }
213:
214: // Check each defined security constraint
215: String uri = request.getDecodedRequestURI();
216: String contextPath = request.getContextPath();
217: if (contextPath.length() > 0)
218: uri = uri.substring(contextPath.length());
219: uri = RequestUtil.URLDecode(uri); // Before checking constraints
220: String method = request.getMethod();
221: for (int i = 0; i < constraints.length; i++) {
222: if (context.getLogger().isDebugEnabled())
223: context.getLogger().debug(
224: " Checking constraint '" + constraints[i]
225: + "' against " + method + " " + uri
226: + " --> "
227: + constraints[i].included(uri, method));
228: if (constraints[i].included(uri, method)) {
229: if (results == null) {
230: results = new ArrayList<SecurityConstraint>();
231: }
232: results.add(constraints[i]);
233: }
234: }
235:
236: // No applicable security constraint was found
237: if (context.getLogger().isDebugEnabled())
238: context.getLogger().debug(
239: " No applicable constraint located");
240: if (results == null)
241: return null;
242: SecurityConstraint[] array = new SecurityConstraint[results
243: .size()];
244: System.arraycopy(results.toArray(), 0, array, 0, array.length);
245: return array;
246: }
247:
248: /**
249: * Initialize this <code>LoginModule</code> with the specified
250: * configuration information.
251: *
252: * @param subject The <code>Subject</code> to be authenticated
253: * @param callbackHandler A <code>CallbackHandler</code> for communicating
254: * with the end user as necessary
255: * @param sharedState State information shared with other
256: * <code>LoginModule</code> instances
257: * @param options Configuration information for this specific
258: * <code>LoginModule</code> instance
259: */
260: public void initialize(Subject subject,
261: CallbackHandler callbackHandler, Map sharedState,
262: Map options) {
263: log.debug("Init");
264:
265: // Save configuration values
266: this .subject = subject;
267: this .callbackHandler = callbackHandler;
268: this .sharedState = sharedState;
269: this .options = options;
270:
271: // Perform instance-specific initialization
272: if (options.get("pathname") != null)
273: this .pathname = (String) options.get("pathname");
274:
275: // Load our defined Principals
276: load();
277:
278: }
279:
280: /**
281: * Phase 1 of authenticating a <code>Subject</code>.
282: *
283: * @return <code>true</code> if the authentication succeeded, or
284: * <code>false</code> if this <code>LoginModule</code> should be
285: * ignored
286: *
287: * @exception LoginException if the authentication fails
288: */
289: public boolean login() throws LoginException {
290:
291: // Set up our CallbackHandler requests
292: if (callbackHandler == null)
293: throw new LoginException("No CallbackHandler specified");
294: Callback callbacks[] = new Callback[2];
295: callbacks[0] = new NameCallback("Username: ");
296: callbacks[1] = new PasswordCallback("Password: ", false);
297:
298: // Interact with the user to retrieve the username and password
299: String username = null;
300: String password = null;
301: try {
302: callbackHandler.handle(callbacks);
303: username = ((NameCallback) callbacks[0]).getName();
304: password = new String(((PasswordCallback) callbacks[1])
305: .getPassword());
306: } catch (IOException e) {
307: throw new LoginException(e.toString());
308: } catch (UnsupportedCallbackException e) {
309: throw new LoginException(e.toString());
310: }
311:
312: // Validate the username and password we have received
313: principal = super .authenticate(username, password);
314:
315: log.debug("login " + username + " " + principal);
316:
317: // Report results based on success or failure
318: if (principal != null) {
319: return (true);
320: } else {
321: throw new FailedLoginException(
322: "Username or password is incorrect");
323: }
324:
325: }
326:
327: /**
328: * Log out this user.
329: *
330: * @return <code>true</code> in all cases because thie
331: * <code>LoginModule</code> should not be ignored
332: *
333: * @exception LoginException if logging out failed
334: */
335: public boolean logout() throws LoginException {
336:
337: subject.getPrincipals().remove(principal);
338: committed = false;
339: principal = null;
340: return (true);
341:
342: }
343:
344: // ---------------------------------------------------------- Realm Methods
345: // ------------------------------------------------------ Protected Methods
346:
347: /**
348: * Load the contents of our configuration file.
349: */
350: protected void load() {
351:
352: // Validate the existence of our configuration file
353: File file = new File(pathname);
354: if (!file.isAbsolute())
355: file = new File(System.getProperty("catalina.base"),
356: pathname);
357: if (!file.exists() || !file.canRead()) {
358: log.warn("Cannot load configuration file "
359: + file.getAbsolutePath());
360: return;
361: }
362:
363: // Load the contents of our configuration file
364: Digester digester = new Digester();
365: digester.setValidating(false);
366: digester.addRuleSet(new MemoryRuleSet());
367: try {
368: digester.push(this );
369: digester.parse(file);
370: } catch (Exception e) {
371: log.warn("Error processing configuration file "
372: + file.getAbsolutePath(), e);
373: return;
374: } finally {
375: digester.reset();
376: }
377:
378: }
379: }
|