Skip to content

Commit

Permalink
Add support for PATs in GitHub Enterprise server
Browse files Browse the repository at this point in the history
  • Loading branch information
vinokurig committed Nov 7, 2023
1 parent 72f191b commit 47efcd1
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public abstract class AbstractGithubPersonalAccessTokenFetcher
private final OAuthAPI oAuthAPI;

/** GitHub API client. */
private final GithubApiClient githubApiClient;
private GithubApiClient githubApiClient;

/** Name of this OAuth provider as found in OAuthAPI. */
private final String providerName;
Expand Down Expand Up @@ -205,9 +205,13 @@ public Optional<Boolean> isValid(PersonalAccessToken personalAccessToken) {

@Override
public Optional<Pair<Boolean, String>> isValid(PersonalAccessTokenParams params) {
if (!githubApiClient.isConnected(params.getScmProviderUrl())) {
LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl());
return Optional.empty();
if (githubApiClient == null || !githubApiClient.isConnected(params.getScmProviderUrl())) {
if (providerName.equals(params.getScmTokenName())) {
githubApiClient = new GithubApiClient(params.getScmProviderUrl());
} else {
LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl());
return Optional.empty();
}
}
try {
if (params.getScmTokenName() != null && params.getScmTokenName().startsWith(OAUTH_2_PREFIX)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static java.util.regex.Pattern.compile;
import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException;
import static org.eclipse.che.api.factory.server.github.GithubApiClient.GITHUB_SAAS_ENDPOINT;
Expand Down Expand Up @@ -53,8 +54,13 @@ public abstract class AbstractGithubURLParser {
*/
private final Pattern githubPattern;

private final String githubPatternTemplate =
"^%s/(?<repoUser>[^/]+)/(?<repoName>[^/]++)((/)|(?:/tree/(?<branchName>.++))|(/pull/(?<pullRequestId>\\d++)))?$";

private final Pattern githubSSHPattern;

private final String githubSSHPatternTemplate = "^git@%s:(?<repoUser>.*)/(?<repoName>.*)$";

private final boolean disableSubdomainIsolation;

private final String providerName;
Expand All @@ -77,19 +83,67 @@ public abstract class AbstractGithubURLParser {
String endpoint =
isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/');

this.githubPattern =
compile(
format(
"^%s/(?<repoUser>[^/]+)/(?<repoName>[^/]++)((/)|(?:/tree/(?<branchName>.++))|(/pull/(?<pullRequestId>\\d++)))?$",
endpoint));
this.githubPattern = compile(format(githubPatternTemplate, endpoint));
this.githubSSHPattern =
compile(format("^git@%s:(?<repoUser>.*)/(?<repoName>.*)$", URI.create(endpoint).getHost()));
compile(format(githubSSHPatternTemplate, URI.create(endpoint).getHost()));
}

public boolean isValid(@NotNull String url) {
String trimmedUrl = trimEnd(url, '/');
return githubPattern.matcher(trimmedUrl).matches()
|| githubSSHPattern.matcher(trimmedUrl).matches();
|| githubSSHPattern.matcher(trimmedUrl).matches()
|| isUserTokenPresent(trimmedUrl)
|| isApiRequestRelevant(trimmedUrl);
}

private boolean isUserTokenPresent(String repositoryUrl) {
Optional<String> serverUrlOptional = getServerUrl(repositoryUrl);
if (serverUrlOptional.isPresent()) {
String serverUrl = serverUrlOptional.get();
try {
Optional<PersonalAccessToken> token =
tokenManager.get(EnvironmentContext.getCurrent().getSubject(), serverUrl);
if (token.isPresent()) {
PersonalAccessToken accessToken = token.get();
return accessToken.getScmTokenName().equals(providerName);
}
} catch (ScmConfigurationPersistenceException
| ScmUnauthorizedException
| ScmCommunicationException exception) {
return false;
}
}
return false;
}

private boolean isApiRequestRelevant(String repositoryUrl) {
Optional<String> serverUrlOptional = getServerUrl(repositoryUrl);
if (serverUrlOptional.isPresent()) {
GithubApiClient GithubApiClient = new GithubApiClient(serverUrlOptional.get());
try {
// If the user request catches the unauthorised error, it means that the provided url
// belongs to GitHub.
GithubApiClient.getUser("");
} catch (ScmCommunicationException e) {
return e.getStatusCode() == HTTP_UNAUTHORIZED;
} catch (ScmItemNotFoundException | ScmBadRequestException | IllegalArgumentException e) {
return false;
}
}
return false;
}

private Optional<String> getServerUrl(String repositoryUrl) {
if (repositoryUrl.startsWith("git@")) {
String substring = repositoryUrl.substring(4);
return Optional.of("https://" + substring.substring(0, substring.indexOf(":")));
}
Matcher serverUrlMatcher = compile("[^/|:]/").matcher(repositoryUrl);
if (serverUrlMatcher.find()) {
return Optional.of(
repositoryUrl.substring(0, repositoryUrl.indexOf(serverUrlMatcher.group()) + 1));
}
return Optional.empty();
}

public GithubUrl parseWithoutAuthentication(String url) throws ApiException {
Expand All @@ -101,16 +155,30 @@ public GithubUrl parse(String url) throws ApiException {
}

private GithubUrl parse(String url, boolean authenticationRequired) throws ApiException {
Matcher matcher;
boolean isHTTPSUrl = githubPattern.matcher(url).matches();
Matcher matcher = isHTTPSUrl ? githubPattern.matcher(url) : githubSSHPattern.matcher(url);
if (isHTTPSUrl) {
matcher = githubPattern.matcher(url);
} else if (githubSSHPattern.matcher(url).matches()) {
matcher = githubSSHPattern.matcher(url);
} else {
matcher = getPatternMatcherByUrl(url).orElseThrow(IllegalArgumentException::new);
isHTTPSUrl = url.startsWith("http");
}
if (!matcher.matches()) {
throw new IllegalArgumentException(
format("The given url %s is not a valid github URL. ", url));
}

String serverUrl =
isNullOrEmpty(oauthEndpoint) || trimEnd(oauthEndpoint, '/').equals(GITHUB_SAAS_ENDPOINT)
? null
? url.startsWith(GITHUB_SAAS_ENDPOINT)
? null
: getServerUrl(url)
.orElseThrow(
() ->
new IllegalArgumentException(
format("The given url %s has unsupported endpoint. ", url)))
: trimEnd(oauthEndpoint, '/');
String repoUser = matcher.group("repoUser");
String repoName = matcher.group("repoName");
Expand All @@ -127,7 +195,12 @@ private GithubUrl parse(String url, boolean authenticationRequired) throws ApiEx

if (pullRequestId != null) {
GithubPullRequest pullRequest =
this.getPullRequest(pullRequestId, repoUser, repoName, authenticationRequired);
this.getPullRequest(
isNullOrEmpty(serverUrl) ? GITHUB_SAAS_ENDPOINT : serverUrl,
pullRequestId,
repoUser,
repoName,
authenticationRequired);
if (pullRequest != null) {
String state = pullRequest.getState();
if (!"open".equalsIgnoreCase(state)) {
Expand Down Expand Up @@ -165,12 +238,14 @@ private GithubUrl parse(String url, boolean authenticationRequired) throws ApiEx
}

private GithubPullRequest getPullRequest(
String pullRequestId, String repoUser, String repoName, boolean authenticationRequired)
String githubEndpoint,
String pullRequestId,
String repoUser,
String repoName,
boolean authenticationRequired)
throws ApiException {
try {
// prepare token
String githubEndpoint =
isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/');
Subject subject = EnvironmentContext.getCurrent().getSubject();
PersonalAccessToken personalAccessToken = null;
Optional<PersonalAccessToken> token = tokenManager.get(subject, githubEndpoint);
Expand Down Expand Up @@ -253,4 +328,21 @@ private GithubCommit getLatestCommit(

return null;
}

private Optional<Matcher> getPatternMatcherByUrl(String url) {
URI uri =
URI.create(
url.matches(format(githubSSHPatternTemplate, ".*"))
? "ssh://" + url.replace(":", "/")
: url);
String scheme = uri.getScheme();
String host = uri.getHost();
Matcher matcher = compile(format(githubPatternTemplate, scheme + "://" + host)).matcher(url);
if (matcher.matches()) {
return Optional.of(matcher);
} else {
matcher = compile(format(githubSSHPatternTemplate, host)).matcher(url);
return matcher.matches() ? Optional.of(matcher) : Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -267,4 +268,38 @@ public void checkPullRequestMergedState() throws Exception {
String url = "https://github.com/eclipse/che/pull/11103";
githubUrlParser.parse(url);
}

@Test
public void shouldParseServerUr() throws Exception {
// given
String url = "https://github-server.com/user/repo";

// when
GithubUrl githubUrl = githubUrlParser.parse(url);

// then
assertEquals(githubUrl.getUsername(), "user");
assertEquals(githubUrl.getRepository(), "repo");
assertEquals(githubUrl.getHostName(), "https://github-server.com");
}

@Test
public void shouldParseServerUrWithPullRequestId() throws Exception {
// given
String url = "https://github-server.com/user/repo/pull/11103";
GithubPullRequest pr =
new GithubPullRequest()
.withState("open")
.withHead(
new GithubHead()
.withUser(new GithubUser().withId(0).withName("eclipse").withLogin("eclipse"))
.withRepo(new GithubRepo().withName("che")));
when(githubApiClient.getPullRequest(any(), any(), any(), any())).thenReturn(pr);

// when
githubUrlParser.parse(url);

// then
verify(personalAccessTokenManager).get(any(Subject.class), eq("https://github-server.com"));
}
}

0 comments on commit 47efcd1

Please sign in to comment.