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