Skip to content

Commit

Permalink
chore: Support configuring the GitHub OAuth endpoint (#350)
Browse files Browse the repository at this point in the history
Currently the GitHub OAuth provider is hardcoded to https://github.com endpoint. In order to support Github Enterprise Server, the endpoint of the GitHub OAuth provider is configurable by the oauth secret.
  • Loading branch information
vinokurig committed Sep 9, 2022
1 parent 778f79f commit b35291d
Show file tree
Hide file tree
Showing 17 changed files with 233 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ che.oauth.github.authuri= https://github.com/login/oauth/authorize
# GitHub OAuth token URI.
che.oauth.github.tokenuri= https://github.com/login/oauth/access_token

# GitHub server address.
# Prerequisite: OAuth 2 integration is configured on the GitHub server.
che.integration.github.oauth_endpoint=NULL

# GitHub OAuth redirect URIs.
# Separate multiple values with comma, for example: URI,URI,URI
che.oauth.github.redirecturis= http://localhost:${CHE_PORT}/api/oauth/callback
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* Copyright (c) 2012-2022 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
Expand Down Expand Up @@ -33,7 +33,7 @@ public OpenShiftGitHubOAuthAuthenticator(
@Nullable @Named("che.oauth.github.tokenuri") String tokenUri)
throws IOException {

super("NULL", "NULL", redirectUris, authUri, tokenUri);
super("NULL", "NULL", redirectUris, null, authUri, tokenUri);

if (!isNullOrEmpty(authUri)
&& !isNullOrEmpty(tokenUri)
Expand Down
4 changes: 4 additions & 0 deletions wsmaster/che-core-api-auth-github/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* Copyright (c) 2012-2022 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
Expand All @@ -12,6 +12,7 @@
package org.eclipse.che.security.oauth;

import static com.google.common.base.Strings.isNullOrEmpty;
import static org.eclipse.che.commons.lang.StringUtils.trimEnd;

import com.google.api.client.util.store.MemoryDataStoreFactory;
import jakarta.mail.internet.AddressException;
Expand All @@ -24,17 +25,27 @@
/** OAuth authentication for github account. */
@Singleton
public class GitHubOAuthAuthenticator extends OAuthAuthenticator {
private final String userRequestUrl;

public GitHubOAuthAuthenticator(
String clientId, String clientSecret, String[] redirectUris, String authUri, String tokenUri)
String clientId,
String clientSecret,
String[] redirectUris,
String authEndpoint,
String authUri,
String tokenUri)
throws IOException {
userRequestUrl =
isNullOrEmpty(authEndpoint) || trimEnd(authEndpoint, '/').equals("https://github.com")
? "https://api.github.com/user"
: trimEnd(authEndpoint, '/') + "/api/v3/user";
configure(
clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory());
}

@Override
public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException {
GitHubUser user =
getJson("https://api.github.com/user", accessToken.getToken(), GitHubUser.class);
GitHubUser user = getJson(userRequestUrl, accessToken.getToken(), GitHubUser.class);
final String email = user.getEmail();

if (isNullOrEmpty(email)) {
Expand Down Expand Up @@ -65,7 +76,7 @@ public OAuthToken getToken(String userId) throws IOException {
if (token == null
|| token.getToken() == null
|| token.getToken().isEmpty()
|| getJson("https://api.github.com/user", token.getToken(), GitHubUser.class) == null) {
|| getJson(userRequestUrl, token.getToken(), GitHubUser.class) == null) {
return null;
}
} catch (OAuthAuthenticationException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package org.eclipse.che.security.oauth;

import static com.google.common.base.Strings.isNullOrEmpty;
import static org.eclipse.che.commons.lang.StringUtils.trimEnd;

import java.io.IOException;
import java.nio.file.Files;
Expand Down Expand Up @@ -42,12 +43,18 @@ public GitHubOAuthAuthenticatorProvider(
@Nullable @Named("che.oauth2.github.clientid_filepath") String gitHubClientIdPath,
@Nullable @Named("che.oauth2.github.clientsecret_filepath") String gitHubClientSecretPath,
@Nullable @Named("che.oauth.github.redirecturis") String[] redirectUris,
@Nullable @Named("che.integration.github.oauth_endpoint") String oauthEndpoint,
@Nullable @Named("che.oauth.github.authuri") String authUri,
@Nullable @Named("che.oauth.github.tokenuri") String tokenUri)
throws IOException {
authenticator =
getOAuthAuthenticator(
gitHubClientIdPath, gitHubClientSecretPath, redirectUris, authUri, tokenUri);
gitHubClientIdPath,
gitHubClientSecretPath,
redirectUris,
oauthEndpoint,
authUri,
tokenUri);
LOG.debug("{} GitHub OAuth Authenticator is used.", authenticator);
}

Expand All @@ -60,10 +67,20 @@ private OAuthAuthenticator getOAuthAuthenticator(
String clientIdPath,
String clientSecretPath,
String[] redirectUris,
String oauthEndpoint,
String authUri,
String tokenUri)
throws IOException {

String trimmedOauthEndpoint = isNullOrEmpty(oauthEndpoint) ? null : trimEnd(oauthEndpoint, '/');
authUri =
isNullOrEmpty(trimmedOauthEndpoint)
? authUri
: trimmedOauthEndpoint + "/login/oauth/authorize";
tokenUri =
isNullOrEmpty(trimmedOauthEndpoint)
? tokenUri
: trimmedOauthEndpoint + "/login/oauth/access_token";
if (!isNullOrEmpty(clientIdPath)
&& !isNullOrEmpty(clientSecretPath)
&& !isNullOrEmpty(authUri)
Expand All @@ -74,7 +91,7 @@ private OAuthAuthenticator getOAuthAuthenticator(
final String clientSecret = Files.readString(Path.of(clientSecretPath)).trim();
if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) {
return new GitHubOAuthAuthenticator(
clientId, clientSecret, redirectUris, authUri, tokenUri);
clientId, clientSecret, redirectUris, trimmedOauthEndpoint, authUri, tokenUri);
}
}
return new NoopOAuthAuthenticator();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* Copyright (c) 2012-2022 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
Expand Down Expand Up @@ -41,13 +41,19 @@ public void shouldProvideNoopAuthenticatorWhenInvalidConfigurationSet(
String gitHubClientIdPath,
String gitHubClientSecretPath,
String[] redirectUris,
String oauthEndpoint,
String authUri,
String tokenUri)
throws IOException {
// given
GitHubOAuthAuthenticatorProvider provider =
new GitHubOAuthAuthenticatorProvider(
gitHubClientIdPath, gitHubClientSecretPath, redirectUris, authUri, tokenUri);
gitHubClientIdPath,
gitHubClientSecretPath,
redirectUris,
oauthEndpoint,
authUri,
tokenUri);
// when
OAuthAuthenticator authenticator = provider.get();
// then
Expand All @@ -62,7 +68,12 @@ public void shouldProvideNoopAuthenticatorWhenConfigFilesAreEmpty() throws IOExc
// given
GitHubOAuthAuthenticatorProvider provider =
new GitHubOAuthAuthenticatorProvider(
emptyFile.getPath(), emptyFile.getPath(), new String[] {TEST_URI}, TEST_URI, TEST_URI);
emptyFile.getPath(),
emptyFile.getPath(),
new String[] {TEST_URI},
null,
TEST_URI,
TEST_URI);
// when
OAuthAuthenticator authenticator = provider.get();
// then
Expand All @@ -80,6 +91,27 @@ public void shouldProvideValidGitHubOAuthAuthenticator() throws IOException {
credentialFile.getPath(),
credentialFile.getPath(),
new String[] {TEST_URI},
null,
TEST_URI,
TEST_URI);
// when
OAuthAuthenticator authenticator = provider.get();

// then
assertNotNull(authenticator);
assertTrue(GitHubOAuthAuthenticator.class.isAssignableFrom(authenticator.getClass()));
}

@Test
public void shouldProvideValidGitHubOAuthAuthenticatorWithConfiguredOAuthEndpoint()
throws IOException {
// given
GitHubOAuthAuthenticatorProvider provider =
new GitHubOAuthAuthenticatorProvider(
credentialFile.getPath(),
credentialFile.getPath(),
new String[] {TEST_URI},
"https://custom.github.com/",
TEST_URI,
TEST_URI);
// when
Expand All @@ -93,22 +125,40 @@ public void shouldProvideValidGitHubOAuthAuthenticator() throws IOException {
@DataProvider(name = "noopConfig")
public Object[][] noopConfig() {
return new Object[][] {
{null, null, null, null, null},
{credentialFile.getPath(), emptyFile.getPath(), null, TEST_URI, null},
{emptyFile.getPath(), emptyFile.getPath(), null, null, TEST_URI},
{null, emptyFile.getPath(), null, TEST_URI, TEST_URI},
{null, credentialFile.getPath(), new String[] {}, null, null},
{emptyFile.getPath(), null, new String[] {}, "", ""},
{credentialFile.getPath(), null, new String[] {}, "", null},
{null, emptyFile.getPath(), new String[] {}, null, ""},
{credentialFile.getPath(), null, new String[] {}, TEST_URI, null},
{null, emptyFile.getPath(), new String[] {}, TEST_URI, TEST_URI},
{emptyFile.getPath(), null, new String[] {TEST_URI}, null, null},
{credentialFile.getPath(), null, new String[] {TEST_URI}, "", ""},
{credentialFile.getPath(), credentialFile.getPath(), new String[] {TEST_URI}, null, TEST_URI},
{credentialFile.getPath(), emptyFile.getPath(), new String[] {TEST_URI}, TEST_URI, null},
{credentialFile.getPath(), credentialFile.getPath(), new String[] {TEST_URI}, TEST_URI, ""},
{emptyFile.getPath(), emptyFile.getPath(), new String[] {TEST_URI}, "", TEST_URI}
{null, null, null, null, null, null},
{null, null, null, "", null, null},
{null, null, null, TEST_URI, null, null},
{credentialFile.getPath(), emptyFile.getPath(), null, null, TEST_URI, null},
{emptyFile.getPath(), emptyFile.getPath(), null, null, null, TEST_URI},
{null, emptyFile.getPath(), null, null, TEST_URI, TEST_URI},
{null, credentialFile.getPath(), new String[] {}, null, null, null},
{emptyFile.getPath(), null, new String[] {}, null, "", ""},
{credentialFile.getPath(), null, new String[] {}, null, "", null},
{null, emptyFile.getPath(), new String[] {}, null, null, ""},
{credentialFile.getPath(), null, new String[] {}, null, TEST_URI, null},
{null, emptyFile.getPath(), new String[] {}, null, TEST_URI, TEST_URI},
{emptyFile.getPath(), null, new String[] {TEST_URI}, null, null, null},
{credentialFile.getPath(), null, new String[] {TEST_URI}, null, "", ""},
{
credentialFile.getPath(),
credentialFile.getPath(),
new String[] {TEST_URI},
null,
null,
TEST_URI
},
{
credentialFile.getPath(), emptyFile.getPath(), new String[] {TEST_URI}, null, TEST_URI, null
},
{
credentialFile.getPath(),
credentialFile.getPath(),
new String[] {TEST_URI},
null,
TEST_URI,
""
},
{emptyFile.getPath(), emptyFile.getPath(), new String[] {TEST_URI}, null, "", TEST_URI}
};
}
}
4 changes: 4 additions & 0 deletions wsmaster/che-core-api-factory-github/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
*/
package org.eclipse.che.api.factory.server.github;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.time.Duration.ofSeconds;
import static org.eclipse.che.commons.lang.StringUtils.trimEnd;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
Expand All @@ -37,6 +39,7 @@
import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException;
import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -46,11 +49,8 @@ public class GithubApiClient {

private static final Logger LOG = LoggerFactory.getLogger(GithubApiClient.class);

/** GitHub API endpoint URL. */
public static final String GITHUB_API_SERVER = "https://api.github.com";

/** GitHub endpoint URL. */
public static final String GITHUB_SERVER = "https://github.com";
public static final String GITHUB_SAAS_ENDPOINT = "https://github.com";

/** GitHub HTTP header containing OAuth scopes. */
public static final String GITHUB_OAUTH_SCOPES_HEADER = "X-OAuth-Scopes";
Expand All @@ -62,19 +62,16 @@ public class GithubApiClient {
private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

/** Default constructor, binds http client to https://api.github.com */
public GithubApiClient() {
this(GITHUB_API_SERVER);
}

/**
* Used for URL injection in testing.
*
* @param apiServerUrl the GitHub API url
*/
GithubApiClient(final String apiServerUrl) {
this.apiServerUrl = URI.create(apiServerUrl);
this.scmServerUrl = URI.create(GITHUB_SERVER);
/** Default constructor, binds http client to GitHub API url */
public GithubApiClient(@Nullable String serverUrl) {
String trimmedServerUrl = !isNullOrEmpty(serverUrl) ? trimEnd(serverUrl, '/') : null;
this.apiServerUrl =
URI.create(
isNullOrEmpty(trimmedServerUrl) || trimmedServerUrl.equals(GITHUB_SAAS_ENDPOINT)
? "https://api.github.com/"
: trimmedServerUrl + "/api/v3/");
this.scmServerUrl =
URI.create(isNullOrEmpty(trimmedServerUrl) ? GITHUB_SAAS_ENDPOINT : trimmedServerUrl);
this.httpClient =
HttpClient.newBuilder()
.executor(
Expand All @@ -101,7 +98,7 @@ public GithubApiClient() {
*/
public GithubUser getUser(String authenticationToken)
throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException {
final URI uri = apiServerUrl.resolve("/user");
final URI uri = apiServerUrl.resolve("./user");
HttpRequest request = buildGithubApiRequest(uri, authenticationToken);
LOG.trace("executeRequest={}", request);
return executeRequest(
Expand All @@ -120,7 +117,7 @@ public GithubPullRequest getPullRequest(
String id, String username, String repoName, String authenticationToken)
throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException {
final URI uri =
apiServerUrl.resolve(String.format("/repos/%1s/%2s/pulls/%3s", username, repoName, id));
apiServerUrl.resolve(String.format("./repos/%1s/%2s/pulls/%3s", username, repoName, id));
HttpRequest request = buildGithubApiRequest(uri, authenticationToken);
LOG.trace("executeRequest={}", request);
return executeRequest(
Expand Down Expand Up @@ -149,7 +146,7 @@ public GithubPullRequest getPullRequest(
*/
public String[] getTokenScopes(String authenticationToken)
throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException {
final URI uri = apiServerUrl.resolve("/user");
final URI uri = apiServerUrl.resolve("./user");
HttpRequest request = buildGithubApiRequest(uri, authenticationToken);
LOG.trace("executeRequest={}", request);
return executeRequest(
Expand Down Expand Up @@ -213,6 +210,6 @@ private <T> T executeRequest(
* @return If the provided url is recognized by the current client
*/
public boolean isConnected(String scmServerUrl) {
return this.scmServerUrl.equals(URI.create(scmServerUrl));
return this.scmServerUrl.equals(URI.create(trimEnd(scmServerUrl, '/')));
}
}
Loading

0 comments on commit b35291d

Please sign in to comment.