/*******************************************************************************
* Copyright (c) 2016 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.openshift.internal.restclient.okhttp;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import com.openshift.internal.restclient.DefaultClient;
import com.openshift.internal.restclient.authorization.AuthorizationDetails;
import com.openshift.internal.util.URIUtils;
import com.openshift.restclient.IClient;
import com.openshift.restclient.authorization.IAuthorizationContext;
import com.openshift.restclient.authorization.IAuthorizationDetails;
import com.openshift.restclient.authorization.UnauthorizedException;
import com.openshift.restclient.http.IHttpConstants;
import okhttp3.Authenticator;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Request.Builder;
import okhttp3.Response;
import okhttp3.Route;
/**
* OkHttp Authenticator implementations for OpenShift 3
* @author jeff.cantrill
*
*/
public class OpenShiftAuthenticator implements Authenticator, IHttpConstants{
public static final String ACCESS_TOKEN = "access_token";
private static final String AUTH_ATTEMPTS = "X-OPENSHIFT-AUTH-ATTEMPTS";
private static final String CSRF_TOKEN = "X-CSRF-Token";
private static final String ERROR = "error";
private static final String ERROR_DETAILS = "error_details";
private Collection<IChallangeHandler> challangeHandlers = new ArrayList<>();
private OkHttpClient okClient;
private IClient client;
@Override
public Request authenticate(Route route, Response response) throws IOException {
if(unauthorizedForCluster(response)){
String requestUrl = response.request().url().toString();
Request authRequest = new Request.Builder()
.addHeader(CSRF_TOKEN, "1")
.url(route.address().url().toString() + "oauth/authorize?response_type=token&client_id=openshift-challenging-client")
.build();
try (
Response authResponse = tryAuth(authRequest)){
if(authResponse.isSuccessful()) {
String token = extractAndSetAuthContextToken(authResponse);
return response.request().newBuilder()
.header(IHttpConstants.PROPERTY_AUTHORIZATION, String.format("%s %s", IHttpConstants.AUTHORIZATION_BEARER, token))
.build();
}
}
throw new UnauthorizedException(captureAuthDetails(requestUrl), ResponseCodeInterceptor.getStatus(response.body().string()));
}
return null;
}
private boolean unauthorizedForCluster(Response response) {
String requestHost = response.request().url().host();
return response.code() == IHttpConstants.STATUS_UNAUTHORIZED && client.getBaseURL().getHost().equals(requestHost);
}
private Response tryAuth(Request authRequest) throws IOException {
return okClient
.newBuilder()
.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
if(StringUtils.isNotBlank(response.request().header(AUTH_ATTEMPTS))) {
return null;
}
if(StringUtils.isNotBlank(response.header(IHttpConstants.PROPERTY_WWW_AUTHENTICATE))) {
for (IChallangeHandler challangeHandler : challangeHandlers) {
if(!challangeHandler.canHandle(response.headers())) {
Builder requestBuilder = response.request().newBuilder()
.header(AUTH_ATTEMPTS, "1");
return challangeHandler.handleChallange(requestBuilder).build();
}
}
}
return null;
}
})
.followRedirects(false)
.followRedirects(false)
.build()
.newCall(authRequest).execute();
}
private IAuthorizationDetails captureAuthDetails(String url) {
IAuthorizationDetails details = null;
Map<String, String> pairs = URIUtils.splitFragment(url);
if (pairs.containsKey(ERROR)) {
details = new AuthorizationDetails(pairs.get(ERROR), pairs.get(ERROR_DETAILS));
}
return details;
}
private String extractAndSetAuthContextToken(Response response) {
String token = null;
Map<String, String> pairs = URIUtils.splitFragment(response.header(PROPERTY_LOCATION));
if (pairs.containsKey(ACCESS_TOKEN)) {
token = pairs.get(ACCESS_TOKEN);
IAuthorizationContext authContext = client.getAuthorizationContext();
if(authContext != null) {
authContext.setToken(token);
}
}
return token;
}
public void setOkClient(OkHttpClient okClient) {
this.okClient = okClient;
}
public void setClient(DefaultClient client) {
this.client = client;
challangeHandlers.clear();
challangeHandlers.add(new BasicChallangeHandler(client.getAuthorizationContext()));
}
}