001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.client;
019
020import static org.apache.commons.lang3.StringUtils.isBlank;
021import static org.slf4j.LoggerFactory.getLogger;
022
023import java.io.IOException;
024
025import org.apache.http.HttpException;
026import org.apache.http.HttpHost;
027import org.apache.http.HttpRequest;
028import org.apache.http.HttpRequestInterceptor;
029import org.apache.http.auth.AuthScope;
030import org.apache.http.auth.AuthState;
031import org.apache.http.auth.Credentials;
032import org.apache.http.auth.UsernamePasswordCredentials;
033import org.apache.http.client.CredentialsProvider;
034import org.apache.http.client.protocol.HttpClientContext;
035import org.apache.http.impl.auth.BasicScheme;
036import org.apache.http.impl.client.BasicCredentialsProvider;
037import org.apache.http.impl.client.CloseableHttpClient;
038import org.apache.http.impl.client.HttpClients;
039import org.apache.http.protocol.HttpContext;
040import org.apache.http.protocol.HttpCoreContext;
041import org.slf4j.Logger;
042
043/**
044 * A utility class for building an httpclient for interacting with a Fedora repository
045 *
046 * @author Aaron Coburn
047 * @since March 9, 2015
048 */
049public class FcrepoHttpClientBuilder {
050
051    private String username;
052
053    private String password;
054
055    private String host;
056
057    private static final Logger LOGGER = getLogger(FcrepoHttpClientBuilder.class);
058
059    /**
060     * Create a FcrepoHttpClientBuilder object with which it is possible to create
061     * an HttpClient object
062     *
063     * @param username an optional username for authentication
064     * @param password an optional password for authentication
065     * @param host an optional realm for authentication
066     */
067    public FcrepoHttpClientBuilder(final String username, final String password, final String host) {
068        this.username = username;
069        this.password = password;
070        this.host = host;
071    }
072
073    /**
074     *  Build an HttpClient
075     *
076     *  @return an HttpClient
077     */
078    public CloseableHttpClient build() {
079
080        if (isBlank(username) || isBlank(password)) {
081            return HttpClients.createSystem();
082        } else {
083            LOGGER.debug("Accessing fcrepo with user credentials");
084
085            final CredentialsProvider credsProvider = new BasicCredentialsProvider();
086            AuthScope scope = null;
087
088            if (isBlank(host)) {
089                scope = new AuthScope(AuthScope.ANY);
090            } else {
091                scope = new AuthScope(new HttpHost(host));
092            }
093            credsProvider.setCredentials(
094                    scope,
095                    new UsernamePasswordCredentials(username, password));
096            return HttpClients.custom()
097                    .setDefaultCredentialsProvider(credsProvider)
098                    .useSystemProperties()
099                    .addInterceptorFirst(new PreemptiveAuthInterceptor())
100                    .build();
101        }
102    }
103
104    static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
105
106        public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
107            final AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
108            // If no auth scheme available yet, try to initialize it
109            // preemptively
110            if (authState.getAuthScheme() == null) {
111                final CredentialsProvider credsProvider = (CredentialsProvider)
112                        context.getAttribute(HttpClientContext.CREDS_PROVIDER);
113                final HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
114                final AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
115                final Credentials creds = credsProvider.getCredentials(authScope);
116                if (creds == null) {
117                    LOGGER.debug("Cannot initiate preemtive authentication, Credentials not found!");
118                }
119                authState.update(new BasicScheme(), creds);
120            }
121        }
122    }
123}