001: /*
002: * @(#)AuthorizationModule.java 0.3-2 18/06/1999
003: *
004: * This file is part of the HTTPClient package
005: * Copyright (C) 1996-1999 Ronald Tschalär
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free
019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
020: * MA 02111-1307, USA
021: *
022: * For questions, suggestions, bug-reports, enhancement-requests etc.
023: * I may be contacted at:
024: *
025: * ronald@innovation.ch
026: *
027: */
028:
029: package HTTPClient;
030:
031: import java.io.IOException;
032: import java.net.ProtocolException;
033: import java.util.Hashtable;
034:
035: /**
036: * This module handles authentication requests. Authentication info is
037: * preemptively sent if any suitable candidate info is available. If a
038: * request returns with an appropriate status (401 or 407) then the
039: * necessary info is sought from the AuthenticationInfo class.
040: *
041: * @version 0.3-2 18/06/1999
042: * @author Ronald Tschalär
043: */
044:
045: class AuthorizationModule implements HTTPClientModule, GlobalConstants {
046: /** This holds the current Proxy-Authorization-Info for each
047: HTTPConnection */
048: private static Hashtable proxy_cntxt_list = new Hashtable();
049:
050: /** counters for challenge and auth-info lists */
051: private int auth_lst_idx, prxy_lst_idx, auth_scm_idx, prxy_scm_idx;
052:
053: /** the last auth info sent, if any */
054: private AuthorizationInfo auth_sent;
055: private AuthorizationInfo prxy_sent;
056:
057: /** is the info in auth_sent a preemtive guess or the result of a 4xx */
058: private boolean auth_from_4xx;
059: private boolean prxy_from_4xx;
060:
061: /** guard against bugs on both our side and the server side */
062: private int num_tries;
063:
064: // Constructors
065:
066: /**
067: * Initialize counters for challenge and auth-info lists.
068: */
069: AuthorizationModule() {
070: auth_lst_idx = 0;
071: prxy_lst_idx = 0;
072: auth_scm_idx = 0;
073: prxy_scm_idx = 0;
074:
075: auth_sent = null;
076: prxy_sent = null;
077:
078: auth_from_4xx = false;
079: prxy_from_4xx = false;
080:
081: num_tries = 0;
082: }
083:
084: // Methods
085:
086: /**
087: * Invoked by the HTTPClient.
088: */
089: public int requestHandler(Request req, Response[] resp) {
090: HTTPConnection con = req.getConnection();
091: AuthorizationHandler auth_handler = AuthorizationInfo
092: .getAuthHandler();
093: AuthorizationInfo guess;
094:
095: // Preemptively send proxy authorization info
096:
097: Proxy: if (con.getProxyHost() != null && !prxy_from_4xx) {
098: Hashtable proxy_auth_list = Util.getList(proxy_cntxt_list,
099: req.getConnection().getContext());
100: guess = (AuthorizationInfo) proxy_auth_list.get(con
101: .getProxyHost()
102: + ":" + con.getProxyPort());
103: if (guess == null)
104: break Proxy;
105:
106: if (auth_handler != null) {
107: try {
108: guess = auth_handler.fixupAuthInfo(guess, req,
109: null, null);
110: } catch (AuthSchemeNotImplException atnie) {
111: break Proxy;
112: }
113: if (guess == null)
114: break Proxy;
115: }
116:
117: int idx;
118: NVPair[] hdrs = req.getHeaders();
119: for (idx = 0; idx < hdrs.length; idx++) {
120: if (hdrs[idx].getName().equalsIgnoreCase(
121: "Proxy-Authorization"))
122: break; // already set
123: }
124: if (idx == hdrs.length) // add proxy-auth header
125: {
126: hdrs = Util.resizeArray(hdrs, idx + 1);
127: req.setHeaders(hdrs);
128: }
129:
130: hdrs[idx] = new NVPair("Proxy-Authorization", guess
131: .toString());
132:
133: prxy_sent = guess;
134: prxy_from_4xx = false;
135:
136: if (DebugMods)
137: System.err.println("AuthM: Preemptively sending "
138: + "Proxy-Authorization '" + guess + "'");
139: }
140:
141: // Preemptively send authorization info
142:
143: Auth: if (!auth_from_4xx) {
144: guess = AuthorizationInfo.findBest(req);
145: if (guess == null)
146: break Auth;
147:
148: if (auth_handler != null) {
149: try {
150: guess = auth_handler.fixupAuthInfo(guess, req,
151: null, null);
152: } catch (AuthSchemeNotImplException atnie) {
153: break Auth;
154: }
155: if (guess == null)
156: break Auth;
157: }
158:
159: int idx;
160: NVPair[] hdrs = req.getHeaders();
161: for (idx = 0; idx < hdrs.length; idx++) {
162: if (hdrs[idx].getName().equalsIgnoreCase(
163: "Authorization"))
164: break; // already set
165: }
166: if (idx == hdrs.length) // add auth header
167: {
168: hdrs = Util.resizeArray(hdrs, idx + 1);
169: req.setHeaders(hdrs);
170: }
171:
172: hdrs[idx] = new NVPair("Authorization", guess.toString());
173:
174: auth_sent = guess;
175: auth_from_4xx = false;
176:
177: if (DebugMods)
178: System.err
179: .println("AuthM: Preemptively sending Authorization '"
180: + guess + "'");
181: }
182:
183: return REQ_CONTINUE;
184: }
185:
186: /**
187: * Invoked by the HTTPClient.
188: */
189: public void responsePhase1Handler(Response resp, RoRequest req)
190: throws IOException {
191: /* If auth info successful update path list. Note: if we
192: * preemptively sent auth info we don't actually know if
193: * it was necessary. Therefore we don't update the path
194: * list in this case; this prevents it from being
195: * contaminated. If the info was necessary, then the next
196: * time we access this resource we will again guess the
197: * same info and send it.
198: */
199: if (resp.getStatusCode() != 401 && resp.getStatusCode() != 407) {
200: if (auth_sent != null && auth_from_4xx) {
201: try {
202: AuthorizationInfo.getAuthorization(auth_sent, req,
203: resp, false).addPath(req.getRequestURI());
204: } catch (AuthSchemeNotImplException asnie) { /* shouldn't happen */
205: }
206: }
207:
208: // reset guard if not an auth challenge
209: num_tries = 0;
210: }
211:
212: auth_from_4xx = false;
213: prxy_from_4xx = false;
214:
215: if (resp.getHeader("WWW-Authenticate") == null) {
216: auth_lst_idx = 0;
217: auth_scm_idx = 0;
218: }
219:
220: if (resp.getHeader("Proxy-Authenticate") == null) {
221: prxy_lst_idx = 0;
222: prxy_scm_idx = 0;
223: }
224: }
225:
226: /**
227: * Invoked by the HTTPClient.
228: */
229: public int responsePhase2Handler(Response resp, Request req)
230: throws IOException, AuthSchemeNotImplException {
231: // Let the AuthHandler handle any Authentication headers.
232:
233: AuthorizationHandler h = AuthorizationInfo.getAuthHandler();
234: if (h != null)
235: h.handleAuthHeaders(resp, req, auth_sent, prxy_sent);
236:
237: // handle 401 and 407 response codes
238:
239: int sts = resp.getStatusCode();
240: switch (sts) {
241: case 401: // Unauthorized
242: case 407: // Proxy Authentication Required
243:
244: // guard against infinite retries due to bugs
245:
246: num_tries++;
247: if (num_tries > 10)
248: throw new ProtocolException(
249: "Bug in authorization handling: server refused the given info 10 times");
250:
251: if (DebugMods)
252: System.err.println("AuthM: Handling status: " + sts
253: + " " + resp.getReasonLine());
254:
255: // handle WWW-Authenticate
256:
257: int[] idx_arr = { auth_lst_idx, // hack to pass by ref
258: auth_scm_idx };
259: auth_sent = setAuthHeaders(resp
260: .getHeader("WWW-Authenticate"), req, resp,
261: "Authorization", idx_arr, auth_sent);
262: if (auth_sent != null)
263: auth_from_4xx = true;
264: auth_lst_idx = idx_arr[0];
265: auth_scm_idx = idx_arr[1];
266:
267: // handle Proxy-Authenticate
268:
269: idx_arr[0] = prxy_lst_idx; // hack to pass by ref
270: idx_arr[1] = prxy_scm_idx;
271: prxy_sent = setAuthHeaders(resp
272: .getHeader("Proxy-Authenticate"), req, resp,
273: "Proxy-Authorization", idx_arr, prxy_sent);
274: if (prxy_sent != null)
275: prxy_from_4xx = true;
276: prxy_lst_idx = idx_arr[0];
277: prxy_scm_idx = idx_arr[1];
278:
279: if (prxy_sent != null) {
280: HTTPConnection con = req.getConnection();
281: Util.getList(proxy_cntxt_list, con.getContext()).put(
282: con.getProxyHost() + ":" + con.getProxyPort(),
283: prxy_sent);
284: }
285:
286: if (req.getStream() == null
287: && (auth_sent != null || prxy_sent != null)) {
288: try {
289: resp.getInputStream().close();
290: } catch (IOException ioe) {
291: }
292:
293: if (DebugMods) {
294: if (auth_sent != null)
295: System.err.println("AuthM: Resending request "
296: + "with Authorization '" + auth_sent
297: + "'");
298: else
299: System.err.println("AuthM: Resending request "
300: + "with Proxy-Authorization '"
301: + prxy_sent + "'");
302: }
303:
304: return RSP_REQUEST;
305: }
306:
307: // check for headers
308:
309: if (auth_sent == null && prxy_sent == null
310: && resp.getHeader("WWW-Authenticate") == null
311: && resp.getHeader("Proxy-Authenticate") == null) {
312: if (resp.getStatusCode() == 401)
313: throw new ProtocolException(
314: "Missing WWW-Authenticate header");
315: else
316: throw new ProtocolException(
317: "Missing Proxy-Authenticate header");
318: }
319:
320: if (DebugMods) {
321: if (req.getStream() == null)
322: System.err.println("AuthM: status " + sts + " not "
323: + "handled - request has an output "
324: + "stream");
325: else
326: System.err.println("AuthM: No Auth Info found - "
327: + "status " + sts + " not handled");
328: }
329:
330: return RSP_CONTINUE;
331:
332: default:
333:
334: return RSP_CONTINUE;
335: }
336: }
337:
338: /**
339: * Invoked by the HTTPClient.
340: */
341: public void responsePhase3Handler(Response resp, RoRequest req) {
342: }
343:
344: /**
345: * Invoked by the HTTPClient.
346: */
347: public void trailerHandler(Response resp, RoRequest req)
348: throws IOException {
349: // Let the AuthHandler handle any Authentication headers.
350:
351: AuthorizationHandler h = AuthorizationInfo.getAuthHandler();
352: if (h != null)
353: h.handleAuthTrailers(resp, req, auth_sent, prxy_sent);
354: }
355:
356: /**
357: * Handles authentication requests and sets the authorization headers.
358: * It tries to retrieve the neccessary parameters from AuthorizationInfo,
359: * and failing that calls the AuthHandler. Handles multiple authentication
360: * headers.
361: *
362: * @param auth_str the authentication header field returned by the server.
363: * @param req the Request used
364: * @param resp the full Response received
365: * @param header the header name to use in the new headers array.
366: * @param idx_arr an array of indicies holding the state of where we
367: * are when handling multiple authorization headers.
368: * @param prev the previous auth info sent, or null if none
369: * @return the new credentials, or null if none found
370: * @exception ProtocolException if <var>auth_str</var> is null.
371: * @exception AuthSchemeNotImplException if thrown by the AuthHandler.
372: */
373: private AuthorizationInfo setAuthHeaders(String auth_str,
374: Request req, RoResponse resp, String header, int[] idx_arr,
375: AuthorizationInfo prev) throws ProtocolException,
376: AuthSchemeNotImplException {
377: if (auth_str == null)
378: return null;
379:
380: // get the list of challenges the server sent
381: AuthorizationInfo[] challenge = AuthorizationInfo
382: .parseAuthString(auth_str, req, resp);
383:
384: if (DebugMods) {
385: System.err.println("AuthM: parsed " + challenge.length
386: + " challenges:");
387: for (int idx = 0; idx < challenge.length; idx++)
388: System.err
389: .println("AuthM: Challenge " + challenge[idx]);
390: }
391:
392: /* some servers expect a 401 to invalidate sent credentials.
393: * However, only do this for Basic scheme (because e.g. digest
394: * "stale" handling will fail otherwise)
395: */
396: if (prev != null && prev.getScheme().equalsIgnoreCase("Basic")) {
397: for (int idx = 0; idx < challenge.length; idx++)
398: if (prev.getRealm().equals(challenge[idx].getRealm())
399: && prev.getScheme().equalsIgnoreCase(
400: challenge[idx].getScheme()))
401: AuthorizationInfo.removeAuthorization(prev, req
402: .getConnection().getContext());
403: }
404:
405: AuthorizationInfo credentials = null;
406: AuthorizationHandler auth_handler = AuthorizationInfo
407: .getAuthHandler();
408:
409: // try next auth challenge in list
410: while (credentials == null && idx_arr[0] != -1) {
411: credentials = AuthorizationInfo.getAuthorization(
412: challenge[idx_arr[0]], req, resp, false);
413: if (auth_handler != null && credentials != null)
414: credentials = auth_handler.fixupAuthInfo(credentials,
415: req, challenge[idx_arr[0]], resp);
416: if (++idx_arr[0] == challenge.length)
417: idx_arr[0] = -1;
418: }
419:
420: // if we don't have any credentials then prompt the user
421: if (credentials == null) {
422: for (int idx = 0; idx < challenge.length; idx++) {
423: try {
424: credentials = AuthorizationInfo.queryAuthHandler(
425: challenge[idx_arr[1]], req, resp);
426: } catch (AuthSchemeNotImplException atnie) {
427: if (idx == challenge.length - 1)
428: throw atnie;
429: continue;
430: }
431: break;
432: }
433: if (++idx_arr[1] == challenge.length)
434: idx_arr[1] = 0;
435: }
436:
437: // if we still don't have any credentials then give up
438: if (credentials == null)
439: return null;
440:
441: // find auth info
442: int auth_idx;
443: NVPair[] hdrs = req.getHeaders();
444: for (auth_idx = 0; auth_idx < hdrs.length; auth_idx++) {
445: if (hdrs[auth_idx].getName().equalsIgnoreCase(header))
446: break;
447: }
448:
449: // add credentials to headers
450: if (auth_idx == hdrs.length) {
451: hdrs = Util.resizeArray(hdrs, auth_idx + 1);
452: req.setHeaders(hdrs);
453: }
454: hdrs[auth_idx] = new NVPair(header, credentials.toString());
455:
456: return credentials;
457: }
458: }
|