Skip to content

Commit

Permalink
Merge pull request #178 from xbaran4/oauth2-factory
Browse files Browse the repository at this point in the history
feat: GitHub OAuth authentication with embededOAuthAPI
  • Loading branch information
xbaran4 authored Dec 8, 2021
2 parents 5a726ee + 3690bbc commit fb8735b
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
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,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<OAuthAuthenticator> {
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";
}
}
}
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

0 comments on commit fb8735b

Please sign in to comment.