Skip to content

Commit

Permalink
Header filtering config (#2627)
Browse files Browse the repository at this point in the history
* Adding configuration to disable header credential stripping

* Added IT test

* Fixed checkstyle

* Fixed bug

Co-authored-by: Aaron Klish <klish@verizonmedia.com>
  • Loading branch information
aklish and Aaron Klish committed Apr 22, 2022
1 parent 9d5b98b commit abb3286
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 22 deletions.
2 changes: 2 additions & 0 deletions elide-core/src/main/java/com/yahoo/elide/ElideSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.yahoo.elide.core.utils.coerce.converters.Serde;
import com.yahoo.elide.jsonapi.JsonApiMapper;
import com.yahoo.elide.jsonapi.links.JSONApiLinks;
import com.yahoo.elide.utils.HeaderUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;

Expand All @@ -39,6 +40,7 @@ public class ElideSettings {
@Getter private final List<SubqueryFilterDialect> subqueryFilterDialects;
@Getter private final FilterDialect graphqlDialect;
@Getter private final JSONApiLinks jsonApiLinks;
@Getter private final HeaderUtils.HeaderProcessor headerProcessor;
@Getter private final int defaultMaxPageSize;
@Getter private final int defaultPageSize;
@Getter private final int updateStatusCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.yahoo.elide.core.utils.coerce.converters.URLSerde;
import com.yahoo.elide.jsonapi.JsonApiMapper;
import com.yahoo.elide.jsonapi.links.JSONApiLinks;
import com.yahoo.elide.utils.HeaderUtils;

import java.net.URL;
import java.time.Instant;
Expand All @@ -56,6 +57,7 @@ public class ElideSettingsBuilder {
private List<SubqueryFilterDialect> subqueryFilterDialects;
private FilterDialect graphqlFilterDialect;
private JSONApiLinks jsonApiLinks;
private HeaderUtils.HeaderProcessor headerProcessor;
private Map<Class, Serde> serdes;
private int defaultMaxPageSize = PaginationImpl.MAX_PAGE_LIMIT;
private int defaultPageSize = PaginationImpl.DEFAULT_PAGE_LIMIT;
Expand All @@ -79,6 +81,7 @@ public ElideSettingsBuilder(DataStore dataStore) {
this.jsonApiMapper = new JsonApiMapper();
this.joinFilterDialects = new ArrayList<>();
this.subqueryFilterDialects = new ArrayList<>();
this.headerProcessor = HeaderUtils::lowercaseAndRemoveAuthHeaders;
updateStatusCode = HttpStatus.SC_NO_CONTENT;
this.serdes = new LinkedHashMap<>();
this.enableJsonLinks = false;
Expand Down Expand Up @@ -118,6 +121,7 @@ public ElideSettings build() {
subqueryFilterDialects,
graphqlFilterDialect,
jsonApiLinks,
headerProcessor,
defaultMaxPageSize,
defaultPageSize,
updateStatusCode,
Expand Down Expand Up @@ -231,6 +235,11 @@ public ElideSettingsBuilder withJSONApiLinks(JSONApiLinks links) {
return this;
}

public ElideSettingsBuilder withHeaderProcessor(HeaderUtils.HeaderProcessor headerProcessor) {
this.headerProcessor = headerProcessor;
return this;
}

public ElideSettingsBuilder withJsonApiPath(String jsonApiPath) {
this.jsonApiPath = jsonApiPath;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@
@Path("/")
public class JsonApiEndpoint {
protected final Elide elide;
protected final HeaderUtils.HeaderProcessor headerProcessor;

@Inject
public JsonApiEndpoint(
@Named("elide") Elide elide) {
this.elide = elide;
this.headerProcessor = elide.getElideSettings().getHeaderProcessor();
}

/**
Expand All @@ -71,8 +73,7 @@ public Response post(
String jsonapiDocument) {
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
String apiVersion = HeaderUtils.resolveApiVersion(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders =
HeaderUtils.lowercaseAndRemoveAuthHeaders(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders = headerProcessor.process(headers.getRequestHeaders());
User user = new SecurityContextUser(securityContext);
return build(elide.post(getBaseUrlEndpoint(uriInfo), path, jsonapiDocument,
queryParams, requestHeaders, user, apiVersion, UUID.randomUUID()));
Expand All @@ -96,8 +97,7 @@ public Response get(
@Context SecurityContext securityContext) {
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
String apiVersion = HeaderUtils.resolveApiVersion(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders =
HeaderUtils.lowercaseAndRemoveAuthHeaders(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders = headerProcessor.process(headers.getRequestHeaders());
User user = new SecurityContextUser(securityContext);

return build(elide.get(getBaseUrlEndpoint(uriInfo), path, queryParams,
Expand Down Expand Up @@ -129,8 +129,7 @@ public Response patch(
String jsonapiDocument) {
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
String apiVersion = HeaderUtils.resolveApiVersion(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders =
HeaderUtils.lowercaseAndRemoveAuthHeaders(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders = headerProcessor.process(headers.getRequestHeaders());
User user = new SecurityContextUser(securityContext);
return build(elide.patch(getBaseUrlEndpoint(uriInfo), contentType, accept, path,
jsonapiDocument, queryParams, requestHeaders, user, apiVersion, UUID.randomUUID()));
Expand Down Expand Up @@ -158,8 +157,7 @@ public Response delete(
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
String apiVersion =
HeaderUtils.resolveApiVersion(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders =
HeaderUtils.lowercaseAndRemoveAuthHeaders(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders = headerProcessor.process(headers.getRequestHeaders());
User user = new SecurityContextUser(securityContext);
return build(elide.delete(getBaseUrlEndpoint(uriInfo), path, jsonApiDocument, queryParams, requestHeaders,
user, apiVersion, UUID.randomUUID()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
*/
public class HeaderUtils {

@FunctionalInterface
public interface HeaderProcessor {
Map<String, List<String>> process(Map<String, List<String>> headers);
}

/**
* Resolve value of api version from request headers.
* @param headers HttpHeaders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@
public class GraphQLEndpoint {
private final Map<String, QueryRunner> runners;
private final Elide elide;
private final HeaderUtils.HeaderProcessor headerProcessor;

@Inject
public GraphQLEndpoint(@Named("elide") Elide elide) {
log.debug("Started ~~");
this.elide = elide;
this.headerProcessor = elide.getElideSettings().getHeaderProcessor();
this.runners = new HashMap<>();
for (String apiVersion : elide.getElideSettings().getDictionary().getApiVersions()) {
runners.put(apiVersion, new QueryRunner(elide, apiVersion));
Expand All @@ -71,8 +73,7 @@ public Response post(
@Context SecurityContext securityContext,
String graphQLDocument) {
String apiVersion = HeaderUtils.resolveApiVersion(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders =
HeaderUtils.lowercaseAndRemoveAuthHeaders(headers.getRequestHeaders());
Map<String, List<String>> requestHeaders = headerProcessor.process(headers.getRequestHeaders());
User user = new SecurityContextUser(securityContext);
QueryRunner runner = runners.getOrDefault(apiVersion, null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.yahoo.elide.modelconfig.validator.DynamicConfigValidator;
import com.yahoo.elide.spring.controllers.SwaggerController;
import com.yahoo.elide.swagger.SwaggerBuilder;
import com.yahoo.elide.utils.HeaderUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -153,6 +154,7 @@ public DataSourceConfiguration getDataSourceConfiguration() {
* Creates the Elide instance with standard settings.
* @param dictionary Stores the static metadata about Elide models.
* @param dataStore The persistence store.
* @param headerProcessor HTTP header function which is invoked for every request.
* @param transactionRegistry Global transaction registry.
* @param settings Elide settings.
* @return A new elide instance.
Expand All @@ -162,6 +164,7 @@ public DataSourceConfiguration getDataSourceConfiguration() {
@ConditionalOnMissingBean
public RefreshableElide getRefreshableElide(EntityDictionary dictionary,
DataStore dataStore,
HeaderUtils.HeaderProcessor headerProcessor,
TransactionRegistry transactionRegistry,
ElideConfigProperties settings,
JsonApiMapper mapper,
Expand All @@ -179,6 +182,7 @@ public RefreshableElide getRefreshableElide(EntityDictionary dictionary,
.withBaseUrl(settings.getBaseUrl())
.withISO8601Dates("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone("UTC"))
.withJsonApiPath(settings.getJsonApi().getPath())
.withHeaderProcessor(headerProcessor)
.withGraphQLApiPath(settings.getGraphql().getPath());

if (settings.isVerboseErrors()) {
Expand Down Expand Up @@ -216,6 +220,22 @@ public QueryRunners getQueryRunners(RefreshableElide refreshableElide) {
return new QueryRunners(refreshableElide);
}

/**
* Function which preprocesses HTTP request headers before storing them in the RequestScope.
* @param settings Configuration settings
* @return A function which processes HTTP Headers.
*/
@Bean
@ConditionalOnMissingBean
public HeaderUtils.HeaderProcessor getHeaderProcessor(ElideConfigProperties settings) {
if (settings.isStripAuthorizatonHeaders()) {
return HeaderUtils::lowercaseAndRemoveAuthHeaders;
} else {
//Identity Function
return (a) -> a;
}
}

/**
* A Set containing Types to be excluded from EntityDictionary's EntityBinding.
* @param settings Elide configuration settings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,9 @@ public class ElideConfigProperties {
* Turns on/off verbose error responses.
*/
private boolean verboseErrors = false;

/**
* Remove Authorization headers from RequestScope to prevent accidental logging of security credentials.
*/
private boolean stripAuthorizatonHeaders = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,20 @@ public class GraphqlController {
private final ElideConfigProperties settings;
private final QueryRunners runners;
private final ObjectMapper mapper;
private final HeaderUtils.HeaderProcessor headerProcessor;

private static final String JSON_CONTENT_TYPE = "application/json";

@Autowired
public GraphqlController(
QueryRunners runners,
JsonApiMapper jsonApiMapper,
HeaderUtils.HeaderProcessor headerProcessor,
ElideConfigProperties settings) {
log.debug("Started ~~");
this.runners = runners;
this.settings = settings;
this.headerProcessor = headerProcessor;
this.mapper = jsonApiMapper.getObjectMapper();
}

Expand All @@ -81,8 +84,7 @@ public Callable<ResponseEntity<String>> post(@RequestHeader HttpHeaders requestH
@RequestBody String graphQLDocument, Authentication principal) {
final User user = new AuthenticationUser(principal);
final String apiVersion = HeaderUtils.resolveApiVersion(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned =
HeaderUtils.lowercaseAndRemoveAuthHeaders(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned = headerProcessor.process(requestHeaders);
final QueryRunner runner = runners.getRunner(apiVersion);
final String baseUrl = getBaseUrlEndpoint();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class JsonApiController {

private final Elide elide;
private final ElideConfigProperties settings;
private final HeaderUtils.HeaderProcessor headerProcessor;
public static final String JSON_API_CONTENT_TYPE = JSONAPI_CONTENT_TYPE;
public static final String JSON_API_PATCH_CONTENT_TYPE = JSONAPI_CONTENT_TYPE_WITH_JSON_PATCH_EXTENSION;

Expand All @@ -65,6 +66,7 @@ public JsonApiController(RefreshableElide refreshableElide, ElideConfigPropertie
log.debug("Started ~~");
this.settings = settings;
this.elide = refreshableElide.getElide();
this.headerProcessor = elide.getElideSettings().getHeaderProcessor();
}

private <K, V> MultivaluedHashMap<K, V> convert(MultiValueMap<K, V> springMVMap) {
Expand All @@ -78,8 +80,7 @@ public Callable<ResponseEntity<String>> elideGet(@RequestHeader HttpHeaders requ
@RequestParam MultiValueMap<String, String> allRequestParams,
HttpServletRequest request, Authentication authentication) {
final String apiVersion = HeaderUtils.resolveApiVersion(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned =
HeaderUtils.lowercaseAndRemoveAuthHeaders(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned = headerProcessor.process(requestHeaders);
final String pathname = getJsonApiPath(request, settings.getJsonApi().getPath());
final User user = new AuthenticationUser(authentication);
final String baseUrl = getBaseUrlEndpoint();
Expand All @@ -101,8 +102,7 @@ public Callable<ResponseEntity<String>> elidePost(@RequestHeader HttpHeaders req
@RequestBody String body,
HttpServletRequest request, Authentication authentication) {
final String apiVersion = HeaderUtils.resolveApiVersion(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned =
HeaderUtils.lowercaseAndRemoveAuthHeaders(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned = headerProcessor.process(requestHeaders);
final String pathname = getJsonApiPath(request, settings.getJsonApi().getPath());
final User user = new AuthenticationUser(authentication);
final String baseUrl = getBaseUrlEndpoint();
Expand All @@ -123,8 +123,7 @@ public Callable<ResponseEntity<String>> elidePatch(@RequestHeader HttpHeaders re
@RequestBody String body,
HttpServletRequest request, Authentication authentication) {
final String apiVersion = HeaderUtils.resolveApiVersion(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned =
HeaderUtils.lowercaseAndRemoveAuthHeaders(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned = headerProcessor.process(requestHeaders);
final String pathname = getJsonApiPath(request, settings.getJsonApi().getPath());
final User user = new AuthenticationUser(authentication);
final String baseUrl = getBaseUrlEndpoint();
Expand All @@ -147,8 +146,7 @@ public Callable<ResponseEntity<String>> elideDelete(@RequestHeader HttpHeaders r
HttpServletRequest request,
Authentication authentication) {
final String apiVersion = HeaderUtils.resolveApiVersion(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned =
HeaderUtils.lowercaseAndRemoveAuthHeaders(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned = headerProcessor.process(requestHeaders);
final String pathname = getJsonApiPath(request, settings.getJsonApi().getPath());
final User user = new AuthenticationUser(authentication);
final String baseUrl = getBaseUrlEndpoint();
Expand All @@ -172,8 +170,7 @@ public Callable<ResponseEntity<String>> elideDeleteRelation(
HttpServletRequest request,
Authentication authentication) {
final String apiVersion = HeaderUtils.resolveApiVersion(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned =
HeaderUtils.lowercaseAndRemoveAuthHeaders(requestHeaders);
final Map<String, List<String>> requestHeadersCleaned = headerProcessor.process(requestHeaders);
final String pathname = getJsonApiPath(request, settings.getJsonApi().getPath());
final User user = new AuthenticationUser(authentication);
final String baseUrl = getBaseUrlEndpoint();
Expand Down
Loading

0 comments on commit abb3286

Please sign in to comment.