001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.catalina.realm;
018:
019: import java.security.Principal;
020: import java.io.File;
021: import java.util.ArrayList;
022: import java.util.HashMap;
023: import org.apache.catalina.Container;
024: import org.apache.catalina.LifecycleException;
025: import org.apache.catalina.util.StringManager;
026: import org.apache.commons.digester.Digester;
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: /**
031: * Simple implementation of <b>Realm</b> that reads an XML file to configure
032: * the valid users, passwords, and roles. The file format (and default file
033: * location) are identical to those currently supported by Tomcat 3.X.
034: * <p>
035: * <strong>IMPLEMENTATION NOTE</strong>: It is assumed that the in-memory
036: * collection representing our defined users (and their roles) is initialized
037: * at application startup and never modified again. Therefore, no thread
038: * synchronization is performed around accesses to the principals collection.
039: *
040: * @author Craig R. McClanahan
041: * @version $Revision: 1.4 $ $Date: 2004/02/27 14:58:45 $
042: */
043:
044: public class MemoryRealm extends RealmBase {
045:
046: private static Log log = LogFactory.getLog(MemoryRealm.class);
047:
048: // ----------------------------------------------------- Instance Variables
049:
050: /**
051: * The Container with which this Realm is associated.
052: */
053: private Container container = null;
054:
055: /**
056: * The Digester we will use to process in-memory database files.
057: */
058: private static Digester digester = null;
059:
060: /**
061: * Descriptive information about this Realm implementation.
062: */
063: protected final String info = "org.apache.catalina.realm.MemoryRealm/1.0";
064:
065: /**
066: * Descriptive information about this Realm implementation.
067: */
068:
069: protected static final String name = "MemoryRealm";
070:
071: /**
072: * The pathname (absolute or relative to Catalina's current working
073: * directory) of the XML file containing our database information.
074: */
075: private String pathname = "conf/tomcat-users.xml";
076:
077: /**
078: * The set of valid Principals for this Realm, keyed by user name.
079: */
080: private HashMap principals = new HashMap();
081:
082: /**
083: * The string manager for this package.
084: */
085: private static StringManager sm = StringManager
086: .getManager(Constants.Package);
087:
088: /**
089: * Has this component been started?
090: */
091: private boolean started = false;
092:
093: // ------------------------------------------------------------- Properties
094:
095: /**
096: * Return descriptive information about this Realm implementation and
097: * the corresponding version number, in the format
098: * <code><description>/<version></code>.
099: */
100: public String getInfo() {
101:
102: return info;
103:
104: }
105:
106: /**
107: * Return the pathname of our XML file containing user definitions.
108: */
109: public String getPathname() {
110:
111: return pathname;
112:
113: }
114:
115: /**
116: * Set the pathname of our XML file containing user definitions. If a
117: * relative pathname is specified, it is resolved against "catalina.base".
118: *
119: * @param pathname The new pathname
120: */
121: public void setPathname(String pathname) {
122:
123: this .pathname = pathname;
124:
125: }
126:
127: // --------------------------------------------------------- Public Methods
128:
129: /**
130: * Return the Principal associated with the specified username and
131: * credentials, if there is one; otherwise return <code>null</code>.
132: *
133: * @param username Username of the Principal to look up
134: * @param credentials Password or other credentials to use in
135: * authenticating this username
136: */
137: public Principal authenticate(String username, String credentials) {
138:
139: GenericPrincipal principal = (GenericPrincipal) principals
140: .get(username);
141:
142: boolean validated = false;
143: if (principal != null) {
144: if (hasMessageDigest()) {
145: // Hex hashes should be compared case-insensitive
146: validated = (digest(credentials)
147: .equalsIgnoreCase(principal.getPassword()));
148: } else {
149: validated = (digest(credentials).equals(principal
150: .getPassword()));
151: }
152: }
153:
154: if (validated) {
155: if (log.isDebugEnabled())
156: log.debug(sm.getString(
157: "memoryRealm.authenticateSuccess", username));
158: return (principal);
159: } else {
160: if (log.isDebugEnabled())
161: log.debug(sm.getString(
162: "memoryRealm.authenticateFailure", username));
163: return (null);
164: }
165:
166: }
167:
168: // -------------------------------------------------------- Package Methods
169:
170: /**
171: * Add a new user to the in-memory database.
172: *
173: * @param username User's username
174: * @param password User's password (clear text)
175: * @param roles Comma-delimited set of roles associated with this user
176: */
177: void addUser(String username, String password, String roles) {
178:
179: // Accumulate the list of roles for this user
180: ArrayList list = new ArrayList();
181: roles += ",";
182: while (true) {
183: int comma = roles.indexOf(',');
184: if (comma < 0)
185: break;
186: String role = roles.substring(0, comma).trim();
187: list.add(role);
188: roles = roles.substring(comma + 1);
189: }
190:
191: // Construct and cache the Principal for this user
192: GenericPrincipal principal = new GenericPrincipal(this ,
193: username, password, list);
194: principals.put(username, principal);
195:
196: }
197:
198: // ------------------------------------------------------ Protected Methods
199:
200: /**
201: * Return a configured <code>Digester</code> to use for processing
202: * the XML input file, creating a new one if necessary.
203: */
204: protected synchronized Digester getDigester() {
205:
206: if (digester == null) {
207: digester = new Digester();
208: digester.setValidating(false);
209: digester.addRuleSet(new MemoryRuleSet());
210: }
211: return (digester);
212:
213: }
214:
215: /**
216: * Return a short name for this Realm implementation.
217: */
218: protected String getName() {
219:
220: return (name);
221:
222: }
223:
224: /**
225: * Return the password associated with the given principal's user name.
226: */
227: protected String getPassword(String username) {
228:
229: GenericPrincipal principal = (GenericPrincipal) principals
230: .get(username);
231: if (principal != null) {
232: return (principal.getPassword());
233: } else {
234: return (null);
235: }
236:
237: }
238:
239: /**
240: * Return the Principal associated with the given user name.
241: */
242: protected Principal getPrincipal(String username) {
243:
244: return (Principal) principals.get(username);
245:
246: }
247:
248: // ------------------------------------------------------ Lifecycle Methods
249:
250: /**
251: * Prepare for active use of the public methods of this Component.
252: *
253: * @exception LifecycleException if this component detects a fatal error
254: * that prevents it from being started
255: */
256: public synchronized void start() throws LifecycleException {
257:
258: // Validate the existence of our database file
259: File file = new File(pathname);
260: if (!file.isAbsolute())
261: file = new File(System.getProperty("catalina.base"),
262: pathname);
263: if (!file.exists() || !file.canRead())
264: throw new LifecycleException(sm.getString(
265: "memoryRealm.loadExist", file.getAbsolutePath()));
266:
267: // Load the contents of the database file
268: if (log.isDebugEnabled())
269: log.debug(sm.getString("memoryRealm.loadPath", file
270: .getAbsolutePath()));
271: Digester digester = getDigester();
272: try {
273: synchronized (digester) {
274: digester.push(this );
275: digester.parse(file);
276: }
277: } catch (Exception e) {
278: throw new LifecycleException("memoryRealm.readXml", e);
279: }
280:
281: // Perform normal superclass initialization
282: super .start();
283:
284: }
285:
286: /**
287: * Gracefully shut down active use of the public methods of this Component.
288: *
289: * @exception LifecycleException if this component detects a fatal error
290: * that needs to be reported
291: */
292: public synchronized void stop() throws LifecycleException {
293:
294: // Perform normal superclass finalization
295: super .stop();
296:
297: // No shutdown activities required
298:
299: }
300:
301: }
|