Skip to content

Commit

Permalink
[#27] Add OAuth support for authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
pjeli committed Jul 20, 2018
1 parent 7102129 commit 19ca5ca
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 16 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ configurations {
dependencies {
outboundDep group: 'org.pac4j', name: 'spark-pac4j', version: '1.2.3'
outboundDep group: 'org.pac4j', name: 'pac4j-ldap', version: '1.9.4'
outboundDep group: 'org.pac4j', name: 'pac4j-oauth', version: '1.9.4'
outboundDep group: 'org.pac4j', name: 'pac4j-jwt', version: '1.9.4'
outboundDep group: 'com.sparkjava', name: 'spark-core', version: '2.5'
outboundDep group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
Expand Down
46 changes: 42 additions & 4 deletions src/main/java/com/paypal/namenode/NNAnalyticsRestAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
import org.ldaptive.pool.SearchValidator;
import org.ldaptive.ssl.KeyStoreCredentialConfig;
import org.ldaptive.ssl.SslConfig;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.core.exception.BadCredentialsException;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.jwt.config.encryption.EncryptionConfiguration;
Expand All @@ -109,6 +111,7 @@
import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator;
import org.pac4j.jwt.profile.JwtGenerator;
import org.pac4j.ldap.credentials.authenticator.LdapAuthenticator;
import org.pac4j.oauth.client.GenericOAuth20Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.Spark;
Expand Down Expand Up @@ -202,9 +205,16 @@ public void init(
}

boolean ldapEnabled = conf.getLdapEnabled();
boolean oauthEnabled = conf.getOAuthEnabled();
if (ldapEnabled && oauthEnabled) {
throw new IllegalStateException(
"Illegal authentication configuration. Please only enable one authentication method.");
}

Spark.port(conf.getPort());

if (ldapEnabled) {
LOG.info("Enabled web security.");
LOG.info("Enabling web security: LDAP");
// jwt:
SignatureConfiguration sigConf =
new SecretSignatureConfiguration(conf.getJwtSignatureSecret());
Expand Down Expand Up @@ -252,15 +262,43 @@ public void init(
LdapAuthenticator ldapAuth = new LdapAuthenticator();
ldapAuth.setLdapAuthenticator(ldaptiveAuthenticator);

secContext.init(conf, jwtAuthenticator, jwtGenerator, ldapAuth);
secContext.initLdap(conf, jwtAuthenticator, jwtGenerator, ldapAuth);
} else if (oauthEnabled) {
LOG.info("Enabling web security: OAUTH");
// jwt:
SignatureConfiguration sigConf =
new SecretSignatureConfiguration(conf.getJwtSignatureSecret());
EncryptionConfiguration encConf =
new SecretEncryptionConfiguration(
conf.getJwtEncryptionSecret(), JWEAlgorithm.DIR, EncryptionMethod.A128GCM);
JwtGenerator<CommonProfile> jwtGenerator = new JwtGenerator<>(sigConf, encConf);
JwtAuthenticator jwtAuthenticator = new JwtAuthenticator(sigConf, encConf);

// oauth:
GenericOAuth20Client oauth = new GenericOAuth20Client();
oauth.setName("oauth");
oauth.setKey(conf.getOAuthKey());
oauth.setSecret(conf.getOAuthSecret());
oauth.setAuthUrl(conf.getOAuthAuthUrl());
oauth.setProfileUrl(conf.getOAuthProfileUrl());
oauth.setTokenUrl(conf.getOAuthTokenUrl());
oauth.setScope(conf.getOAuthScope());

final Clients clients = new Clients("http://localhost:8080/callback", oauth);
final Config config = new Config(clients);

secContext.initOAuth(conf, jwtAuthenticator, jwtGenerator, config);
} else {
secContext.init(conf, null, null, null);
LOG.info("Disabled web security.");
LOG.info("Disabling web security.");
secContext.initNoAuth(conf);
}

/* This is the call to load everything under ./resources/public as HTML resources. */
Spark.staticFileLocation("/public");

/* CALLBACK is used by indirect authentication clients as part of pac4j */
get("/callback", secContext.getCallback());

/* ENDPOINTS endpoint is meant to showcase all available REST API endpoints in JSON list form. */
get(
"/endpoints",
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/paypal/security/SecurityConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class SecurityConfiguration {
private static final String NNA_PORT_DEFAULT = "8080";
private static final String NNA_HISTORICAL_DEFAULT = "false";
private static final String LDAP_ENABLED_DEFAULT = "false";
private static final String OAUTH_ENABLED_DEFAULT = "false";
private static final String AUTHORIZATION_ENABLED_DEFAULT = "false";
private static final String LDAP_USE_STARTTLS_DEFAULT = "false";
private static final String LDAP_CONNECT_TIMEOUT_DEFAULT = "1000";
Expand Down Expand Up @@ -123,6 +124,34 @@ public int getLdapConnectionPoolMaxSize() {
return Integer.parseInt(properties.getProperty("ldap.connection.pool.max.size"));
}

public boolean getOAuthEnabled() {
return Boolean.parseBoolean(properties.getProperty("oauth.enable", OAUTH_ENABLED_DEFAULT));
}

public String getOAuthKey() {
return properties.getProperty("oauth.key");
}

public String getOAuthSecret() {
return properties.getProperty("oauth.secret");
}

public String getOAuthAuthUrl() {
return properties.getProperty("oauth.auth.url");
}

public String getOAuthProfileUrl() {
return properties.getProperty("oauth.profile.url");
}

public String getOAuthTokenUrl() {
return properties.getProperty("oauth.token.url");
}

public String getOAuthScope() {
return properties.getProperty("oauth.scope");
}

public int getSuggestionsReloadSleepMs() {
return Integer.parseInt(
properties.getProperty(
Expand Down
80 changes: 68 additions & 12 deletions src/main/java/com/paypal/security/SecurityContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,22 @@
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authorize.AuthorizationException;
import org.ldaptive.auth.FormatDnResolver;
import org.pac4j.core.config.Config;
import org.pac4j.core.credentials.UsernamePasswordCredentials;
import org.pac4j.core.exception.HttpAction;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.profile.ProfileManager;
import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator;
import org.pac4j.jwt.profile.JwtGenerator;
import org.pac4j.ldap.credentials.authenticator.LdapAuthenticator;
import org.pac4j.sparkjava.CallbackRoute;
import org.pac4j.sparkjava.SecurityFilter;
import org.pac4j.sparkjava.SparkWebContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.Request;
import spark.Response;
import spark.Route;

public class SecurityContext {

Expand All @@ -47,6 +51,7 @@ public class SecurityContext {
private JwtAuthenticator jwtAuthenticator;
private JwtGenerator<CommonProfile> jwtGenerator;
private LdapAuthenticator ldapAuthenticator;
private Config oauthConfig;

private UserSet adminUsers;
private UserSet writeUsers;
Expand All @@ -68,7 +73,19 @@ private enum ACCESS_LEVEL {

public SecurityContext() {}

public void init(
public void initNoAuth(SecurityConfiguration secConf) {
this.securityConfiguration = secConf;

this.adminUsers = new UserSet(secConf.getAdminUsers());
this.writeUsers = new UserSet(secConf.getWriteUsers());
this.readOnlyUsers = new UserSet(secConf.getReadOnlyUsers());
this.cacheReaderUsers = new UserSet(secConf.getCacheReaderUsers());
this.localOnlyUsers = new UserPasswordSet(secConf.getLocalOnlyUsers());

this.init = true;
}

public void initLdap(
SecurityConfiguration secConf,
JwtAuthenticator jwtAuth,
JwtGenerator<CommonProfile> jwtGen,
Expand All @@ -87,6 +104,25 @@ public void init(
this.init = true;
}

public void initOAuth(
SecurityConfiguration secConf,
JwtAuthenticator jwtAuth,
JwtGenerator<CommonProfile> jwtGen,
Config oauthConfig) {
this.securityConfiguration = secConf;
this.jwtAuthenticator = jwtAuth;
this.jwtGenerator = jwtGen;
this.oauthConfig = oauthConfig;

this.adminUsers = new UserSet(secConf.getAdminUsers());
this.writeUsers = new UserSet(secConf.getWriteUsers());
this.readOnlyUsers = new UserSet(secConf.getReadOnlyUsers());
this.cacheReaderUsers = new UserSet(secConf.getCacheReaderUsers());
this.localOnlyUsers = new UserPasswordSet(secConf.getLocalOnlyUsers());

this.init = true;
}

public synchronized void refresh(SecurityConfiguration secConf) {
this.adminUsers = new UserSet(secConf.getAdminUsers());
this.writeUsers = new UserSet(secConf.getWriteUsers());
Expand All @@ -95,10 +131,18 @@ public synchronized void refresh(SecurityConfiguration secConf) {
this.localOnlyUsers = new UserPasswordSet(secConf.getLocalOnlyUsers());
}

public Route getCallback() {
if (securityConfiguration.getOAuthEnabled() && oauthConfig != null && init) {
return new CallbackRoute(oauthConfig);
}
return null;
}

public void handleAuthentication(Request req, Response res)
throws AuthenticationException, HttpAction {
boolean authenticationEnabled = securityConfiguration.getLdapEnabled();
if (!authenticationEnabled) {
boolean ldapEnabled = securityConfiguration.getLdapEnabled();
boolean oauthEnabled = securityConfiguration.getOAuthEnabled();
if (!ldapEnabled && !oauthEnabled) {
String reqUsername = req.queryParams("proxy");
if (reqUsername != null && !reqUsername.isEmpty()) {
currentUser.set(reqUsername);
Expand Down Expand Up @@ -158,20 +202,32 @@ public void handleAuthentication(Request req, Response res)
}
}

for (String ldapBaseDn : ldapBaseDns) {
String ldapDnRegexd = ldapBaseDn.replaceAll("%u", user);
ldapAuthenticator.getLdapAuthenticator().setDnResolver(new FormatDnResolver(ldapDnRegexd));
credentials = new UsernamePasswordCredentials(user, password, req.ip());
if (ldapEnabled) {
for (String ldapBaseDn : ldapBaseDns) {
String ldapDnRegexd = ldapBaseDn.replaceAll("%u", user);
ldapAuthenticator
.getLdapAuthenticator()
.setDnResolver(new FormatDnResolver(ldapDnRegexd));
credentials = new UsernamePasswordCredentials(user, password, req.ip());

try {
ldapAuthenticator.validate(credentials, new SparkWebContext(req, res));
} catch (RuntimeException e) {
authFailedEx = e;
continue;
}

authFailedEx = null;
break;
}
}

if (oauthEnabled) {
try {
ldapAuthenticator.validate(credentials, new SparkWebContext(req, res));
new SecurityFilter(oauthConfig, "oauth").handle(req, res);
} catch (RuntimeException e) {
authFailedEx = e;
continue;
}

authFailedEx = null;
break;
}

if (authFailedEx != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ enum OPERATION {
}

enum ENDPOINT {
callback,
endpoints,
credentials,
loadingStatus,
Expand Down Expand Up @@ -270,6 +271,7 @@ enum ENDPOINT {

EnumSet<ENDPOINT> UNSECURED_ENDPOINTS =
EnumSet.of(
ENDPOINT.callback,
ENDPOINT.endpoints,
ENDPOINT.credentials,
ENDPOINT.sets,
Expand Down

0 comments on commit 19ca5ca

Please sign in to comment.