feature/Remove in-memory mode and embedded redis in case of Rate Limiting

This commit is contained in:
Marko Milić 2023-10-27 11:50:28 +02:00
parent 66bbf50768
commit 03bf9e4fc1
6 changed files with 29 additions and 74 deletions

View File

@ -405,10 +405,6 @@ There are two supported modes:
It is assumed that you have some Redis instance if you wan to use the functionality in multi node architecture.
To set up Rate Limiting in case of In-Memory mode edit your props file in next way:
```
use_consumer_limits_in_memory_mode=true
```
We apply Rate Limiting for two type of access:
* Authorized

View File

@ -318,12 +318,6 @@
<artifactId>scalameta_${scala.version}</artifactId>
<version>3.7.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.codemonstur/embedded-redis -->
<dependency>
<groupId>com.github.codemonstur</groupId>
<artifactId>embedded-redis</artifactId>
<version>1.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.nimbusds/oauth2-oidc-sdk -->
<dependency>

View File

@ -798,8 +798,6 @@ featured_apis=elasticSearchWarehouseV300
# Define how many calls per hour a consumer can make
# In case isn't defined default value is "false"
# use_consumer_limits=false
# In case isn't defined default value is "false"
# use_consumer_limits_in_memory_mode=false
# In case isn't defined default value is 60
# user_consumer_limit_anonymous_access=100
# redis_address=127.0.0.1

View File

@ -6,14 +6,11 @@ import code.api.util.ErrorMessages.TooManyRequests
import code.api.util.RateLimitingJson.CallLimit
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.model.User
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.util.Props
import net.liftweb.common.{Box, Empty}
import redis.clients.jedis.Jedis
import redis.embedded.RedisServer
import scala.collection.immutable
import scala.collection.immutable.{List, Nil}
import scala.util.Random
object RateLimitingPeriod extends Enumeration {
@ -76,34 +73,10 @@ object RateLimitingUtil extends MdcLoggable {
val port = APIUtil.getPropsAsIntValue("redis_port", 6379)
val url = APIUtil.getPropsValue("redis_address", "127.0.0.1")
private val mockedRedisPort = 6380 + Random.nextInt(20)
private val mockedRedisHost = "127.0.0.1"
private var server: RedisServer = null
def useConsumerLimits = APIUtil.getPropsAsBoolValue("use_consumer_limits", false)
def inMemoryMode = APIUtil.getPropsAsBoolValue("use_consumer_limits_in_memory_mode", false)
lazy val jedis = Props.mode match {
case Props.RunModes.Test =>
startMockedRedis(mode="Test")
case _ =>
if(inMemoryMode == true) {
startMockedRedis(mode="In-Memory")
} else {
new Jedis(url, port)
}
}
private def startMockedRedis(mode: String): Jedis = {
import redis.clients.jedis.Jedis
server = new RedisServer(mockedRedisPort)
server.start()
logger.info(msg = "-------------| Mocked Redis instance has been run in " + mode + " mode")
logger.info(msg = "-------------| at host: " + mockedRedisHost)
logger.info(msg = "-------------| at port: " + mockedRedisPort)
new Jedis(mockedRedisHost, mockedRedisPort)
}
lazy val jedis = new Jedis(url, port)
def isRedisAvailable() = {
try {
@ -175,17 +148,8 @@ object RateLimitingUtil extends MdcLoggable {
jedis.setex(key, seconds, "1")
(seconds, 1)
case _ => // otherwise increment the counter
// TODO redis-mock has a bug "INCR clears TTL"
inMemoryMode match {
case true =>
val cnt: Long = jedis.get(key).toLong + 1
jedis.setex(key, ttl, String.valueOf(cnt))
(ttl, cnt)
case false =>
val cnt = jedis.incr(key)
(ttl, cnt)
}
val cnt = jedis.incr(key)
(ttl, cnt)
}
}
} catch {

View File

@ -1242,15 +1242,9 @@ trait APIMethods310 {
for {
(_, callContext) <- anonymousAccess(cc)
rateLimiting <- NewStyle.function.tryons("", 400, callContext) {
RateLimitingUtil.inMemoryMode match {
case true =>
val isActive = if(RateLimitingUtil.useConsumerLimits == true) true else false
RateLimiting(RateLimitingUtil.useConsumerLimits, "In-Memory", true, isActive)
case false =>
val isRedisAvailable = RateLimitingUtil.isRedisAvailable()
val isActive = if(RateLimitingUtil.useConsumerLimits == true && isRedisAvailable == true) true else false
RateLimiting(RateLimitingUtil.useConsumerLimits, "REDIS", isRedisAvailable, isActive)
}
val isRedisAvailable = RateLimitingUtil.isRedisAvailable()
val isActive = if (RateLimitingUtil.useConsumerLimits && isRedisAvailable) true else false
RateLimiting(RateLimitingUtil.useConsumerLimits, "REDIS", isRedisAvailable, isActive)
}
} yield {
(createRateLimitingInfo(rateLimiting), HttpCode.`200`(callContext))

View File

@ -25,23 +25,23 @@ TESOBE (http://www.tesobe.com/)
*/
package code.api.v4_0_0
import java.time.format.DateTimeFormatter
import java.time.{ZoneId, ZonedDateTime}
import java.util.Date
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole.{CanSetCallLimits, canCreateDynamicEndpoint}
import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn}
import code.api.util.{APIUtil, ApiRole, ExampleValue}
import code.api.util.{ApiRole, ExampleValue, RateLimitingUtil}
import code.api.v3_0_0.OBPAPI3_0_0.Implementations3_0_0.getCurrentUser
import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0
import code.entitlement.Entitlement
import code.setup.PropsReset
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiVersionUtils.versions
import code.setup.PropsReset
import java.time.format.DateTimeFormatter
import java.time.{ZoneId, ZonedDateTime}
import java.util.Date
class RateLimitingTest extends V400ServerSetup with PropsReset {
@ -92,6 +92,7 @@ class RateLimitingTest extends V400ServerSetup with PropsReset {
feature("Rate Limit - " + ApiCallsLimit + " - " + ApiVersion400) {
scenario("We will try to set Rate Limiting per minute for a Consumer - unauthorized access", ApiCallsLimit, ApiVersion400) {
assume(RateLimitingUtil.isRedisAvailable())
When("We make a request v4.0.0")
val response400 = setRateLimitingAnonymousAccess(callLimitJsonInitial)
Then("We should get a 401")
@ -100,6 +101,7 @@ class RateLimitingTest extends V400ServerSetup with PropsReset {
response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn)
}
scenario("We will try to set Rate Limiting per minute without a proper Role " + ApiRole.canSetCallLimits, ApiCallsLimit, ApiVersion400) {
assume(RateLimitingUtil.isRedisAvailable())
When("We make a request v4.0.0 without a Role " + ApiRole.canSetCallLimits)
val response400 = setRateLimitingWithoutRole(user1, callLimitJsonInitial)
Then("We should get a 403")
@ -108,6 +110,7 @@ class RateLimitingTest extends V400ServerSetup with PropsReset {
response400.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanSetCallLimits)
}
scenario("We will try to set Rate Limiting per minute with a proper Role " + ApiRole.canSetCallLimits, ApiCallsLimit, ApiVersion400) {
assume(RateLimitingUtil.isRedisAvailable())
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
val response400 = setRateLimiting(user1, callLimitJsonInitial)
Then("We should get a 200")
@ -115,7 +118,8 @@ class RateLimitingTest extends V400ServerSetup with PropsReset {
response400.body.extract[CallLimitJsonV400]
}
scenario("We will set Rate Limiting per second for an Endpoint", ApiCallsLimit, ApiVersion400) {
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
assume(RateLimitingUtil.isRedisAvailable())
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
val response01 = setRateLimiting(user1, callLimitJsonSecond)
Then("We should get a 200")
response01.code should equal(200)
@ -137,7 +141,8 @@ class RateLimitingTest extends V400ServerSetup with PropsReset {
response04.code should equal(200)
}
scenario("We will set Rate Limiting per minute for an Endpoint", ApiCallsLimit, ApiVersion400) {
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
assume(RateLimitingUtil.isRedisAvailable())
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
val response01 = setRateLimiting(user1, callLimitJsonMinute)
Then("We should get a 200")
response01.code should equal(200)
@ -158,7 +163,8 @@ class RateLimitingTest extends V400ServerSetup with PropsReset {
response04.code should equal(200)
}
scenario("We will set Rate Limiting per hour for an Endpoint", ApiCallsLimit, ApiVersion400) {
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
assume(RateLimitingUtil.isRedisAvailable())
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
val response01 = setRateLimiting(user1, callLimitJsonHour)
Then("We should get a 200")
response01.code should equal(200)
@ -179,7 +185,8 @@ class RateLimitingTest extends V400ServerSetup with PropsReset {
response04.code should equal(200)
}
scenario("We will set Rate Limiting per week for an Endpoint", ApiCallsLimit, ApiVersion400) {
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
assume(RateLimitingUtil.isRedisAvailable())
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
val response01 = setRateLimiting(user1, callLimitJsonWeek)
Then("We should get a 200")
response01.code should equal(200)
@ -200,7 +207,8 @@ class RateLimitingTest extends V400ServerSetup with PropsReset {
response04.code should equal(200)
}
scenario("We will set Rate Limiting per month for an Endpoint", ApiCallsLimit, ApiVersion400) {
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
assume(RateLimitingUtil.isRedisAvailable())
When("We make a request v4.0.0 with a Role " + ApiRole.canSetCallLimits)
val response01 = setRateLimiting(user1, callLimitJsonMonth)
Then("We should get a 200")
response01.code should equal(200)
@ -224,7 +232,8 @@ class RateLimitingTest extends V400ServerSetup with PropsReset {
feature(s"Dynamic Endpoint: test $ApiCreateDynamicEndpoint version $ApiVersion400 - authorized access - with role - should be success!") {
scenario("We will call the endpoint with user credentials", ApiCreateDynamicEndpoint, ApiVersion400) {
When("We make a request v4.0.0")
assume(RateLimitingUtil.isRedisAvailable())
When("We make a request v4.0.0")
val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1)