diff options
author | Jakob Odersky <jakob@driver.xyz> | 2018-08-22 12:51:36 -0700 |
---|---|---|
committer | Jakob Odersky <jakob@driver.xyz> | 2018-09-12 14:17:39 -0700 |
commit | 5ec270aa98b806f32338fa25357abdf45dd0625b (patch) | |
tree | f1423f8add00cc2f899d2f8259b9ab33ba3c914b /src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala | |
parent | 3eb6a9e96bd8bf111490f390ea94a1c6d7677eff (diff) | |
download | driver-core-5ec270aa98b806f32338fa25357abdf45dd0625b.tar.gz driver-core-5ec270aa98b806f32338fa25357abdf45dd0625b.tar.bz2 driver-core-5ec270aa98b806f32338fa25357abdf45dd0625b.zip |
Trait-based initialization and other utilities
Adds the concept of a 'platform', a centralized place in which
environment-specific information will be managed, and provides common
initialization logic for most "standard" apps.
As part of the common initialization, other parts of core have also
been reworked:
- HTTP-related unmarshallers and path matchers have been factored out
from core.json to a new core.rest.directives package (core.json
extends those unmarshallers and matchers for backwards
compatibility)
- CORS handling has also been moved to a dedicated utility trait
- Some custom headers have been moved from raw headers to typed ones
in core.rest.headers
- The concept of a "reporter" has been introduced. A reporter is a
context-aware combination of tracing and logging. It is intended to
issue diagnostic messages that can be traced across service
boundaries.
Closes #192
Closes #195
Diffstat (limited to 'src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala')
-rw-r--r-- | src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala | 72 |
1 files changed, 72 insertions, 0 deletions
diff --git a/src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala b/src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala new file mode 100644 index 0000000..5a6bbfd --- /dev/null +++ b/src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala @@ -0,0 +1,72 @@ +package xyz.driver.core +package rest +package directives + +import akka.http.scaladsl.model.HttpMethods._ +import akka.http.scaladsl.model.headers._ +import akka.http.scaladsl.model.{HttpResponse, StatusCodes} +import akka.http.scaladsl.server.{Route, Directives => AkkaDirectives} + +/** Directives to handle Cross-Origin Resource Sharing (CORS). */ +trait CorsDirectives extends AkkaDirectives { + + /** Route handler that injects Cross-Origin Resource Sharing (CORS) headers depending on the request + * origin. + * + * In a microservice environment, it can be difficult to know in advance the exact origin + * from which requests may be issued [1]. For example, the request may come from a web page served from + * any of the services, on any namespace or from other documentation sites. In general, only a set + * of domain suffixes can be assumed to be known in advance. Unfortunately however, browsers that + * implement CORS require exact specification of allowed origins, including full host name and scheme, + * in order to send credentials and headers with requests to other origins. + * + * This route wrapper provides a simple way alleviate CORS' exact allowed-origin requirement by + * dynamically echoing the origin as an allowed origin if and only if its domain is whitelisted. + * + * Note that the simplicity of this implementation comes with two notable drawbacks: + * + * - All OPTION requests are "hijacked" and will not be passed to the inner route of this wrapper. + * + * - Allowed methods and headers can not be customized on a per-request basis. All standard + * HTTP methods are allowed, and allowed headers are specified for all inner routes. + * + * This handler is not suited for cases where more fine-grained control of responses is required. + * + * [1] Assuming browsers communicate directly with the services and that requests aren't proxied through + * a common gateway. + * + * @param allowedSuffixes The set of domain suffixes (e.g. internal.example.org, example.org) of allowed + * origins. + * @param allowedHeaders Header names that will be set in `Access-Control-Allow-Headers`. + * @param inner Route into which CORS headers will be injected. + */ + def cors(allowedSuffixes: Set[String], allowedHeaders: Seq[String])(inner: Route): Route = { + optionalHeaderValueByType[Origin](()) { maybeOrigin => + val allowedOrigins: HttpOriginRange = maybeOrigin match { + // Note that this is not a security issue: the client will never send credentials if the allowed + // origin is set to *. This case allows us to deal with clients that do not send an origin header. + case None => HttpOriginRange.* + case Some(requestOrigin) => + val allowedOrigin = requestOrigin.origins.find(origin => + allowedSuffixes.exists(allowed => origin.host.host.address endsWith allowed)) + allowedOrigin.map(HttpOriginRange(_)).getOrElse(HttpOriginRange.*) + } + + respondWithHeaders( + `Access-Control-Allow-Origin`.forRange(allowedOrigins), + `Access-Control-Allow-Credentials`(true), + `Access-Control-Allow-Headers`(allowedHeaders: _*), + `Access-Control-Expose-Headers`(allowedHeaders: _*) + ) { + options { // options is used during preflight check + complete( + HttpResponse(StatusCodes.OK) + .withHeaders(`Access-Control-Allow-Methods`(OPTIONS, POST, PUT, GET, DELETE, PATCH, TRACE))) + } ~ inner // in case of non-preflight check we don't do anything special + } + } + } + +} + +object CorsDirectives extends CorsDirectives |