Skip to content

Commit

Permalink
version 2.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sief committed Jan 19, 2017
1 parent 38ca211 commit c1ed218
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 39 deletions.
136 changes: 101 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
Play2 Guard Module
==========

Play2 module for blocking & throttling abusive requests.
Play2 module for blocking and throttling abusive requests.

- IP address whitelisting/blacklisting
- general request throttling
- throttling specific Actions based on IP address or request parameter
- throttling specific Actions based on IP address and failure rate (e.g. wrong credentials)
- throttling specific Actions based on IP address or other request attribute
- throttling specific Actions based on IP address or other request attribute and failure rate, e.g. wrong credentials or any other Result attribute

Target
----------

This module targets the __Scala__ version of __Play 2.4.x__
This module targets the __Scala__ version of __Play 2.5.x__

Rate Limit Algorithm
----------
Expand All @@ -21,18 +21,22 @@ Based on the token bucket algorithm: http://en.wikipedia.org/wiki/Token_bucket
Getting play-guard
----------

The current stable version is 1.6.0, which is cross-built against Scala 2.10.x and 2.11.x.
The current stable version is 2.0.0

(For Play 2.4.x you can use version 1.6.0 which is cross-built against Scala 2.10.x and 2.11.x.)

Add the following dependency to your build file:

```scala
"com.digitaltangible" %% "play-guard" % "1.6.0"
"com.digitaltangible" %% "play-guard" % "2.0.0"
```

1. GuardFilter
==========

Filter for rate limiting and IP address whitelisting/blacklisting
Filter for global rate limiting and IP address whitelisting/blacklisting.

__Note: this global filter is only useful if you don't have access to a reverse proxy like nginx where you can handle these kind of things__

1.1 Rules
----------
Expand All @@ -47,20 +51,31 @@ else if global rate limit exceeded => reject with ‘429 TOO_MANY_REQUEST’

1.2 Usage
----------
just include it in your filter list of your Global object, e.g.:

For compile time DI:

```scala
object Global extends WithFilters(GuardFilter()) {
class ApplicationComponents(context: Context) extends BuiltInComponentsFromContext(context) with PlayGuardComponents {

override lazy val httpFilters: Seq[EssentialFilter] = Seq(guardFilter)
}
```

Runtime DI with Guice:

```scala
@Singleton
class Filters @Inject()(env: Environment, guardFilter: GuardFilter) extends DefaultHttpFilters(guardFilter)
```


Requires configuration in your application.conf.

__Note: the config format has changed with v1.6.0__
__Note: the config format has changed with v2.0.0__


```
play.guard {
playguard {
# the http header to use for the client IP address.
# If not set, RequestHeader.remoteAddress will be used
Expand All @@ -75,70 +90,121 @@ play.guard {
}
}
ip {
whitelist = "1.1.1.1,2.2.2.2"
blacklist = "3.3.3.3,4.4.4.4"
whitelist = ["1.1.1.1", "2.2.2.2"]
blacklist = ["3.3.3.3", "4.4.4.4"]
bucket {
size = 5
rate = 10
size = 50
rate = 50
}
}
}
}
```

The filter uses the black/whitelists from the configuration by default. You can also plug in you own IpChecker implementation. With runtime time DI you have to disable the default module in your application.conf and bind your implementation in your app's module:

```
play {
modules {
disabled += "com.digitaltangible.playguard.PlayGuardIpCheckerModule"
}
}
```



2. RateLimitAction
==========

Action wrapper for rate limiting specific actions. Comes in three flavours:
Action function/filter for request and failure rate limiting specific actions. You can derive the bucket key from the request.

The rate limit actions all take a RateLimiter instance as the first parameter:

```scala
class RateLimiter(size: Int, rate: Float, logPrefix: String = "", clock: Clock = CurrentTimeClock)(implicit system: ActorSystem)
```

It holds the token bucket group with the specified size and rate and can be shared between actions if you want to use the same bucket group for various actions.


2.1 Simple rate limit

2.1 Request rate limit
-------

From the sample app:
There is a general ActionFilter for handling any type of request so you can chain it behind you own ActionTransformer:

```scala
/**
* ActionFilter which holds a RateLimiter with a bucket for each key returned by function f.
* Can be used with any Request type. Useful if you want to use content from a wrapped request, e.g. User ID
*
* @param rl
* @param rejectResponse
* @param f
* @tparam R
* @return
*/
class RateLimitActionFilter[R[_] <: Request[_]](rl: RateLimiter)(rejectResponse: R[_] => Result, f: R[_] => Any) extends ActionFilter[R]
```

There are also two convenience actions which provide the same functionality as in previous versions:

IP address as key (from the sample app):

```scala
// allow 3 requests immediately and get a new token every 5 seconds
private val ipRateLimited = IpRateLimitAction(RateLimiter(3, 1f / 5, "test limit by IP address")) {
implicit r: RequestHeader => BadRequest( s"""rate limit for ${r.remoteAddress} exceeded""")
private val ipRateLimitedAction = IpRateLimitAction(new RateLimiter(3, 1f / 5, "test limit by IP address")) {
implicit r: RequestHeader => TooManyRequests( s"""rate limit for ${r.remoteAddress} exceeded""")
}

def limitedByIp = ipRateLimited {
def limitedByIp: Action[AnyContent] = ipRateLimitedAction {
Ok("limited by IP")
}
```

2.2 Simple rate limit based on key parameter
-------

From the sample app:

Action parameter as key (from the sample app):

```scala
// allow 4 requests immediately and get a new token every 15 seconds
private val tokenRateLimited = KeyRateLimitAction(RateLimiter(4, 1f / 15, "test by token")) _
private val keyRateLimitedAction = KeyRateLimitAction(new RateLimiter(4, 1f / 15, "test by token")) _

def limitedByKey(key: String) = tokenRateLimited(_ => BadRequest( s"""rate limit for '$key' exceeded"""))(key) {
def limitedByKey(key: String): Action[AnyContent] = keyRateLimitedAction(_ => TooManyRequests( s"""rate limit for '$key' exceeded"""), key) {
Ok("limited by token")
}

```

2.3 Failure rate limit
2.3 Error rate limit
-------

This limits access to a specific action based on the failure rate. This is useful if you want to prevent brute force bot attacks on authentication requests.
There is a general ActionFunction for handling any type of request so you can chain it behind you own ActionTransformer and determine failure from the Result:

```scala
/**
* ActionFunction which holds a RateLimiter with a bucket for each key returned by function keyFromRequest.
* Tokens are consumed only by failures determined by function resultCheck. If no tokens remain, requests with this key are rejected.
* Can be used with any Request type. Useful if you want to use content from a wrapped request, e.g. User ID
*
* @param rl
* @param rejectResponse
* @param keyFromRequest
* @param resultCheck
* @tparam R
*/
class FailureRateLimitFunction[R[_] <: Request[_]](rl: RateLimiter)(rejectResponse: R[_] => Result, keyFromRequest: R[_] => Any, resultCheck: Result => Boolean) extends ActionFunction[R, R]```
```

The convenience action HttpErrorRateLimitAction limits the HTTP error rate for each IP address. This is for example useful if you want to prevent brute force bot attacks on authentication requests.

From the sample app:

```scala
// allow 2 failures immediately and get a new token every 10 seconds
private val failRateLimited = FailureRateLimitAction(2, 1f / 10, {
private val httpErrorRateLimited = HttpErrorRateLimitAction(new RateLimiter(2, 1f / 10, "test failure rate limit")) {
implicit r: RequestHeader => BadRequest("failure rate exceeded")
}, "test failure rate limit")
}

def failureLimitedByIp(fail: Boolean) = failRateLimited {
def failureLimitedByIp(fail: Boolean): Action[AnyContent] = httpErrorRateLimited {
if (fail) BadRequest("failed")
else Ok("Ok")
}
```

2 changes: 1 addition & 1 deletion module/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name := """play-guard"""

organization := """com.digitaltangible"""

version := "2.0.0-SNAPSHOT"
version := "2.0.0"

scalaVersion := "2.11.8"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ class SampleController @Inject()(implicit system: ActorSystem, conf: Configurati

if (fail) BadRequest("failed")
else Ok("Ok")
}}
}
}
2 changes: 1 addition & 1 deletion samples/play-guard-guice-sample/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ scalaVersion := "2.11.8"


libraryDependencies ++= Seq(
"com.digitaltangible" %% "play-guard" % "2.0.0-SNAPSHOT"
"com.digitaltangible" %% "play-guard" % "2.0.0"
)

2 changes: 1 addition & 1 deletion samples/play-guard-sample/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ lazy val root = (project in file(".")).enablePlugins(PlayScala)
scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
"com.digitaltangible" %% "play-guard" % "2.0.0-SNAPSHOT"
"com.digitaltangible" %% "play-guard" % "2.0.0"
)

0 comments on commit c1ed218

Please sign in to comment.