1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.realm;
20
21
22 import java.security.Principal;
23 import java.io.File;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Map;
27 import org.apache.catalina.LifecycleException;
28 import org.apache.catalina.util.StringManager;
29 import org.apache.juli.logging.Log;
30 import org.apache.juli.logging.LogFactory;
31 import org.apache.tomcat.util.digester.Digester;
32
33
34 /**
35 * Simple implementation of <b>Realm</b> that reads an XML file to configure
36 * the valid users, passwords, and roles. The file format (and default file
37 * location) are identical to those currently supported by Tomcat 3.X.
38 * <p>
39 * <strong>IMPLEMENTATION NOTE</strong>: It is assumed that the in-memory
40 * collection representing our defined users (and their roles) is initialized
41 * at application startup and never modified again. Therefore, no thread
42 * synchronization is performed around accesses to the principals collection.
43 *
44 * @author Craig R. McClanahan
45 * @version $Revision: 543691 $ $Date: 2007-06-02 03:37:08 +0200 (sam., 02 juin 2007) $
46 */
47
48 public class MemoryRealm extends RealmBase {
49
50 private static Log log = LogFactory.getLog(MemoryRealm.class);
51
52 // ----------------------------------------------------- Instance Variables
53
54
55 /**
56 * The Digester we will use to process in-memory database files.
57 */
58 private static Digester digester = null;
59
60
61 /**
62 * Descriptive information about this Realm implementation.
63 */
64 protected final String info =
65 "org.apache.catalina.realm.MemoryRealm/1.0";
66
67
68 /**
69 * Descriptive information about this Realm implementation.
70 */
71
72 protected static final String name = "MemoryRealm";
73
74
75 /**
76 * The pathname (absolute or relative to Catalina's current working
77 * directory) of the XML file containing our database information.
78 */
79 private String pathname = "conf/tomcat-users.xml";
80
81
82 /**
83 * The set of valid Principals for this Realm, keyed by user name.
84 */
85 private Map<String,GenericPrincipal> principals =
86 new HashMap<String,GenericPrincipal>();
87
88
89 /**
90 * The string manager for this package.
91 */
92 private static StringManager sm =
93 StringManager.getManager(Constants.Package);
94
95
96 // ------------------------------------------------------------- Properties
97
98
99 /**
100 * Return descriptive information about this Realm implementation and
101 * the corresponding version number, in the format
102 * <code><description>/<version></code>.
103 */
104 public String getInfo() {
105
106 return info;
107
108 }
109
110
111 /**
112 * Return the pathname of our XML file containing user definitions.
113 */
114 public String getPathname() {
115
116 return pathname;
117
118 }
119
120
121 /**
122 * Set the pathname of our XML file containing user definitions. If a
123 * relative pathname is specified, it is resolved against "catalina.base".
124 *
125 * @param pathname The new pathname
126 */
127 public void setPathname(String pathname) {
128
129 this.pathname = pathname;
130
131 }
132
133
134 // --------------------------------------------------------- Public Methods
135
136
137 /**
138 * Return the Principal associated with the specified username and
139 * credentials, if there is one; otherwise return <code>null</code>.
140 *
141 * @param username Username of the Principal to look up
142 * @param credentials Password or other credentials to use in
143 * authenticating this username
144 */
145 public Principal authenticate(String username, String credentials) {
146
147 GenericPrincipal principal =
148 (GenericPrincipal) principals.get(username);
149
150 boolean validated = false;
151 if (principal != null) {
152 if (hasMessageDigest()) {
153 // Hex hashes should be compared case-insensitive
154 validated = (digest(credentials)
155 .equalsIgnoreCase(principal.getPassword()));
156 } else {
157 validated =
158 (digest(credentials).equals(principal.getPassword()));
159 }
160 }
161
162 if (validated) {
163 if (log.isDebugEnabled())
164 log.debug(sm.getString("memoryRealm.authenticateSuccess", username));
165 return (principal);
166 } else {
167 if (log.isDebugEnabled())
168 log.debug(sm.getString("memoryRealm.authenticateFailure", username));
169 return (null);
170 }
171
172 }
173
174
175 // -------------------------------------------------------- Package Methods
176
177
178 /**
179 * Add a new user to the in-memory database.
180 *
181 * @param username User's username
182 * @param password User's password (clear text)
183 * @param roles Comma-delimited set of roles associated with this user
184 */
185 void addUser(String username, String password, String roles) {
186
187 // Accumulate the list of roles for this user
188 ArrayList<String> list = new ArrayList<String>();
189 roles += ",";
190 while (true) {
191 int comma = roles.indexOf(',');
192 if (comma < 0)
193 break;
194 String role = roles.substring(0, comma).trim();
195 list.add(role);
196 roles = roles.substring(comma + 1);
197 }
198
199 // Construct and cache the Principal for this user
200 GenericPrincipal principal =
201 new GenericPrincipal(this, username, password, list);
202 principals.put(username, principal);
203
204 }
205
206
207 // ------------------------------------------------------ Protected Methods
208
209
210 /**
211 * Return a configured <code>Digester</code> to use for processing
212 * the XML input file, creating a new one if necessary.
213 */
214 protected synchronized Digester getDigester() {
215
216 if (digester == null) {
217 digester = new Digester();
218 digester.setValidating(false);
219 digester.addRuleSet(new MemoryRuleSet());
220 }
221 return (digester);
222
223 }
224
225
226 /**
227 * Return a short name for this Realm implementation.
228 */
229 protected String getName() {
230
231 return (name);
232
233 }
234
235
236 /**
237 * Return the password associated with the given principal's user name.
238 */
239 protected String getPassword(String username) {
240
241 GenericPrincipal principal =
242 (GenericPrincipal) principals.get(username);
243 if (principal != null) {
244 return (principal.getPassword());
245 } else {
246 return (null);
247 }
248
249 }
250
251
252 /**
253 * Return the Principal associated with the given user name.
254 */
255 protected Principal getPrincipal(String username) {
256
257 return (Principal) principals.get(username);
258
259 }
260
261 /**
262 * Returns the principals for this realm.
263 *
264 * @return The principals, keyed by user name (a String)
265 */
266 protected Map getPrincipals() {
267 return principals;
268 }
269
270
271 // ------------------------------------------------------ Lifecycle Methods
272
273
274 /**
275 * Prepare for active use of the public methods of this Component.
276 *
277 * @exception LifecycleException if this component detects a fatal error
278 * that prevents it from being started
279 */
280 public synchronized void start() throws LifecycleException {
281
282 // Perform normal superclass initialization
283 super.start();
284
285 // Validate the existence of our database file
286 File file = new File(pathname);
287 if (!file.isAbsolute())
288 file = new File(System.getProperty("catalina.base"), pathname);
289 if (!file.exists() || !file.canRead())
290 throw new LifecycleException
291 (sm.getString("memoryRealm.loadExist",
292 file.getAbsolutePath()));
293
294 // Load the contents of the database file
295 if (log.isDebugEnabled())
296 log.debug(sm.getString("memoryRealm.loadPath",
297 file.getAbsolutePath()));
298 Digester digester = getDigester();
299 try {
300 synchronized (digester) {
301 digester.push(this);
302 digester.parse(file);
303 }
304 } catch (Exception e) {
305 throw new LifecycleException
306 (sm.getString("memoryRealm.readXml"), e);
307 } finally {
308 digester.reset();
309 }
310
311 }
312
313
314 /**
315 * Gracefully shut down active use of the public methods of this Component.
316 *
317 * @exception LifecycleException if this component detects a fatal error
318 * that needs to be reported
319 */
320 public synchronized void stop() throws LifecycleException {
321
322 // Perform normal superclass finalization
323 super.stop();
324
325 // No shutdown activities required
326
327 }
328
329
330 }