2

To ensure people don't append random query parameters (e.g. appending &r=234522.123 or similar) to avoid hitting our cache I want to have a way to reject any queries that are not handled explicitly. I can of course create one that contains a whitelist, but that would have to be separately maintained and I hate maintaining two things that needs to stay in synch. (Though, it would aid in failing faster.) Is this possible with Spray routing?

4lex1v
  • 21,367
  • 6
  • 52
  • 86
Stig Brautaset
  • 2,602
  • 1
  • 22
  • 39

3 Answers3

1

I ended up with this:

// This contains a white-list of allowed query parameters. This is useful to
// ensure people don't try to use &r=234234 to bust your caches.
def allowedParameters(params: String*): Directive0 = parameterSeq.flatMap {
  case xs =>
    val illegal = xs.collect {
      case (k, _) if !params.contains(k) => k
    }
    if (illegal.nonEmpty)
      reject(ValidationRejection("Illegal query parameters: " + illegal.mkString("", ", ", "\nAllowed ones are: ") + params.mkString(", ")))
    else
      pass
}

For usage, have a look at the unit tests:

val allowedRoute = {
  allowedParameters("foo", "bar") {
    complete("OK")
  }
}

"Allowed Parameter Directive" should "reject parameters not in its whitelist" in {
  Get("/?foo&bar&quux") ~> allowedRoute ~> check {
    handled should equal(false)
    rejection should be(ValidationRejection("Illegal query parameters: quux\nAllowed ones are: foo, bar"))
  }
}

it should "allow properly sorted parameters through" in {
  Get("/?bar&foo") ~> allowedRoute ~> check {
    handled should equal(true)
    responseAs[String] should equal("OK")
  }
}
Stig Brautaset
  • 2,602
  • 1
  • 22
  • 39
1

Actually, you have a good solution, i can only suggest a small refactoring:

def only(params: String*): Directive0 = {
  def check: Map[String, String] => Boolean = _.keySet diff params.toSet isEmpty
  parameterMap.require(check, rejection)
}

You can write it as a one-liner, but it would be just longer

4lex1v
  • 21,367
  • 6
  • 52
  • 86
-2

With a route like this:

val myRoute = {
  ...
  pathPrefix("test") {
    parameter("good") {
      good =>
        complete("GOOD")
    }
  } ~
 ...
}

Spray will require first parameter to be good and to have value, i.e. ?good=value. No other parameters that have values are allowed.

yǝsʞǝla
  • 16,272
  • 2
  • 44
  • 65
  • I don't understand how you get to that conclusion. Do you mean that other parameters are *ignored*? That is not what I want. I want the route to be rejected with an error message. – Stig Brautaset Mar 06 '14 at 20:18
  • I meant that if there are any other query parameters that have value, i.e. `param=value` except for the `good=value` then the route will be rejected with an error message. `url/test?good=true` - accepted, `url/test?bad=true?good=true` - rejected, `url/test?good=true?bad=true` - rejected. However for some reason params without values are not rejected: `url/test?good=true?bad` or `url/test?bad?good=true` are accepted. – yǝsʞǝla Mar 06 '14 at 20:36
  • I think we talk about two different things when we say "rejected". Your `/test?good=...` route will only be *accepted* if you have a parameter called good, with a value, in the request. But it doesn't reject other parameters in the sense I want; I want don't want superfluous arguments to be merely ignored, I want them to be an error. – Stig Brautaset Mar 11 '14 at 22:44
  • Spray just converts all your params into map or seq of tuples, and with parameter directive extract from this collection data you want. It would reject `url/test?good=true?bad=true` cause it's an invalid url query – 4lex1v Mar 12 '14 at 17:49