diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties
index 47f9ada7321..5de83b82ad0 100644
--- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties
+++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties
@@ -194,14 +194,13 @@ che.auth.access_denied_error_page=/error-oauth
# Reserved user names
che.auth.reserved_user_names=
-# Configuration of GitHub OAuth client.
-# You can setup GitHub OAuth to automate authentication to remote repositories.
-# You need to first register this application with GitHub OAuth.
-# GitHub OAuth client ID.
-che.oauth.github.clientid=NULL
-
-# GitHub OAuth client secret.
-che.oauth.github.clientsecret=NULL
+
+# Configuration of GitHub OAuth2 client. Used to obtain Personal access tokens.
+# Location of the file with GitHub client id.
+che.oauth2.github.clientid_filepath=NULL
+
+# Location of the file with GitHub client secret.
+che.oauth2.github.clientsecret_filepath=NULL
# GitHub OAuth authorization URI.
che.oauth.github.authuri= https://github.com/login/oauth/authorize
diff --git a/wsmaster/che-core-api-auth-github/pom.xml b/wsmaster/che-core-api-auth-github/pom.xml
index e0a52f87a77..96423ffbe95 100644
--- a/wsmaster/che-core-api-auth-github/pom.xml
+++ b/wsmaster/che-core-api-auth-github/pom.xml
@@ -60,8 +60,13 @@
che-core-commons-inject
- org.eclipse.che.core
- che-core-commons-json
+ org.slf4j
+ slf4j-api
+
+
+ org.testng
+ testng
+ test
diff --git a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java
index 6a9f319cbba..8f7354846b8 100644
--- a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java
+++ b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java
@@ -17,70 +17,31 @@
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Type;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
-import org.eclipse.che.commons.annotation.Nullable;
-import org.eclipse.che.commons.json.JsonHelper;
-import org.eclipse.che.commons.json.JsonParseException;
import org.eclipse.che.security.oauth.shared.User;
/** OAuth authentication for github account. */
@Singleton
public class GitHubOAuthAuthenticator extends OAuthAuthenticator {
- @Inject
public GitHubOAuthAuthenticator(
- @Nullable @Named("che.oauth.github.clientid") String clientId,
- @Nullable @Named("che.oauth.github.clientsecret") String clientSecret,
- @Nullable @Named("che.oauth.github.redirecturis") String[] redirectUris,
- @Nullable @Named("che.oauth.github.authuri") String authUri,
- @Nullable @Named("che.oauth.github.tokenuri") String tokenUri)
+ String clientId, String clientSecret, String[] redirectUris, String authUri, String tokenUri)
throws IOException {
- if (!isNullOrEmpty(clientId)
- && !isNullOrEmpty(clientSecret)
- && !isNullOrEmpty(authUri)
- && !isNullOrEmpty(tokenUri)
- && redirectUris != null
- && redirectUris.length != 0) {
-
- configure(
- clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory());
- }
+ configure(
+ clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory());
}
@Override
public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException {
GitHubUser user =
- getJson(
- "https://api.github.com/user?access_token=" + accessToken.getToken(), GitHubUser.class);
-
- GithubEmail[] result =
- getJson2(
- "https://api.github.com/user/emails?access_token=" + accessToken.getToken(),
- GithubEmail[].class,
- null);
+ getJson("https://api.github.com/user", accessToken.getToken(), GitHubUser.class);
+ final String email = user.getEmail();
- GithubEmail verifiedEmail = null;
- for (GithubEmail email : result) {
- if (email.isPrimary() && email.isVerified()) {
- verifiedEmail = email;
- break;
- }
- }
- if (verifiedEmail == null
- || verifiedEmail.getEmail() == null
- || verifiedEmail.getEmail().isEmpty()) {
+ if (isNullOrEmpty(email)) {
throw new OAuthAuthenticationException(
"Sorry, we failed to find any verified emails associated with your GitHub account."
+ " Please, verify at least one email in your GitHub account and try to connect with GitHub again.");
}
- user.setEmail(verifiedEmail.getEmail());
- final String email = user.getEmail();
try {
new InternetAddress(email).validate();
} catch (AddressException e) {
@@ -89,32 +50,6 @@ public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException
return user;
}
- protected O getJson2(String getUserUrl, Class userClass, Type type)
- throws OAuthAuthenticationException {
- HttpURLConnection urlConnection = null;
- InputStream urlInputStream = null;
-
- try {
- urlConnection = (HttpURLConnection) new URL(getUserUrl).openConnection();
- urlConnection.setRequestProperty("Accept", "application/vnd.github.v3.html+json");
- urlInputStream = urlConnection.getInputStream();
- return JsonHelper.fromJson(urlInputStream, userClass, type);
- } catch (JsonParseException | IOException e) {
- throw new OAuthAuthenticationException(e.getMessage(), e);
- } finally {
- if (urlInputStream != null) {
- try {
- urlInputStream.close();
- } catch (IOException ignored) {
- }
- }
-
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- }
- }
-
@Override
public final String getOAuthProvider() {
return "github";
@@ -123,62 +58,19 @@ public final String getOAuthProvider() {
@Override
public OAuthToken getToken(String userId) throws IOException {
final OAuthToken token = super.getToken(userId);
- if (!(token == null || token.getToken() == null || token.getToken().isEmpty())) {
- // Need to check if token which stored is valid for requests, then if valid - we returns it to
- // caller
- String tokenVerifyUrl = "https://api.github.com/?access_token=" + token.getToken();
- HttpURLConnection http = null;
- try {
- http = (HttpURLConnection) new URL(tokenVerifyUrl).openConnection();
- http.setInstanceFollowRedirects(false);
- http.setRequestMethod("GET");
- http.setRequestProperty("Accept", "application/json");
-
- if (http.getResponseCode() == 401) {
- return null;
- }
- } finally {
- if (http != null) {
- http.disconnect();
- }
+ // Need to check if token which is stored is valid for requests, then if valid - we returns it
+ // to
+ // caller
+ try {
+ if (token == null
+ || token.getToken() == null
+ || token.getToken().isEmpty()
+ || getJson("https://api.github.com/user", token.getToken(), GitHubUser.class) == null) {
+ return null;
}
-
- return token;
- }
- return null;
- }
-
- /**
- * information for each email address indicating if the address has been verified and if it’s the
- * user’s primary email address for GitHub.
- */
- public static class GithubEmail {
- private boolean primary;
- private boolean verified;
- private String email;
-
- public boolean isPrimary() {
- return primary;
- }
-
- public void setPrimary(boolean primary) {
- this.primary = primary;
- }
-
- public boolean isVerified() {
- return verified;
- }
-
- public void setVerified(boolean verified) {
- this.verified = verified;
- }
-
- public String getEmail() {
- return email;
- }
-
- public void setEmail(String email) {
- this.email = email;
+ } catch (OAuthAuthenticationException e) {
+ return null;
}
+ return token;
}
}
diff --git a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java
new file mode 100644
index 00000000000..4a101737eee
--- /dev/null
+++ b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2012-2021 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/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.security.oauth;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+import org.eclipse.che.api.auth.shared.dto.OAuthToken;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.security.oauth.shared.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides implementation of GitHub {@link OAuthAuthenticator} based on available configuration.
+ *
+ * @author Pavol Baran
+ */
+@Singleton
+public class GitHubOAuthAuthenticatorProvider implements Provider {
+ private static final Logger LOG = LoggerFactory.getLogger(GitHubOAuthAuthenticatorProvider.class);
+ private final OAuthAuthenticator authenticator;
+
+ @Inject
+ 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.oauth.github.authuri") String authUri,
+ @Nullable @Named("che.oauth.github.tokenuri") String tokenUri)
+ throws IOException {
+ authenticator =
+ getOAuthAuthenticator(
+ gitHubClientIdPath, gitHubClientSecretPath, redirectUris, authUri, tokenUri);
+ LOG.debug("{} GitHub OAuth Authenticator is used.", authenticator);
+ }
+
+ @Override
+ public OAuthAuthenticator get() {
+ return authenticator;
+ }
+
+ private OAuthAuthenticator getOAuthAuthenticator(
+ String clientIdPath,
+ String clientSecretPath,
+ String[] redirectUris,
+ String authUri,
+ String tokenUri)
+ throws IOException {
+
+ if (!isNullOrEmpty(clientIdPath)
+ && !isNullOrEmpty(clientSecretPath)
+ && !isNullOrEmpty(authUri)
+ && !isNullOrEmpty(tokenUri)
+ && Objects.nonNull(redirectUris)
+ && redirectUris.length != 0) {
+ String clientId = Files.readString(Path.of(clientIdPath));
+ String clientSecret = Files.readString(Path.of(clientSecretPath));
+ if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) {
+ return new GitHubOAuthAuthenticator(
+ clientId, clientSecret, redirectUris, authUri, tokenUri);
+ }
+ }
+ return new NoopOAuthAuthenticator();
+ }
+
+ static class NoopOAuthAuthenticator extends OAuthAuthenticator {
+ @Override
+ public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException {
+ throw new OAuthAuthenticationException(
+ "The fallback noop authenticator cannot be used for GitHub authentication. Make sure OAuth is properly configured.");
+ }
+
+ @Override
+ public String getOAuthProvider() {
+ return "Noop";
+ }
+ }
+}
diff --git a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GithubModule.java b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GithubModule.java
index 788594ad373..16ef10f5e2e 100644
--- a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GithubModule.java
+++ b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GithubModule.java
@@ -26,6 +26,6 @@ public class GithubModule extends AbstractModule {
protected void configure() {
Multibinder oAuthAuthenticators =
Multibinder.newSetBinder(binder(), OAuthAuthenticator.class);
- oAuthAuthenticators.addBinding().to(GitHubOAuthAuthenticator.class);
+ oAuthAuthenticators.addBinding().toProvider(GitHubOAuthAuthenticatorProvider.class);
}
}
diff --git a/wsmaster/che-core-api-auth-github/src/test/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProviderTest.java b/wsmaster/che-core-api-auth-github/src/test/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProviderTest.java
new file mode 100644
index 00000000000..fe42ff67c6a
--- /dev/null
+++ b/wsmaster/che-core-api-auth-github/src/test/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProviderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2012-2021 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/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.security.oauth;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class GitHubOAuthAuthenticatorProviderTest {
+ private static final String TEST_URI = "https://api.github.com";
+ private File credentialFile;
+ private File emptyFile;
+
+ @BeforeClass
+ public void setup() throws IOException {
+ credentialFile = File.createTempFile("GitHubOAuthAuthenticatorProviderTest-", "-credentials");
+ Files.asCharSink(credentialFile, Charset.defaultCharset()).write("id/secret");
+ credentialFile.deleteOnExit();
+ emptyFile = File.createTempFile("GitHubOAuthAuthenticatorProviderTest-", "-empty");
+ emptyFile.deleteOnExit();
+ }
+
+ @Test(dataProvider = "noopConfig")
+ public void shouldProvideNoopAuthenticatorWhenInvalidConfigurationSet(
+ String gitHubClientIdPath,
+ String gitHubClientSecretPath,
+ String[] redirectUris,
+ String authUri,
+ String tokenUri)
+ throws IOException {
+ // given
+ GitHubOAuthAuthenticatorProvider provider =
+ new GitHubOAuthAuthenticatorProvider(
+ gitHubClientIdPath, gitHubClientSecretPath, redirectUris, authUri, tokenUri);
+ // when
+ OAuthAuthenticator authenticator = provider.get();
+ // then
+ assertNotNull(authenticator);
+ assertTrue(
+ GitHubOAuthAuthenticatorProvider.NoopOAuthAuthenticator.class.isAssignableFrom(
+ authenticator.getClass()));
+ }
+
+ @Test
+ public void shouldProvideNoopAuthenticatorWhenConfigFilesAreEmpty() throws IOException {
+ // given
+ GitHubOAuthAuthenticatorProvider provider =
+ new GitHubOAuthAuthenticatorProvider(
+ emptyFile.getPath(), emptyFile.getPath(), new String[] {TEST_URI}, TEST_URI, TEST_URI);
+ // when
+ OAuthAuthenticator authenticator = provider.get();
+ // then
+ assertNotNull(authenticator);
+ assertTrue(
+ GitHubOAuthAuthenticatorProvider.NoopOAuthAuthenticator.class.isAssignableFrom(
+ authenticator.getClass()));
+ }
+
+ @Test
+ public void shouldProvideValidGitHubOAuthAuthenticator() throws IOException {
+ // given
+ GitHubOAuthAuthenticatorProvider provider =
+ new GitHubOAuthAuthenticatorProvider(
+ credentialFile.getPath(),
+ credentialFile.getPath(),
+ new String[] {TEST_URI},
+ TEST_URI,
+ TEST_URI);
+ // when
+ OAuthAuthenticator authenticator = provider.get();
+
+ // then
+ assertNotNull(authenticator);
+ assertTrue(GitHubOAuthAuthenticator.class.isAssignableFrom(authenticator.getClass()));
+ }
+
+ @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}
+ };
+ }
+}
diff --git a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java
index a0703c431d3..61c6bb5b90f 100644
--- a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java
+++ b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java
@@ -38,6 +38,7 @@
import java.util.Map;
import java.util.regex.Pattern;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
+import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.json.JsonHelper;
import org.eclipse.che.commons.json.JsonParseException;
import org.eclipse.che.security.oauth.shared.OAuthTokenProvider;
@@ -213,8 +214,7 @@ public String callback(URL requestUrl, List scopes) throws OAuthAuthenti
.execute();
String userId = getUserFromUrl(authorizationCodeResponseUrl);
if (userId == null) {
- userId =
- getUser(newDto(OAuthToken.class).withToken(tokenResponse.getAccessToken())).getId();
+ userId = EnvironmentContext.getCurrent().getSubject().getUserId();
}
flow.createAndStoreCredential(tokenResponse, userId);
return userId;
@@ -254,13 +254,14 @@ private String getUserFromUrl(AuthorizationCodeResponseUrl authorizationCodeResp
return null;
}
- protected O getJson(String getUserUrl, Class userClass)
+ protected O getJson(String getUserUrl, String accessToken, Class userClass)
throws OAuthAuthenticationException {
HttpURLConnection urlConnection = null;
InputStream urlInputStream = null;
try {
urlConnection = (HttpURLConnection) new URL(getUserUrl).openConnection();
+ urlConnection.setRequestProperty("Authorization", "token " + accessToken);
urlInputStream = urlConnection.getInputStream();
return JsonHelper.fromJson(urlInputStream, userClass, null);
} catch (JsonParseException | IOException e) {
diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcher.java
index 730040d7ec1..daf7a32ee55 100644
--- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcher.java
+++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcher.java
@@ -11,6 +11,7 @@
*/
package org.eclipse.che.api.factory.server.github;
+import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
@@ -250,6 +251,8 @@ private String getLocalAuthenticateUrl() {
return apiEndpoint
+ "/oauth/authenticate?oauth_provider="
+ OAUTH_PROVIDER_NAME
+ + "&scope="
+ + Joiner.on(',').join(DEFAULT_TOKEN_SCOPES)
+ "&request_method=POST&signature_method=rsa";
}
}