Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: GitHub OAuth authentication with embededOAuthAPI #178

Merged
merged 7 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,6 @@ 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

# GitHub OAuth authorization URI.
che.oauth.github.authuri= https://github.com/login/oauth/authorize

Expand Down
9 changes: 7 additions & 2 deletions wsmaster/che-core-api-auth-github/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,13 @@
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-json</artifactId>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -89,32 +50,6 @@ public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException
return user;
}

protected <O> O getJson2(String getUserUrl, Class<O> 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";
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* 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 com.google.common.annotations.VisibleForTesting;
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;

xbaran4 marked this conversation as resolved.
Show resolved Hide resolved
@Singleton
public class GitHubOAuthAuthenticatorProvider implements Provider<OAuthAuthenticator> {
private static final Logger LOG = LoggerFactory.getLogger(GitHubOAuthAuthenticatorProvider.class);
public static final String GITHUB_CLIENT_ID_PATH = "/che-conf/oauth/github/id";
public static final String GITHUB_CLIENT_SECRET_PATH = "/che-conf/oauth/github/secret";

private final OAuthAuthenticator authenticator;

@Inject
public GitHubOAuthAuthenticatorProvider(
@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(
redirectUris, authUri, tokenUri, GITHUB_CLIENT_ID_PATH, GITHUB_CLIENT_SECRET_PATH);
}

@Override
public OAuthAuthenticator get() {
return authenticator;
}

@VisibleForTesting
OAuthAuthenticator getOAuthAuthenticator(
String[] redirectUris,
String authUri,
String tokenUri,
String clientIdPath,
String clientSecretPath)
throws IOException {

if (isNullOrEmpty(authUri)
|| isNullOrEmpty(tokenUri)
|| Objects.isNull(redirectUris)
|| redirectUris.length == 0) {
xbaran4 marked this conversation as resolved.
Show resolved Hide resolved
LOG.debug(
"URIs for GitHub OAuth authentication are missing or empty. Make sure your configuration is correct.");
return new NoopOAuthAuthenticator();
}

String clientId, clientSecret;
try {
clientId = Files.readString(Path.of(clientIdPath));
clientSecret = Files.readString(Path.of(clientSecretPath));
} catch (IOException e) {
LOG.debug(
skabashnyuk marked this conversation as resolved.
Show resolved Hide resolved
"NoopOAuthAuthenticator will be used, because files containing GitHub credentials cannot be accessed. Cause: {}",
e.getMessage());
return new NoopOAuthAuthenticator();
}

if (isNullOrEmpty(clientId) || isNullOrEmpty(clientSecret)) {
LOG.debug(
"A file containing GitHub credentials is empty. NoopOAuthAuthenticator will be used.");
return new NoopOAuthAuthenticator();
}

return new GitHubOAuthAuthenticator(clientId, clientSecret, redirectUris, authUri, tokenUri);
}

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";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ public class GithubModule extends AbstractModule {
protected void configure() {
Multibinder<OAuthAuthenticator> oAuthAuthenticators =
Multibinder.newSetBinder(binder(), OAuthAuthenticator.class);
oAuthAuthenticators.addBinding().to(GitHubOAuthAuthenticator.class);
oAuthAuthenticators.addBinding().toProvider(GitHubOAuthAuthenticatorProvider.class);
}
}
Loading