|
17 | 17 | import com.ning.http.client.AsyncHandler;
|
18 | 18 | import com.ning.http.client.FluentCaseInsensitiveStringsMap;
|
19 | 19 | import com.ning.http.client.MaxRedirectException;
|
| 20 | +import com.ning.http.client.ProxyServer; |
20 | 21 | import com.ning.http.client.Realm;
|
21 | 22 | import com.ning.http.client.Realm.AuthScheme;
|
22 | 23 | import com.ning.http.client.Request;
|
@@ -80,6 +81,7 @@ final class AhcEventFilter extends HttpClientFilter {
|
80 | 81 | super(maxHerdersSizeProperty);
|
81 | 82 | this.provider = provider;
|
82 | 83 | HANDLER_MAP.put(HttpStatus.UNAUTHORIZED_401.getStatusCode(), AuthorizationHandler.INSTANCE);
|
| 84 | + HANDLER_MAP.put(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407.getStatusCode(), ProxyAuthorizationHandler.INSTANCE); |
83 | 85 | HANDLER_MAP.put(HttpStatus.MOVED_PERMANENTLY_301.getStatusCode(), RedirectHandler.INSTANCE);
|
84 | 86 | HANDLER_MAP.put(HttpStatus.FOUND_302.getStatusCode(), RedirectHandler.INSTANCE);
|
85 | 87 | HANDLER_MAP.put(HttpStatus.SEE_OTHER_303.getStatusCode(), RedirectHandler.INSTANCE);
|
@@ -522,16 +524,13 @@ public boolean handleStatus(final HttpResponsePacket responsePacket,
|
522 | 524 |
|
523 | 525 | final Realm newRealm;
|
524 | 526 | if (ntlmAuthenticate != null) {
|
| 527 | + final Connection connection = ctx.getConnection(); |
525 | 528 | // NTLM
|
526 | 529 | // Connection-based auth
|
527 |
| - isContinueAuth = ntlmChallenge(ctx.getConnection(), |
528 |
| - ntlmAuthenticate, req, |
529 |
| - req.getHeaders(), realm); |
530 |
| - |
531 |
| - newRealm = new Realm.RealmBuilder().clone(realm)// |
532 |
| - .setUri(req.getUri())// |
533 |
| - .setMethodName(req.getMethod())// |
534 |
| - .build(); |
| 530 | + newRealm = ntlmChallenge(connection, |
| 531 | + ntlmAuthenticate, |
| 532 | + req, realm, false); |
| 533 | + isContinueAuth = !Utils.isNtlmEstablished(connection); |
535 | 534 | } else {
|
536 | 535 | // Request-based auth
|
537 | 536 | isContinueAuth = false;
|
@@ -585,66 +584,115 @@ public boolean handleStatus(final HttpResponsePacket responsePacket,
|
585 | 584 |
|
586 | 585 | return false;
|
587 | 586 | }
|
| 587 | + } // END AuthorizationHandler |
588 | 588 |
|
589 |
| - private boolean ntlmChallenge(final Connection c, |
590 |
| - final String wwwAuth, final Request request, |
591 |
| - final FluentCaseInsensitiveStringsMap headers, |
592 |
| - final Realm realm) |
593 |
| - throws NTLMEngineException { |
| 589 | + private static final class ProxyAuthorizationHandler implements StatusHandler { |
594 | 590 |
|
595 |
| - if (wwwAuth.equals("NTLM")) { |
596 |
| - // server replied bare NTLM => we didn't preemptively sent Type1Msg |
597 |
| - String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(); |
| 591 | + static final ProxyAuthorizationHandler INSTANCE = new ProxyAuthorizationHandler(); |
| 592 | + // -------------------------------------- Methods from StatusHandler |
598 | 593 |
|
599 |
| - addNTLMAuthorizationHeader(headers, challengeHeader, false); |
600 |
| - return true; |
601 |
| - } else { |
602 |
| - // probably receiving Type2Msg, so we issue Type3Msg |
603 |
| - addType3NTLMAuthorizationHeader(wwwAuth, headers, realm, false); |
604 |
| - // we mark NTLM as established for the Connection to |
605 |
| - // avoid preemptive NTLM |
606 |
| - Utils.setNtlmEstablished(c); |
607 |
| - |
608 |
| - return false; |
609 |
| - } |
| 594 | + @Override |
| 595 | + public boolean handlesStatus(int statusCode) { |
| 596 | + return HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407.statusMatches(statusCode); |
610 | 597 | }
|
611 |
| - |
612 |
| - private void addNTLMAuthorizationHeader( |
613 |
| - FluentCaseInsensitiveStringsMap headers, |
614 |
| - String challengeHeader, boolean proxyInd) { |
615 |
| - headers.add(authorizationHeaderName(proxyInd), "NTLM " + challengeHeader); |
616 |
| - } |
617 |
| - |
618 |
| - private void addType3NTLMAuthorizationHeader(String auth, |
619 |
| - FluentCaseInsensitiveStringsMap headers, Realm realm, |
620 |
| - boolean proxyInd) throws NTLMEngineException { |
621 |
| - headers.remove(authorizationHeaderName(proxyInd)); |
622 |
| - |
623 |
| - if (isNonEmpty(auth) && auth.startsWith("NTLM ")) { |
624 |
| - String serverChallenge = auth.substring("NTLM ".length()).trim(); |
625 |
| - String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg( |
626 |
| - realm.getPrincipal(), realm.getPassword(), |
627 |
| - realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge); |
628 |
| - addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); |
| 598 | + |
| 599 | + @SuppressWarnings(value = {"unchecked"}) |
| 600 | + @Override |
| 601 | + public boolean handleStatus(final HttpResponsePacket responsePacket, |
| 602 | + final HttpTransactionContext httpTransactionContext, |
| 603 | + final FilterChainContext ctx) { |
| 604 | + final List<String> proxyAuthHeaders = |
| 605 | + listOf(responsePacket.getHeaders() |
| 606 | + .values(Header.ProxyAuthenticate)); |
| 607 | + |
| 608 | + if (proxyAuthHeaders.isEmpty()) { |
| 609 | + throw new IllegalStateException("407 response received, but no " |
| 610 | + + "Proxy-Authenticate header was present"); |
629 | 611 | }
|
630 |
| - } |
631 |
| - |
632 |
| - private String authorizationHeaderName(boolean proxyInd) { |
633 |
| - return proxyInd |
634 |
| - ? Header.ProxyAuthorization.toString() |
635 |
| - : Header.Authorization.toString(); |
636 |
| - } |
637 |
| - |
638 |
| - private static List<String> listOf(final Iterable<String> values) { |
639 |
| - final List<String> list = new ArrayList<String>(2); |
640 |
| - for (String value : values) { |
641 |
| - list.add(value); |
| 612 | + |
| 613 | + final GrizzlyAsyncHttpProvider provider = |
| 614 | + httpTransactionContext.provider; |
| 615 | + |
| 616 | + final ProxyServer proxyServer = httpTransactionContext.getProxyServer(); |
| 617 | + |
| 618 | + if (proxyServer == null) { |
| 619 | + httpTransactionContext.invocationStatus = InvocationStatus.STOP; |
| 620 | + final AsyncHandler ah = httpTransactionContext.getAsyncHandler(); |
| 621 | + |
| 622 | + if (ah != null) { |
| 623 | + try { |
| 624 | + ah.onStatusReceived( |
| 625 | + httpTransactionContext.responseStatus); |
| 626 | + } catch (Exception e) { |
| 627 | + httpTransactionContext.abort(e); |
| 628 | + } |
| 629 | + } |
| 630 | + return true; |
642 | 631 | }
|
643 | 632 |
|
644 |
| - return list; |
645 |
| - } |
646 |
| - } // END AuthorizationHandler |
| 633 | + final Request req = httpTransactionContext.getAhcRequest(); |
| 634 | + |
| 635 | + try { |
| 636 | + String ntlmAuthenticate = getNTLM(proxyAuthHeaders); |
| 637 | + |
| 638 | + final Realm newRealm; |
| 639 | + if (ntlmAuthenticate != null) { |
| 640 | + // NTLM |
| 641 | + // Connection-based auth |
| 642 | + newRealm = ntlmProxyChallenge(ctx.getConnection(), |
| 643 | + ntlmAuthenticate, |
| 644 | + req, proxyServer); |
| 645 | + } else { |
| 646 | + final String firstAuthHeader = proxyAuthHeaders.get(0); |
| 647 | + |
| 648 | + // BASIC or DIGEST |
| 649 | + newRealm = proxyServer.realmBuilder() |
| 650 | + .setUri(req.getUri())// |
| 651 | + .setOmitQuery(true)// |
| 652 | + .setMethodName(req.getMethod())// |
| 653 | + .setUsePreemptiveAuth(true)// |
| 654 | + .parseProxyAuthenticateHeader(firstAuthHeader)// |
| 655 | + .build(); |
| 656 | + } |
647 | 657 |
|
| 658 | + responsePacket.setSkipRemainder(true); // ignore the remainder of the response |
| 659 | + |
| 660 | + final Connection c; |
| 661 | + |
| 662 | + // @TODO we may want to ditch the keep-alive connection if the response payload is too large |
| 663 | + if (responsePacket.getProcessingState().isKeepAlive()) { |
| 664 | + // if it's HTTP keep-alive connection - reuse the |
| 665 | + // same Grizzly Connection |
| 666 | + c = ctx.getConnection(); |
| 667 | + httpTransactionContext.reuseConnection(); |
| 668 | + } else { |
| 669 | + // if it's not keep-alive - take new Connection from the pool |
| 670 | + final ConnectionManager m = provider.getConnectionManager(); |
| 671 | + c = m.openSync(req); |
| 672 | + } |
| 673 | + |
| 674 | + final Request nextRequest = new RequestBuilder(req) |
| 675 | + .setRealm(newRealm) |
| 676 | + .build(); |
| 677 | + |
| 678 | + final HttpTransactionContext newContext |
| 679 | + = httpTransactionContext.cloneAndStartTransactionFor( |
| 680 | + c, nextRequest); |
| 681 | + newContext.invocationStatus = InvocationStatus.STOP; |
| 682 | + |
| 683 | + try { |
| 684 | + provider.execute(newContext); |
| 685 | + } catch (IOException ioe) { |
| 686 | + newContext.abort(ioe); |
| 687 | + } |
| 688 | + } catch (Exception e) { |
| 689 | + httpTransactionContext.abort(e); |
| 690 | + } |
| 691 | + |
| 692 | + return false; |
| 693 | + } |
| 694 | + } // END ProxyAuthorizationHandler |
| 695 | + |
648 | 696 | private static final class RedirectHandler implements StatusHandler {
|
649 | 697 |
|
650 | 698 | static final RedirectHandler INSTANCE = new RedirectHandler();
|
@@ -778,5 +826,88 @@ private static Realm getRealm(final HttpTransactionContext httpTransactionContex
|
778 | 826 | ? realm
|
779 | 827 | : httpTransactionContext.provider.getClientConfig().getRealm();
|
780 | 828 | }
|
| 829 | + |
| 830 | + private static Realm ntlmChallenge(final Connection c, |
| 831 | + final String wwwAuth, |
| 832 | + final Request request, |
| 833 | + final Realm realm, |
| 834 | + final boolean proxyInd) |
| 835 | + throws NTLMEngineException { |
| 836 | + |
| 837 | + final FluentCaseInsensitiveStringsMap headers = request.getHeaders(); |
| 838 | + if (wwwAuth.equals("NTLM")) { |
| 839 | + // server replied bare NTLM => we didn't preemptively sent Type1Msg |
| 840 | + String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(); |
| 841 | + |
| 842 | + addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); |
| 843 | + } else { |
| 844 | + // probably receiving Type2Msg, so we issue Type3Msg |
| 845 | + addType3NTLMAuthorizationHeader(wwwAuth, headers, realm, proxyInd); |
| 846 | + // we mark NTLM as established for the Connection to |
| 847 | + // avoid preemptive NTLM |
| 848 | + Utils.setNtlmEstablished(c); |
| 849 | + } |
| 850 | + |
| 851 | + return new Realm.RealmBuilder().clone(realm)// |
| 852 | + .setUri(request.getUri())// |
| 853 | + .setMethodName(request.getMethod())// |
| 854 | + .build(); |
| 855 | + } |
| 856 | + |
| 857 | + private static Realm ntlmProxyChallenge(final Connection c, |
| 858 | + final String wwwAuth, final Request request, |
| 859 | + final ProxyServer proxyServer) |
| 860 | + throws NTLMEngineException { |
| 861 | + |
| 862 | + final FluentCaseInsensitiveStringsMap headers = request.getHeaders(); |
| 863 | + headers.remove(Header.ProxyAuthorization.toString()); |
| 864 | + |
| 865 | + Realm realm = proxyServer.realmBuilder()// |
| 866 | + .setScheme(AuthScheme.NTLM)// |
| 867 | + .setUri(request.getUri())// |
| 868 | + .setMethodName(request.getMethod()).build(); |
| 869 | + |
| 870 | + addType3NTLMAuthorizationHeader(wwwAuth, headers, realm, true); |
| 871 | + // we mark NTLM as established for the Connection to |
| 872 | + // avoid preemptive NTLM |
| 873 | + Utils.setNtlmEstablished(c); |
| 874 | + |
| 875 | + return realm; |
| 876 | + } |
| 877 | + |
| 878 | + private static void addNTLMAuthorizationHeader( |
| 879 | + FluentCaseInsensitiveStringsMap headers, |
| 880 | + String challengeHeader, boolean proxyInd) { |
| 881 | + headers.add(authorizationHeaderName(proxyInd), "NTLM " + challengeHeader); |
| 882 | + } |
| 883 | + |
| 884 | + private static void addType3NTLMAuthorizationHeader(String auth, |
| 885 | + FluentCaseInsensitiveStringsMap headers, Realm realm, |
| 886 | + boolean proxyInd) throws NTLMEngineException { |
| 887 | + headers.remove(authorizationHeaderName(proxyInd)); |
| 888 | + |
| 889 | + if (isNonEmpty(auth) && auth.startsWith("NTLM ")) { |
| 890 | + String serverChallenge = auth.substring("NTLM ".length()).trim(); |
| 891 | + String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg( |
| 892 | + realm.getPrincipal(), realm.getPassword(), |
| 893 | + realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge); |
| 894 | + addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); |
| 895 | + } |
| 896 | + } |
| 897 | + |
| 898 | + private static String authorizationHeaderName(final boolean proxyInd) { |
| 899 | + return proxyInd |
| 900 | + ? Header.ProxyAuthorization.toString() |
| 901 | + : Header.Authorization.toString(); |
| 902 | + } |
| 903 | + |
| 904 | + private static List<String> listOf(final Iterable<String> values) { |
| 905 | + final List<String> list = new ArrayList<String>(2); |
| 906 | + for (String value : values) { |
| 907 | + list.add(value); |
| 908 | + } |
| 909 | + |
| 910 | + return list; |
| 911 | + } |
781 | 912 |
|
782 | 913 | } // END AsyncHttpClientEventFilter
|
0 commit comments