package org.jenkinsci.plugins.github.internal;
import com.cloudbees.jenkins.GitHubWebHook;
import com.squareup.okhttp.Cache;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.OkUrlFactory;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.github.config.GitHubServerConfig;
import org.jenkinsci.plugins.github.util.misc.NullSafeFunction;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.GitHubBuilder;
import org.kohsuke.github.HttpConnector;
import org.kohsuke.github.RateLimitHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.jenkinsci.plugins.github.config.GitHubServerConfig.GITHUB_URL;
import static org.jenkinsci.plugins.github.config.GitHubServerConfig.tokenFor;
import static org.jenkinsci.plugins.github.internal.GitHubClientCacheOps.toCacheDir;
/**
* Converts server config to authorized GH instance on {@link #applyNullSafe(GitHubServerConfig)}.
* If login process is not successful it returns null
*
* Uses okHttp (https://github.com/square/okhttp) as connector to have ability to use cache and proxy
* The capacity of cache can be changed in advanced section of global configuration for plugin
*
* Don't use this class in any place directly
* as of it have public static factory {@link GitHubServerConfig#loginToGithub()}
*
* @author lanwen (Merkushev Kirill)
* @see GitHubServerConfig#loginToGithub()
*/
@Restricted(NoExternalUse.class)
public class GitHubLoginFunction extends NullSafeFunction<GitHubServerConfig, GitHub> {
private static final Logger LOGGER = LoggerFactory.getLogger(GitHubLoginFunction.class);
/**
* Called by {@link #apply(Object)}
* Logins to GH and returns client instance
*
* @param github config where token saved
*
* @return authorized client or null on login error
*/
@Override
@CheckForNull
protected GitHub applyNullSafe(@Nonnull GitHubServerConfig github) {
String accessToken = tokenFor(github.getCredentialsId());
GitHubBuilder builder = new GitHubBuilder()
.withOAuthToken(accessToken)
.withConnector(connector(github))
.withRateLimitHandler(RateLimitHandler.FAIL);
try {
if (isNotBlank(github.getApiUrl())) {
builder.withEndpoint(github.getApiUrl());
}
LOGGER.debug("Create new GH client with creds id {}", github.getCredentialsId());
return builder.build();
} catch (IOException e) {
LOGGER.warn("Failed to login with creds {}", github.getCredentialsId(), e);
return null;
}
}
/**
* Uses proxy if configured on pluginManager/advanced page
*
* @param apiUrl GitHub's url to build proxy to
*
* @return proxy to use it in connector. Should not be null as it can lead to unexpected behaviour
*/
@Nonnull
private Proxy getProxy(String apiUrl) {
Jenkins jenkins = GitHubWebHook.getJenkinsInstance();
if (jenkins.proxy == null) {
return Proxy.NO_PROXY;
} else {
try {
return jenkins.proxy.createProxy(new URL(apiUrl).getHost());
} catch (MalformedURLException e) {
return jenkins.proxy.createProxy(apiUrl);
}
}
}
/**
* okHttp connector to be used as backend for GitHub client.
* Uses proxy of jenkins
* If cache size > 0, uses cache
*
* @return connector to be used as backend for client
*/
private OkHttpConnector connector(GitHubServerConfig config) {
OkHttpClient client = new OkHttpClient().setProxy(getProxy(defaultIfBlank(config.getApiUrl(), GITHUB_URL)));
if (config.getClientCacheSize() > 0) {
Cache cache = toCacheDir().apply(config);
client.setCache(cache);
}
return new OkHttpConnector(new OkUrlFactory(client));
}
/**
* Copy-paste due to class loading issues
*
* @see org.kohsuke.github.extras.OkHttpConnector
*/
private static class OkHttpConnector implements HttpConnector {
private final OkUrlFactory urlFactory;
private OkHttpConnector(OkUrlFactory urlFactory) {
this.urlFactory = urlFactory;
}
@Override
public HttpURLConnection connect(URL url) throws IOException {
return urlFactory.open(url);
}
}
}