Implement GitHub SSO option

This commit is contained in:
Andrew Guibert 2018-05-23 12:44:18 -05:00
parent b2075728ef
commit 2320d5d4ab
11 changed files with 264 additions and 7 deletions

View File

@ -10,6 +10,8 @@ liberty {
dropins = [war]
bootstrapProperties = ['httpPort': httpPort, 'httpsPort': httpsPort]
configDirectory = file('src/main/liberty/config')
jvmOptions = ['-Dorg.libertybikes.auth.service.github.GitHubOAuthAPI/mp-rest/url=https://github.com',
'-Dorg.libertybikes.auth.service.github.GitHubUserAPI/mp-rest/url=https://api.github.com']
}
}
dependencies {

View File

@ -0,0 +1,49 @@
package org.libertybikes.auth.service.github;
import java.net.URI;
import java.util.UUID;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.libertybikes.auth.service.AuthApp;
@Path("/GitHubAuth")
@ApplicationScoped
public class GitHubAuth {
private final static String GITHUB_AUTH_URL = "https://github.com/login/oauth/authorize";
@Inject
@ConfigProperty(name = "github_key")
String key;
@Inject
@ConfigProperty(name = "auth_url", defaultValue = AuthApp.HTTPS_AUTH_SERVICE)
String authUrl;
@GET
public Response getAuthURL(@Context HttpServletRequest request) {
try {
String randomCode = UUID.randomUUID().toString();
request.getSession().setAttribute("github", randomCode);
// GitHub will tell the users browser to go to this address once they are done authing.
String callbackURL = authUrl + "/GitHubCallback";
// send the user to GitHub to be authenticated
String redirectURL = GITHUB_AUTH_URL + "?client_id=" + key + "&redirect_url=" + callbackURL + "&scope=user:email&state=" + randomCode;
return Response.temporaryRedirect(new URI(redirectURL)).build();
} catch (Exception e) {
e.printStackTrace();
return Response.status(500).build();
}
}
}

View File

@ -0,0 +1,131 @@
package org.libertybikes.auth.service.github;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.libertybikes.auth.service.AuthApp;
import org.libertybikes.auth.service.JwtAuth;
import org.libertybikes.auth.service.github.GitHubOAuthAPI.GithubTokenResponse;
import org.libertybikes.auth.service.github.GitHubUserAPI.EmailData;
@Path("/GitHubCallback")
@ApplicationScoped
public class GitHubCallback extends JwtAuth {
@Inject
GitHubOAuthAPI githubOAuthAPI;
@Inject
GitHubUserAPI githubUserAPI;
@Inject
@ConfigProperty(name = "github_key")
String key;
@Inject
@ConfigProperty(name = "github_secret")
String secret;
@Inject
@ConfigProperty(name = "frontend_url", defaultValue = AuthApp.FRONTEND_URL)
String frontendUrl;
@Inject
@ConfigProperty(name = "auth_url", defaultValue = AuthApp.HTTPS_AUTH_SERVICE)
String authUrl;
@Inject
Validator validator;
@GET
@Path("/test")
@Valid
public GithubTokenResponse testToken() {
for (Method m : getClass().getMethods()) {
System.out.println("@AGG found method: " + m.getName());
for (Annotation a : m.getAnnotations())
System.out.println(" anno=" + a);
}
GithubTokenResponse badToken = new GithubTokenResponse();
badToken.access_token = "bogus";
return badToken;
}
// TODO: need to manually validate return types because the @Valid annotation on our MP Rest Client interface
// is getting lost when the instance is injected into this class.
private void validate(Object obj) {
Objects.requireNonNull(obj);
Set<ConstraintViolation<Object>> issues = validator.validate(obj);
if (!issues.isEmpty()) {
throw new ConstraintViolationException(issues);
}
}
@GET
public Response getAuthURL(@Context HttpServletRequest request) throws IOException, URISyntaxException {
try {
String githubCode = request.getParameter("code");
String randomCode = (String) request.getSession().getAttribute("github");
String thisURL = authUrl + "/GitHubCallback";
// First send the user through GitHub OAuth to get permission to read their email address
GithubTokenResponse response = githubOAuthAPI.accessToken(key, secret, githubCode, thisURL, randomCode);
// for (Method m : githubOAuthAPI.getClass().getMethods()) {
// System.out.println("@AGG found method: " + m.getName());
// for (Annotation a : m.getAnnotations())
// System.out.println(" anno=" + a);
// }
validate(response);
System.out.println("GitHub access token: " + response.access_token);
// Once we have the access token, use it to read their email
EmailData[] emails = githubUserAPI.getEmail(response.access_token);
for (EmailData email : emails)
validate(email);
String primaryEmail = null;
for (EmailData data : emails)
if (data.primary) {
primaryEmail = data.email;
break;
} else {
primaryEmail = data.email;
}
System.out.println("Got primary email of: " + primaryEmail);
Map<String, String> claims = new HashMap<String, String>();
claims.put("valid", "true");
claims.put("id", "GITHUB:" + primaryEmail);
claims.put("upn", primaryEmail);
claims.put("email", primaryEmail);
return Response.temporaryRedirect(new URI(frontendUrl + "/" + createJwt(claims))).build();
} catch (Exception e) {
e.printStackTrace();
return fail();
}
}
private Response fail() throws URISyntaxException {
return Response.temporaryRedirect(new URI(frontendUrl)).build();
}
}

View File

@ -0,0 +1,32 @@
package org.libertybikes.auth.service.github;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@RegisterRestClient
@Path("/login/oauth")
public interface GitHubOAuthAPI {
public static class GithubTokenResponse {
@NotNull
public String access_token;
}
@GET
@Path("/access_token")
@Produces(MediaType.APPLICATION_JSON)
@Valid // TODO: beanval isn't being honored here when used with MP Rest client
public GithubTokenResponse accessToken(@QueryParam("client_id") String key,
@QueryParam("client_secret") String secret,
@QueryParam("code") String code,
@QueryParam("redirect_uri") String redirect_uri,
@QueryParam("state") String state);
}

View File

@ -0,0 +1,29 @@
package org.libertybikes.auth.service.github;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@RegisterRestClient
@Path("/user")
public interface GitHubUserAPI {
public static class EmailData {
@Email
public String email;
public boolean primary;
}
@GET
@Path("/emails")
@Produces(MediaType.APPLICATION_JSON)
@Valid // TODO: beanval isn't being called here when rest client instance is injected
public EmailData[] getEmail(@QueryParam("access_token") String accessToken);
}

View File

@ -1,4 +1,4 @@
package org.libertybikes.auth.service;
package org.libertybikes.auth.service.google;
import java.net.URI;
import java.util.Arrays;
@ -12,6 +12,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.libertybikes.auth.service.AuthApp;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.http.HttpTransport;

View File

@ -1,4 +1,4 @@
package org.libertybikes.auth.service;
package org.libertybikes.auth.service.google;
import java.io.IOException;
import java.net.URI;
@ -20,6 +20,8 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.libertybikes.auth.service.AuthApp;
import org.libertybikes.auth.service.JwtAuth;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;

View File

@ -1,12 +1,14 @@
<server>
<featureManager>
<feature>appSecurity-3.0</feature>
<feature>beanValidation-2.0</feature>
<feature>cdi-2.0</feature>
<feature>jaxrs-2.1</feature>
<feature>jndi-1.0</feature>
<feature>jsonb-1.0</feature>
<feature>mpConfig-1.2</feature>
<feature>mpJwt-1.0</feature>
<feature>mpRestClient-1.0</feature>
</featureManager>
<httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="${httpPort}" httpsPort="${httpsPort}" />

View File

@ -18,6 +18,9 @@
<div class="form-item">
<button type="button" (click)="loginGoogle()">Sign in with Google</button>
</div>
<div class="form-item">
<button type="button" (click)="loginGithub()">Sign in with GitHub</button>
</div>
<h2 *ngIf="isFullDevice" class="section-header">or</h2>
<div *ngIf="isFullDevice" class="form-item">
<button type="button" (click)="hostRound()">Host Round</button>

View File

@ -71,6 +71,10 @@ export class LoginComponent implements OnInit, OnDestroy {
loginGoogle() {
window.location.href = `${environment.API_URL_AUTH}/auth-service/GoogleAuth`;
}
loginGithub() {
window.location.href = `${environment.API_URL_AUTH}/auth-service/GitHubAuth`;
}
async quickJoin() {
// First get an unstarted round ID

View File

@ -9,7 +9,8 @@ public class Player {
public static enum DOMAIN {
BASIC,
GMAIL;
GMAIL,
GITHUB;
@Override
public String toString() {
@ -59,10 +60,11 @@ public class Player {
@JsonbTransient
public DOMAIN getDomain() {
if (id.startsWith(DOMAIN.GMAIL.toString()))
return DOMAIN.GMAIL;
else
return DOMAIN.BASIC;
for (DOMAIN d : DOMAIN.values()) {
if (id.startsWith(d.toString()))
return d;
}
return DOMAIN.BASIC;
}
@Override