Skip to content

Commit

Permalink
first draft of jwt auth
Browse files Browse the repository at this point in the history
  • Loading branch information
ssalinas committed Oct 20, 2017
1 parent 85d1ce9 commit 1b99c24
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 47 deletions.
Expand Up @@ -2,13 +2,15 @@


import com.hubspot.singularity.auth.authenticator.SingularityAuthenticator; import com.hubspot.singularity.auth.authenticator.SingularityAuthenticator;
import com.hubspot.singularity.auth.authenticator.SingularityDisabledAuthenticator; import com.hubspot.singularity.auth.authenticator.SingularityDisabledAuthenticator;
import com.hubspot.singularity.auth.authenticator.SingularityExternalJWTAuthenticator;
import com.hubspot.singularity.auth.authenticator.SingularityHeaderPassthroughAuthenticator; import com.hubspot.singularity.auth.authenticator.SingularityHeaderPassthroughAuthenticator;
import com.hubspot.singularity.auth.authenticator.SingularityQueryParamAuthenticator; import com.hubspot.singularity.auth.authenticator.SingularityQueryParamAuthenticator;


public enum SingularityAuthenticatorClass { public enum SingularityAuthenticatorClass {
DISABLED(SingularityDisabledAuthenticator.class), DISABLED(SingularityDisabledAuthenticator.class),
HEADER_PASSTHROUGH(SingularityHeaderPassthroughAuthenticator.class), HEADER_PASSTHROUGH(SingularityHeaderPassthroughAuthenticator.class),
QUERYPARAM_PASSTHROUGH(SingularityQueryParamAuthenticator.class); QUERYPARAM_PASSTHROUGH(SingularityQueryParamAuthenticator.class),
JWT(SingularityExternalJWTAuthenticator.class);


private final Class<? extends SingularityAuthenticator> authenticatorClass; private final Class<? extends SingularityAuthenticator> authenticatorClass;


Expand Down
@@ -1,42 +1,57 @@
package com.hubspot.singularity.auth.authenticator; package com.hubspot.singularity.auth.authenticator;


import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.hubspot.singularity.SingularityAsyncHttpClient;
import com.hubspot.singularity.SingularityUser; import com.hubspot.singularity.SingularityUser;
import com.hubspot.singularity.WebExceptions; import com.hubspot.singularity.WebExceptions;
import com.hubspot.singularity.config.AuthConfiguration; import com.hubspot.singularity.config.JWTConfiguration;
import com.hubspot.singularity.config.SingularityConfiguration; import com.hubspot.singularity.config.SingularityConfiguration;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.Response;


@Singleton @Singleton
public class SingularityExternalJWTAuthenticator implements SingularityAuthenticator { public class SingularityExternalJWTAuthenticator implements SingularityAuthenticator {
private final SingularityAsyncHttpClient asyncHttpClient; private static final Logger LOG = LoggerFactory.getLogger(SingularityExternalJWTAuthenticator.class);
private final AuthConfiguration authConfiguration;
private final AsyncHttpClient asyncHttpClient;
private final JWTConfiguration jwtConfiguration;
private final Provider<HttpServletRequest> requestProvider; private final Provider<HttpServletRequest> requestProvider;
private final ObjectMapper objectMapper;
private final Cache<String, UserPermissions> permissionsCache; private final Cache<String, UserPermissions> permissionsCache;


@Inject @Inject
public SingularityExternalJWTAuthenticator(SingularityAsyncHttpClient asyncHttpClient, SingularityConfiguration configuration, Provider<HttpServletRequest> requestProvider) { public SingularityExternalJWTAuthenticator(AsyncHttpClient asyncHttpClient,
SingularityConfiguration configuration,
Provider<HttpServletRequest> requestProvider,
ObjectMapper objectMapper) {
this.asyncHttpClient = asyncHttpClient; this.asyncHttpClient = asyncHttpClient;
this.authConfiguration = configuration.getAuthConfiguration(); this.jwtConfiguration = configuration.getJwtConfiguration();
this.requestProvider = requestProvider; this.requestProvider = requestProvider;
this.objectMapper = objectMapper;
this.permissionsCache = CacheBuilder.<String, UserPermissions>newBuilder() this.permissionsCache = CacheBuilder.<String, UserPermissions>newBuilder()
.expireAfterWrite("") .expireAfterWrite(jwtConfiguration.getCacheValidationMs(), TimeUnit.MILLISECONDS)
.build(new CacheLoader<String, UserPermissions>() { .build();
@Override
public UserPermissions load(String key) throws Exception {
return null;
}
})
} }


@Override @Override
Expand All @@ -45,31 +60,85 @@ public Optional<SingularityUser> get() {


UserPermissions permissions = verifyToken(jwt); UserPermissions permissions = verifyToken(jwt);


return Optional.of(new SingularityUser("", "", "", "")); Set<String> groups = new HashSet<>();
groups.addAll(permissions.getGroups());
groups.addAll(permissions.getScopes());
return Optional.of(new SingularityUser(permissions.getUid(), Optional.of(permissions.getUid()), Optional.of(permissions.getUid()), groups));
} }


private String extractJWT(HttpServletRequest request) { private String extractJWT(HttpServletRequest request) {
String cookieValue = null; String cookieValue = null;
for (Cookie cookie : request.getCookies()) { for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals(authConfiguration.getJwtAuthCookieName())) { if (cookie.getName().equals(jwtConfiguration.getAuthCookieName())) {
cookieValue = cookie.getValue(); cookieValue = cookie.getValue();
break; break;
} }
} }


if (Strings.isNullOrEmpty(cookieValue)) { if (Strings.isNullOrEmpty(cookieValue)) {
throw WebExceptions.unauthorized(String.format("No %s cookie present, please log in first", authConfiguration.getJwtAuthCookieName())); throw WebExceptions.unauthorized(String.format("No %s cookie present, please log in first", jwtConfiguration.getAuthCookieName()));
} else { } else {
return cookieValue; return cookieValue;
} }
} }


private UserPermissions verifyToken(String jwt) { private UserPermissions verifyToken(String jwt) {
// http request to verify, returns user permissions/email/id UserPermissions maybeCachedPermissions = permissionsCache.getIfPresent(jwt);
if (maybeCachedPermissions != null) {
return maybeCachedPermissions;
} else {
try {
Response response = asyncHttpClient.prepareGet(jwtConfiguration.getAuthVerificationUrl())
.addHeader("Authorization", String.format("Bearer: %s", jwt))
.execute()
.get();
if (response.getStatusCode() > 299) {
throw WebExceptions.unauthorized(String.format("Got status code %d when verifying jwt", response.getStatusCode()));
} else {
String responseBody = response.getResponseBody();
UserPermissions permissions = objectMapper.readValue(responseBody, UserPermissions.class);
permissionsCache.put(jwt, permissions);
return permissions;
}
} catch (IOException|ExecutionException|InterruptedException e) {
LOG.error("Exception while verifying jwt", e);
throw WebExceptions.unauthorized(String.format("Exception while verifying jwt: %s", e.getMessage()));
}
}
} }



@JsonIgnoreProperties(ignoreUnknown = true)
private class UserPermissions { private class UserPermissions {
// mimic janus Permissions class private final String uid;
private final Set<String> groups;
private final Set<String> scopes;

@JsonCreator
public UserPermissions(@JsonProperty("uid") String uid, @JsonProperty("groups") Set<String> groups, @JsonProperty("scopes") Set<String> scopes) {
this.uid = uid;
this.groups = groups;
this.scopes = scopes;
}

public String getUid() {
return uid;
}

public Set<String> getGroups() {
return groups;
}

public Set<String> getScopes() {
return scopes;
}

@Override
public String toString() {
return "UserPermissions{" +
"uid='" + uid + '\'' +
", groups=" + groups +
", scopes=" + scopes +
'}';
}
} }
} }
Expand Up @@ -45,14 +45,6 @@ public class AuthConfiguration {
@NotNull @NotNull
private String requestUserHeaderName = "X-Username"; // used by SingularityHeaderPassthroughAuthenticator private String requestUserHeaderName = "X-Username"; // used by SingularityHeaderPassthroughAuthenticator


@JsonProperty
@NotNull
private String jwtAuthVerificationUrl = ""; // used by SingularityExternalJWTAuthenticator

@JsonProperty
@NotNull
private String jwtAuthCookieName = ""; // used by SingularityExternalJWTAuthenticator

public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
} }
Expand Down Expand Up @@ -124,22 +116,4 @@ public String getRequestUserHeaderName() {
public void setRequestUserHeaderName(String requestUserHeaderName) { public void setRequestUserHeaderName(String requestUserHeaderName) {
this.requestUserHeaderName = requestUserHeaderName; this.requestUserHeaderName = requestUserHeaderName;
} }

public String getJwtAuthVerificationUrl() {
return jwtAuthVerificationUrl;
}

public AuthConfiguration setJwtAuthVerificationUrl(String jwtAuthVerificationUrl) {
this.jwtAuthVerificationUrl = jwtAuthVerificationUrl;
return this;
}

public String getJwtAuthCookieName() {
return jwtAuthCookieName;
}

public AuthConfiguration setJwtAuthCookieName(String jwtAuthCookieName) {
this.jwtAuthCookieName = jwtAuthCookieName;
return this;
}
} }
Expand Up @@ -306,6 +306,10 @@ public class SingularityConfiguration extends Configuration {
@Valid @Valid
private LDAPConfiguration ldapConfiguration; private LDAPConfiguration ldapConfiguration;


@JsonProperty("jwt")
@Valid
private JWTConfiguration jwtConfiguration;

@JsonProperty("auth") @JsonProperty("auth")
@NotNull @NotNull
@Valid @Valid
Expand Down Expand Up @@ -1256,6 +1260,15 @@ public Optional<LDAPConfiguration> getLdapConfigurationOptional() {
return Optional.fromNullable(ldapConfiguration); return Optional.fromNullable(ldapConfiguration);
} }


public JWTConfiguration getJwtConfiguration() {
return jwtConfiguration;
}

public SingularityConfiguration setJwtConfiguration(JWTConfiguration jwtConfiguration) {
this.jwtConfiguration = jwtConfiguration;
return this;
}

public void setLdapConfiguration(LDAPConfiguration ldapConfiguration) { public void setLdapConfiguration(LDAPConfiguration ldapConfiguration) {
this.ldapConfiguration = ldapConfiguration; this.ldapConfiguration = ldapConfiguration;
} }
Expand Down

0 comments on commit 1b99c24

Please sign in to comment.