001: // GenericAuthFilter.java
002: // $Id: GenericAuthFilter.java,v 1.19 2007/02/09 22:39:25 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigsaw.auth;
007:
008: import java.io.PrintStream;
009:
010: import java.util.Enumeration;
011:
012: import java.net.InetAddress;
013:
014: import org.w3c.tools.resources.Attribute;
015: import org.w3c.tools.resources.AttributeHolder;
016: import org.w3c.tools.resources.AttributeRegistry;
017: import org.w3c.tools.resources.FramedResource;
018: import org.w3c.tools.resources.InvalidResourceException;
019: import org.w3c.tools.resources.ProtocolException;
020: import org.w3c.tools.resources.Resource;
021: import org.w3c.tools.resources.ResourceFilter;
022: import org.w3c.tools.resources.ResourceFrame;
023: import org.w3c.tools.resources.ResourceReference;
024: import org.w3c.tools.resources.StringArrayAttribute;
025:
026: import org.w3c.jigsaw.http.Client;
027: import org.w3c.jigsaw.http.HTTPException;
028: import org.w3c.jigsaw.http.Reply;
029: import org.w3c.jigsaw.http.Request;
030: import org.w3c.jigsaw.http.httpd;
031:
032: import org.w3c.jigsaw.html.HtmlGenerator;
033:
034: import org.w3c.tools.codec.Base64Decoder;
035: import org.w3c.tools.codec.Base64FormatException;
036:
037: import org.w3c.www.http.HTTP;
038: import org.w3c.www.http.HttpChallenge;
039: import org.w3c.www.http.HttpCredential;
040: import org.w3c.www.http.HttpFactory;
041: import org.w3c.www.http.HttpReplyMessage;
042: import org.w3c.www.http.HttpRequestMessage;
043:
044: import org.w3c.tools.resources.ProtocolException;
045:
046: /**
047: * A generic authentication filter.
048: * This filter will use both IP and basic authentication to try to authenticate
049: * incomming request. It should not be use for big user's database (typically
050: * the ones that have more than 1000 entries).
051: */
052:
053: class BasicAuthContextException extends Exception {
054:
055: BasicAuthContextException(String msg) {
056: super (msg);
057: }
058: }
059:
060: class BasicAuthContext {
061: String user = null;
062: String password = null;
063: String cookie = null;
064:
065: public String toString() {
066: return user + ":" + password;
067: }
068:
069: BasicAuthContext(Request request) throws BasicAuthContextException,
070: ProtocolException {
071: HttpCredential credential = null;
072:
073: credential = (request.isProxy() ? request
074: .getProxyAuthorization() : request.getAuthorization());
075: if (!credential.getScheme().equalsIgnoreCase("Basic")) {
076: String msg = ("Invalid authentication scheme \""
077: + credential.getScheme() + " expecting \"Basic\"");
078: throw new BasicAuthContextException(msg);
079: }
080: // Decode the credentials:
081: String decoded = null;
082: this .cookie = credential.getAuthParameter("cookie");
083: try {
084: Base64Decoder b = new Base64Decoder(cookie);
085: decoded = b.processString();
086: } catch (Base64FormatException e) {
087: String msg = "Invalid BASE64 encoding of credentials.";
088: throw new BasicAuthContextException(msg);
089: }
090: // Get user and password:
091: int icolon = decoded.indexOf(':');
092: if ((icolon > 0) && (icolon + 1 < decoded.length())) {
093: // ok, parse was find, check user:
094: this .user = decoded.substring(0, icolon);
095: this .password = decoded.substring(icolon + 1);
096: } else {
097: String msg = "Invalid credentials syntax in " + decoded;
098: throw new BasicAuthContextException(msg);
099: }
100: }
101: }
102:
103: /**
104: * GenericAuthFilter provides for both IP and basic authentication.
105: * This is really a first implementation. It looses on several points:
106: * <ul>
107: * <li>AuthUser instances, being a subclass of resource dump their classes
108: * along with their attributes, although here we know that they will all
109: * be instances of AuthUser.
110: * <li>The way the ipmatcher is maintained doesn't make much sense.
111: * <li>The way groups are handled is no good.
112: * <li>The SimpleResourceStore is not an adequat store for the user database,
113: * it should rather use the jdbmResourceStore (not written yet).
114: * </ul>
115: * However, this provides for the basic functionnalities.
116: */
117:
118: public class GenericAuthFilter extends AuthFilter {
119: /**
120: * Attribute index - The list of allowed users.
121: */
122: protected static int ATTR_ALLOWED_USERS = -1;
123: /**
124: * Attribute index - The list of allowed groups.
125: */
126: protected static int ATTR_ALLOWED_GROUPS = -1;
127:
128: static {
129: Attribute a = null;
130: Class c = null;
131: try {
132: c = Class.forName("org.w3c.jigsaw.auth.GenericAuthFilter");
133: } catch (Exception ex) {
134: ex.printStackTrace();
135: System.exit(1);
136: }
137: // The list of allowed users
138: a = new StringArrayAttribute("users", null, Attribute.EDITABLE);
139: ATTR_ALLOWED_USERS = AttributeRegistry.registerAttribute(c, a);
140: // The list of allowed groups:
141: a = new StringArrayAttribute("groups", null, Attribute.EDITABLE);
142: ATTR_ALLOWED_GROUPS = AttributeRegistry.registerAttribute(c, a);
143: }
144:
145: /**
146: * The IPMatcher to match IP templates to user records.
147: */
148: protected IPMatcher ipmatcher = null;
149: /**
150: * The catalog of realms that make our scope.
151: */
152: protected RealmsCatalog catalog = null;
153: /**
154: * Our associated realm.
155: */
156: protected ResourceReference rr_realm = null;
157: /**
158: * The nam of the realm we cache in <code>realm</code>.
159: */
160: protected String loaded_realm = null;
161:
162: /**
163: * The challenge to issue to any client for Basic Authentication.
164: */
165: protected HttpChallenge challenge = null;
166:
167: /**
168: * Get a pointer to our realm, and initialize our ipmatcher.
169: */
170:
171: protected synchronized void acquireRealm() {
172: // Get our catalog:
173: if (catalog == null) {
174: httpd server = (httpd) ((FramedResource) getTargetResource())
175: .getServer();
176: catalog = server.getRealmsCatalog();
177: }
178: // Check that our realm name is valid:
179: String name = getRealm();
180: if (name == null)
181: return;
182: if ((rr_realm != null) && name.equals(loaded_realm))
183: return;
184: // Load the realm and create the ipmtacher object
185: rr_realm = catalog.loadRealm(name);
186: if (rr_realm != null) {
187: try {
188: AuthRealm realm = (AuthRealm) rr_realm.lock();
189: Enumeration e = realm.enumerateUserNames();
190: if (e.hasMoreElements()) {
191: ipmatcher = new IPMatcher();
192: }
193: while (e.hasMoreElements()) {
194: String uname = (String) e.nextElement();
195: ResourceReference rr_user = realm.loadUser(uname);
196: try {
197: AuthUser user = (AuthUser) rr_user.lock();
198: short ips[][] = user.getIPTemplates();
199: if (ips != null) {
200: for (int i = 0; i < ips.length; i++)
201: ipmatcher.add(ips[i], rr_user);
202: }
203: } catch (InvalidResourceException ex) {
204: System.out.println("Invalid user reference : "
205: + uname);
206: } finally {
207: rr_user.unlock();
208: }
209: }
210: } catch (InvalidResourceException ex) {
211:
212: } finally {
213: rr_realm.unlock();
214: }
215: }
216: }
217:
218: /**
219: * Check that our realm does exist.
220: * Otherwise we are probably being initialized, and we don't authenticate
221: * yet.
222: * @return A boolean <strong>true</strong> if realm can be initialized.
223: */
224:
225: protected synchronized boolean checkRealm() {
226: acquireRealm();
227: return (ipmatcher != null);
228: }
229:
230: /**
231: * Get the list of allowed users.
232: */
233:
234: public String[] getAllowedUsers() {
235: return (String[]) getValue(ATTR_ALLOWED_USERS, null);
236: }
237:
238: /**
239: * Get the list of allowed groups.
240: */
241:
242: public String[] getAllowedGroups() {
243: return (String[]) getValue(ATTR_ALLOWED_GROUPS, null);
244: }
245:
246: /**
247: * Lookup a user by its IP address.
248: * @param ipaddr The IP address to look for.
249: * @return An AuthUser instance or <strong>null</strong>.
250: */
251:
252: public synchronized ResourceReference lookupUser(InetAddress ipaddr) {
253: if (ipmatcher == null)
254: acquireRealm();
255: return (ResourceReference) ipmatcher
256: .lookup(ipaddr.getAddress());
257: }
258:
259: /**
260: * Lookup a user by its name.
261: * @param name The user's name.
262: * @return An AuthUser instance, or <strong>null</strong>.
263: */
264:
265: public synchronized ResourceReference lookupUser(String name) {
266: if (rr_realm == null)
267: acquireRealm();
268: try {
269: AuthRealm realm = (AuthRealm) rr_realm.lock();
270: return realm.loadUser(name);
271: } catch (InvalidResourceException ex) {
272: return null;
273: } finally {
274: rr_realm.unlock();
275: }
276: }
277:
278: /**
279: * Check the given Basic context against our database.
280: * @param ctxt The basic auth context to check.
281: * @return A AuthUser instance if check succeeded, <strong>null</strong>
282: * otherwise.
283: */
284:
285: protected ResourceReference checkBasicAuth(BasicAuthContext ctxt) {
286: ResourceReference rr_user = (ResourceReference) lookupUser(ctxt.user);
287: if (rr_user != null) {
288: try {
289: AuthUser user = (AuthUser) rr_user.lock();
290: // This user doesn't even exists !
291: if (user == null)
292: return null;
293: // If it has a password check it
294: if (!user.definesAttribute("password")) {
295: return rr_user;
296: } else {
297: return user.getPassword().equals(ctxt.password) ? rr_user
298: : null;
299: }
300: } catch (InvalidResourceException ex) {
301: return null;
302: } finally {
303: rr_user.unlock();
304: }
305: }
306: return null;
307: }
308:
309: /**
310: * Is this user allowed in the realm ?
311: * First check in the list of allowed users (if any), than in the list
312: * of allowed groups (if any). If no allowed users or allowed groups
313: * are defined, than simply check for the existence of this user.
314: * @return A boolean <strong>true</strong> if access allowed.
315: */
316:
317: protected boolean checkUser(AuthUser user) {
318: String allowed_users[] = getAllowedUsers();
319: // Check in the list of allowed users:
320: if (allowed_users != null) {
321: for (int i = 0; i < allowed_users.length; i++) {
322: if (allowed_users[i].equals(user.getName()))
323: return true;
324: }
325: }
326: // Check in the list of allowed groups:
327: String allowed_groups[] = getAllowedGroups();
328: if (allowed_groups != null) {
329: String ugroups[] = user.getGroups();
330: if (ugroups != null) {
331: for (int i = 0; i < ugroups.length; i++) {
332: for (int j = 0; j < allowed_groups.length; j++) {
333: if (allowed_groups[j].equals(ugroups[i]))
334: return true;
335: }
336: }
337: }
338: }
339: // If no users or groups specified, return true
340: if ((allowed_users == null) && (allowed_groups == null))
341: return true;
342: return false;
343: }
344:
345: /**
346: * Catch set value on the realm, to maintain cached values.
347: */
348:
349: public void setValue(int idx, Object value) {
350: super .setValue(idx, value);
351: if (idx == ATTR_REALM) {
352: // Initialize the filter challenge:
353: challenge = HttpFactory.makeChallenge("Basic");
354: challenge.setAuthParameter("realm", getRealm());
355: }
356: }
357:
358: /**
359: * Authenticate the given request.
360: * We first check for valid authentication information. If no
361: * authentication is provided, than we try to map the IP address to some
362: * of the ones we know about. If the IP address is not found, we challenge
363: * the client for a password.
364: * <p>If the IP address is found, than either our user entry requires an
365: * extra password step (in wich case we challenge it), or simple IP
366: * based authentication is enough, so we allow the request.
367: * @param request The request to be authentified.
368: * @exception org.w3c.tools.resources.ProtocolException if authentication
369: * failed
370: */
371:
372: public void authenticate(Request request) throws ProtocolException {
373: // Are we being edited ?
374: if (!checkRealm())
375: return;
376: // Internal requests always allowed:
377: Client client = request.getClient();
378: if (client == null)
379: return;
380: // Check for User by IP address:
381: boolean ipchecked = false;
382: ResourceReference rr_user = lookupUser(client.getInetAddress());
383: if (rr_user != null) {
384: try {
385: AuthUser user = (AuthUser) rr_user.lock();
386: if (user != null) {
387: ipchecked = true;
388: // Good the user exists, does it need more authentication ?
389: if (!user.definesAttribute("password")
390: && checkUser(user)) {
391: request
392: .setState(STATE_AUTHUSER, user
393: .getName());
394: request.setState(STATE_AUTHTYPE, "ip");
395: return;
396: }
397: }
398: } catch (InvalidResourceException ex) {
399: //FIXME
400: } finally {
401: rr_user.unlock();
402: }
403: }
404: // Check authentication according to auth method:
405: if ((request.hasAuthorization() && !request.isProxy())
406: || (request.isProxy() && request
407: .hasProxyAuthorization())) {
408: BasicAuthContext ctxt = null;
409: try {
410: ctxt = new BasicAuthContext(request);
411: } catch (BasicAuthContextException ex) {
412: ctxt = null;
413: }
414: // Is that user allowed ?
415: if (ctxt != null) {
416: rr_user = checkBasicAuth(ctxt);
417: if (rr_user != null) {
418: try {
419: AuthUser user = (AuthUser) rr_user.lock();
420: if ((user != null) && checkUser(user)) {
421: // Check that if IP auth was required,
422: // it succeeded:
423: boolean iprequired = user
424: .definesAttribute("ipaddress");
425: if ((!iprequired) || ipchecked) {
426: // Set the request fields, and continue:
427: request.setState(STATE_AUTHUSER,
428: ctxt.user);
429: request.setState(STATE_AUTHTYPE,
430: "Basic");
431: return;
432: }
433: }
434: } catch (InvalidResourceException ex) {
435: //FIXME
436: } finally {
437: rr_user.unlock();
438: }
439: }
440: }
441: }
442:
443: // Every possible scheme has failed for this request, emit an error
444: Reply e = null;
445: if (request.isProxy()) {
446: e = request.makeReply(HTTP.PROXY_AUTH_REQUIRED);
447: e.setProxyAuthenticate(challenge);
448: } else {
449: e = request.makeReply(HTTP.UNAUTHORIZED);
450: e.setWWWAuthenticate(challenge);
451: }
452: HtmlGenerator g = new HtmlGenerator("Unauthorized");
453: g.append("<h1>Unauthorized access</h1>"
454: + "<p>You are denied access to this resource.");
455: e.setStream(g);
456: throw new HTTPException(e);
457: }
458:
459: /**
460: * Initialize the filter.
461: */
462:
463: public void initialize(Object values[]) {
464: super .initialize(values);
465: if (getRealm() != null) {
466: // Initialize the filter challenge:
467: challenge = HttpFactory.makeChallenge("Basic");
468: challenge.setAuthParameter("realm", getRealm());
469: }
470: }
471:
472: }
|