All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.asynchttpclient.netty.handler.intercept.Unauthorized401Interceptor Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
/*
 * Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at
 *     http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */
package org.asynchttpclient.netty.handler.intercept;

import static org.asynchttpclient.Dsl.realm;
import static org.asynchttpclient.util.AuthenticatorUtils.*;
import static org.asynchttpclient.util.MiscUtils.withDefault;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;

import java.util.List;

import org.asynchttpclient.Realm;
import org.asynchttpclient.Realm.AuthScheme;
import org.asynchttpclient.Request;
import org.asynchttpclient.RequestBuilder;
import org.asynchttpclient.netty.NettyResponseFuture;
import org.asynchttpclient.netty.channel.ChannelManager;
import org.asynchttpclient.netty.channel.ChannelState;
import org.asynchttpclient.netty.request.NettyRequestSender;
import org.asynchttpclient.ntlm.NtlmEngine;
import org.asynchttpclient.proxy.ProxyServer;
import org.asynchttpclient.spnego.SpnegoEngine;
import org.asynchttpclient.spnego.SpnegoEngineException;
import org.asynchttpclient.uri.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Unauthorized401Interceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(Unauthorized401Interceptor.class);

    private final ChannelManager channelManager;
    private final NettyRequestSender requestSender;

    public Unauthorized401Interceptor(ChannelManager channelManager, NettyRequestSender requestSender) {
        this.channelManager = channelManager;
        this.requestSender = requestSender;
    }

    public boolean exitAfterHandling401(//
            final Channel channel,//
            final NettyResponseFuture future,//
            HttpResponse response,//
            final Request request,//
            int statusCode,//
            Realm realm,//
            ProxyServer proxyServer,//
            HttpRequest httpRequest) {

        if (realm == null) {
            LOGGER.info("Can't handle 401 as there's no realm");
            return false;
        }

        if (future.getInAuth().getAndSet(true)) {
            LOGGER.info("Can't handle 401 as auth was already performed");
            return false;
        }

        List wwwAuthHeaders = response.headers().getAll(HttpHeaders.Names.WWW_AUTHENTICATE);

        if (wwwAuthHeaders.isEmpty()) {
            LOGGER.info("Can't handle 401 as response doesn't contain WWW-Authenticate headers");
            return false;
        }

        // FIXME what's this???
        future.setChannelState(ChannelState.NEW);
        HttpHeaders requestHeaders = new DefaultHttpHeaders(false).add(request.getHeaders());

        switch (realm.getScheme()) {
        case BASIC:
            if (getHeaderWithPrefix(wwwAuthHeaders, "Basic") == null) {
                LOGGER.info("Can't handle 401 with Basic realm as WWW-Authenticate headers don't match");
                return false;
            }

            if (realm.isUsePreemptiveAuth()) {
                // FIXME do we need this, as future.getAndSetAuth
                // was tested above?
                // auth was already performed, most likely auth
                // failed
                LOGGER.info("Can't handle 401 with Basic realm as auth was preemptive and already performed");
                return false;
            }

            // FIXME do we want to update the realm, or directly
            // set the header?
            Realm newBasicRealm = realm(realm)//
                    .setUsePreemptiveAuth(true)//
                    .build();
            future.setRealm(newBasicRealm);
            break;

        case DIGEST:
            String digestHeader = getHeaderWithPrefix(wwwAuthHeaders, "Digest");
            if (digestHeader == null) {
                LOGGER.info("Can't handle 401 with Digest realm as WWW-Authenticate headers don't match");
                return false;
            }
            Realm newDigestRealm = realm(realm)//
                    .setUri(request.getUri())//
                    .setMethodName(request.getMethod())//
                    .setUsePreemptiveAuth(true)//
                    .parseWWWAuthenticateHeader(digestHeader)//
                    .build();
            future.setRealm(newDigestRealm);
            break;

        case NTLM:
            String ntlmHeader = getHeaderWithPrefix(wwwAuthHeaders, "NTLM");
            if (ntlmHeader == null) {
                LOGGER.info("Can't handle 401 with NTLM realm as WWW-Authenticate headers don't match");
                return false;
            }

            ntlmChallenge(ntlmHeader, request, requestHeaders, realm, future);
            Realm newNtlmRealm = realm(realm)//
                    .setUsePreemptiveAuth(true)//
                    .build();
            future.setRealm(newNtlmRealm);
            break;

        case KERBEROS:
        case SPNEGO:
            if (getHeaderWithPrefix(wwwAuthHeaders, NEGOTIATE) == null) {
                LOGGER.info("Can't handle 401 with Kerberos or Spnego realm as WWW-Authenticate headers don't match");
                return false;
            }
            try {
                kerberosChallenge(channel, wwwAuthHeaders, request, requestHeaders, realm, future);

            } catch (SpnegoEngineException e) {
                // FIXME
                String ntlmHeader2 = getHeaderWithPrefix(wwwAuthHeaders, "NTLM");
                if (ntlmHeader2 != null) {
                    LOGGER.warn("Kerberos/Spnego auth failed, proceeding with NTLM");
                    ntlmChallenge(ntlmHeader2, request, requestHeaders, realm, future);
                    Realm newNtlmRealm2 = realm(realm)//
                            .setScheme(AuthScheme.NTLM)//
                            .setUsePreemptiveAuth(true)//
                            .build();
                    future.setRealm(newNtlmRealm2);
                } else {
                    requestSender.abort(channel, future, e);
                    return false;
                }
            }
            break;
        default:
            throw new IllegalStateException("Invalid Authentication scheme " + realm.getScheme());
        }

        final Request nextRequest = new RequestBuilder(future.getCurrentRequest()).setHeaders(requestHeaders).build();

        LOGGER.debug("Sending authentication to {}", request.getUri());
        if (future.isKeepAlive()//
                && !HttpHeaders.isTransferEncodingChunked(httpRequest)//
                && !HttpHeaders.isTransferEncodingChunked(response)) {
            future.setReuseChannel(true);
            requestSender.drainChannelAndExecuteNextRequest(channel, future, nextRequest);
        } else {
            channelManager.closeChannel(channel);
            requestSender.sendNextRequest(nextRequest, future);
        }

        return true;
    }

    private void ntlmChallenge(String authenticateHeader,//
            Request request,//
            HttpHeaders requestHeaders,//
            Realm realm,//
            NettyResponseFuture future) {

        if (authenticateHeader.equals("NTLM")) {
            // server replied bare NTLM => we didn't preemptively sent Type1Msg
            String challengeHeader = NtlmEngine.INSTANCE.generateType1Msg();
            // FIXME we might want to filter current NTLM and add (leave other
            // Authorization headers untouched)
            requestHeaders.set(HttpHeaders.Names.AUTHORIZATION, "NTLM " + challengeHeader);
            future.getInAuth().set(false);

        } else {
            String serverChallenge = authenticateHeader.substring("NTLM ".length()).trim();
            String challengeHeader = NtlmEngine.INSTANCE.generateType3Msg(realm.getPrincipal(), realm.getPassword(), realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge);
            // FIXME we might want to filter current NTLM and add (leave other
            // Authorization headers untouched)
            requestHeaders.set(HttpHeaders.Names.AUTHORIZATION, "NTLM " + challengeHeader);
        }
    }

    private void kerberosChallenge(Channel channel,//
            List authHeaders,//
            Request request,//
            HttpHeaders headers,//
            Realm realm,//
            NettyResponseFuture future) throws SpnegoEngineException {

        Uri uri = request.getUri();
        String host = withDefault(request.getVirtualHost(), uri.getHost());
        String challengeHeader = SpnegoEngine.instance().generateToken(host);
        headers.set(HttpHeaders.Names.AUTHORIZATION, NEGOTIATE + " " + challengeHeader);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy