diff --git a/obp-api/pom.xml b/obp-api/pom.xml
index 0cbb7cb5b2..846196e016 100644
--- a/obp-api/pom.xml
+++ b/obp-api/pom.xml
@@ -116,10 +116,12 @@
postgresql
42.4.3
+
com.oracle.database.jdbc
- ojdbc8
- 21.5.0.0
+ ojdbc8-production
+ 23.2.0.0
+ pom
diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template
index 37e90c3774..b6efb92cce 100644
--- a/obp-api/src/main/resources/props/sample.props.template
+++ b/obp-api/src/main/resources/props/sample.props.template
@@ -100,6 +100,14 @@ read_authentication_type_validation_requires_role=false
## enable logging all the database queries in log file
#logging.database.queries.enable=true
+## enable logging all the database queries in log file
+#database_query_timeout_in_seconds=
+
+## Define endpoint timeouts in miliseconds
+short_endpoint_timeout = 1000
+medium_endpoint_timeout = 7000
+long_endpoint_timeout = 60000
+
##Added Props property_name_prefix, default is OBP_. This adds the prefix only for the system environment property name, eg: db.driver --> OBP_db.driver
#system_environment_property_name_prefix=OBP_
diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
index 941bac83b4..b10785f090 100644
--- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
+++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
@@ -137,7 +137,7 @@ import com.openbankproject.commons.util.Functions.Implicits._
import com.openbankproject.commons.util.{ApiVersion, Functions}
import javax.mail.internet.MimeMessage
import net.liftweb.common._
-import net.liftweb.db.DBLogEntry
+import net.liftweb.db.{DB, DBLogEntry}
import net.liftweb.http.LiftRules.DispatchPF
import net.liftweb.http._
import net.liftweb.http.provider.HTTPCookie
@@ -149,7 +149,7 @@ import net.liftweb.util.Helpers._
import net.liftweb.util.{DefaultConnectionIdentifier, Helpers, Props, Schedule, _}
import org.apache.commons.io.FileUtils
-import scala.concurrent.ExecutionContext
+import scala.concurrent.{ExecutionContext, Future}
/**
* A class that's instantiated early and run. It allows the application
@@ -279,6 +279,25 @@ class Boot extends MdcLoggable {
}
}
}
+
+ // Database query timeout
+ APIUtil.getPropsValue("database_query_timeout_in_seconds").map { timeoutInSeconds =>
+ tryo(timeoutInSeconds.toInt).isDefined match {
+ case true =>
+ DB.queryTimeout = Full(timeoutInSeconds.toInt)
+ logger.info(s"Query timeout database_query_timeout_in_seconds is set to ${timeoutInSeconds} seconds")
+ case false =>
+ logger.error(
+ s"""
+ |------------------------------------------------------------------------------------
+ |Query timeout database_query_timeout_in_seconds [${timeoutInSeconds}] is not an integer value.
+ |Actual DB.queryTimeout value: ${DB.queryTimeout}
+ |------------------------------------------------------------------------------------""".stripMargin)
+ }
+
+ }
+
+
implicit val formats = CustomJsonFormats.formats
LiftRules.statelessDispatch.prepend {
case _ if tryo(DB.use(DefaultConnectionIdentifier){ conn => conn}.isClosed).isEmpty =>
@@ -835,15 +854,26 @@ class Boot extends MdcLoggable {
// create Hydra client if exists active consumer but missing Hydra client
def createHydraClients() = {
- import scala.concurrent.ExecutionContext.Implicits.global
- // exists hydra clients id
- val oAuth2ClientIds = HydraUtil.hydraAdmin.listOAuth2Clients(Long.MaxValue, 0L).stream()
- .map[String](_.getClientId)
- .collect(Collectors.toSet())
-
- Consumers.consumers.vend.getConsumersFuture().foreach{ consumers =>
- consumers.filter(consumer => consumer.isActive.get && !oAuth2ClientIds.contains(consumer.key.get))
- .foreach(HydraUtil.createHydraClient(_))
+ try {
+ import scala.concurrent.ExecutionContext.Implicits.global
+ // exists hydra clients id
+ val oAuth2ClientIds = HydraUtil.hydraAdmin.listOAuth2Clients(Long.MaxValue, 0L).stream()
+ .map[String](_.getClientId)
+ .collect(Collectors.toSet())
+
+ Consumers.consumers.vend.getConsumersFuture().foreach{ consumers =>
+ consumers.filter(consumer => consumer.isActive.get && !oAuth2ClientIds.contains(consumer.key.get))
+ .foreach(HydraUtil.createHydraClient(_))
+ }
+ } catch {
+ case e: Exception =>
+ if(HydraUtil.integrateWithHydra) {
+ logger.error("------------------------------ Mirror consumer in hydra issue ------------------------------")
+ e.printStackTrace()
+ } else {
+ logger.warn("------------------------------ Mirror consumer in hydra issue ------------------------------")
+ logger.warn(e)
+ }
}
}
diff --git a/obp-api/src/main/scala/code/api/constant/constant.scala b/obp-api/src/main/scala/code/api/constant/constant.scala
index 8b70623d67..b0707099f5 100644
--- a/obp-api/src/main/scala/code/api/constant/constant.scala
+++ b/obp-api/src/main/scala/code/api/constant/constant.scala
@@ -14,6 +14,10 @@ object Constant extends MdcLoggable {
final val limit = 500
}
+ final val shortEndpointTimeoutInMillis = APIUtil.getPropsAsLongValue(nameOfProperty = "short_endpoint_timeout", 1L * 1000L)
+ final val mediumEndpointTimeoutInMillis = APIUtil.getPropsAsLongValue(nameOfProperty = "medium_endpoint_timeout", 7L * 1000L)
+ final val longEndpointTimeoutInMillis = APIUtil.getPropsAsLongValue(nameOfProperty = "long_endpoint_timeout", 60L * 1000L)
+
final val h2DatabaseDefaultUrlValue = "jdbc:h2:mem:OBPTest_H2_v2.1.214;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=10"
final val HostName = APIUtil.getPropsValue("hostname").openOrThrowException(ErrorMessages.HostnameNotSpecified)
@@ -102,6 +106,7 @@ object ResponseHeader {
final lazy val `WWW-Authenticate` = "WWW-Authenticate"
final lazy val ETag = "ETag"
final lazy val `Cache-Control` = "Cache-Control"
+ final lazy val Connection = "Connection"
}
object BerlinGroup extends Enumeration {
diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala
index ab44b9f34b..24ce46b399 100644
--- a/obp-api/src/main/scala/code/api/util/APIUtil.scala
+++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala
@@ -116,6 +116,7 @@ import org.apache.commons.lang3.StringUtils
import java.security.AccessControlException
import java.util.regex.Pattern
+import code.api.util.FutureUtil.EndpointTimeout
import code.etag.MappedETag
import code.users.Users
import net.liftweb.mapper.By
@@ -807,6 +808,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
def check401(message: String): Boolean = {
message.contains(extractErrorMessageCode(UserNotLoggedIn))
}
+ def check408(message: String): Boolean = {
+ message.contains(extractErrorMessageCode(requestTimeout))
+ }
val (code, responseHeaders) =
message match {
case msg if check401(msg) =>
@@ -816,6 +820,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
(401, getHeaders() ::: headers.list ::: addHeader)
case msg if check403(msg) =>
(403, getHeaders() ::: headers.list)
+ case msg if check408(msg) =>
+ (408, getHeaders() ::: headers.list ::: List((ResponseHeader.Connection, "close")))
case _ =>
(httpCode, getHeaders() ::: headers.list)
}
@@ -2950,8 +2956,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
* @tparam T
* @return
*/
- implicit def scalaFutureToBoxedJsonResponse[T](scf: OBPReturnType[T])(implicit m: Manifest[T]): Box[JsonResponse] = {
- futureToBoxedResponse(scalaFutureToLaFuture(scf))
+ implicit def scalaFutureToBoxedJsonResponse[T](scf: OBPReturnType[T])(implicit t: EndpointTimeout, m: Manifest[T]): Box[JsonResponse] = {
+ futureToBoxedResponse(scalaFutureToLaFuture(FutureUtil.futureWithTimeout(scf)))
}
diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
index 7954296e17..12c536f260 100644
--- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
+++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
@@ -41,6 +41,10 @@ object ErrorMessages {
val NoValidElasticsearchIndicesConfigured = "OBP-00011: No elasticsearch indices are allowed on this instance. Please set es.warehouse.allowed.indices = index1,index2 (or = ALL for all). "
val CustomerFirehoseNotAllowedOnThisInstance = "OBP-00012: Customer firehose is not allowed on this instance. Please set allow_customer_firehose = true in props files. "
+ // Exceptions (OBP-01XXX) ------------------------------------------------>
+ val requestTimeout = "OBP-01000: Request Timeout. The OBP API decided to return a timeout. This is probably because a backend service did not respond in time. "
+ // <------------------------------------------------ Exceptions (OBP-01XXX)
+
// WebUiProps Exceptions (OBP-08XXX)
val InvalidWebUiProps = "OBP-08001: Incorrect format of name."
val WebUiPropsNotFound = "OBP-08002: WebUi props not found. Please specify a valid value for WEB_UI_PROPS_ID."
diff --git a/obp-api/src/main/scala/code/api/util/FutureUtil.scala b/obp-api/src/main/scala/code/api/util/FutureUtil.scala
new file mode 100644
index 0000000000..7202c2f028
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/util/FutureUtil.scala
@@ -0,0 +1,64 @@
+package code.api.util
+
+import java.util.concurrent.TimeoutException
+import java.util.{Timer, TimerTask}
+
+import code.api.Constant
+
+import scala.concurrent.{ExecutionContext, Future, Promise}
+import scala.language.postfixOps
+
+object FutureUtil {
+
+ // All Future's that use futureWithTimeout will use the same Timer object
+ // it is thread safe and scales to thousands of active timers
+ // The true parameter ensures that timeout timers are daemon threads and do not stop
+ // the program from shutting down
+
+ val timer: Timer = new Timer(true)
+
+ case class EndpointTimeout(inMillis: Long)
+
+ implicit val defaultTimeout: EndpointTimeout = EndpointTimeout(Constant.longEndpointTimeoutInMillis)
+
+ /**
+ * Returns the result of the provided future within the given time or a timeout exception, whichever is first
+ * This uses Java Timer which runs a single thread to handle all futureWithTimeouts and does not block like a
+ * Thread.sleep would
+ * @param future Caller passes a future to execute
+ * @param timeout Time before we return a Timeout exception instead of future's outcome
+ * @return Future[T]
+ */
+ def futureWithTimeout[T](future : Future[T])(implicit timeout : EndpointTimeout, ec: ExecutionContext): Future[T] = {
+
+ // Promise will be fulfilled with either the callers Future or the timer task if it times out
+ var p = Promise[T]
+
+ // and a Timer task to handle timing out
+
+ val timerTask = new TimerTask() {
+ def run() : Unit = {
+ p.tryFailure(new TimeoutException(ErrorMessages.requestTimeout))
+ }
+ }
+
+ // Set the timeout to check in the future
+ timer.schedule(timerTask, timeout.inMillis)
+
+ future.map {
+ a =>
+ if(p.trySuccess(a)) {
+ timerTask.cancel()
+ }
+ }
+ .recover {
+ case e: Exception =>
+ if(p.tryFailure(e)) {
+ timerTask.cancel()
+ }
+ }
+
+ p.future
+ }
+
+}
diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala
index e436c1cd81..21366d3fd5 100644
--- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala
+++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala
@@ -1,13 +1,16 @@
package code.api.v5_1_0
+import code.api.Constant
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{apiCollectionJson400, apiCollectionsJson400, apiInfoJson400, postApiCollectionJson400, revokedConsentJsonV310, _}
import code.api.util.APIUtil._
import code.api.util.ApiRole._
import code.api.util.ApiTag._
import code.api.util.ErrorMessages.{$UserNotLoggedIn, BankNotFound, ConsentNotFound, InvalidJsonFormat, UnknownError, UserNotFoundByUserId, UserNotLoggedIn, _}
+import code.api.util.FutureUtil.EndpointTimeout
import code.api.util.NewStyle.HttpCode
import code.api.util._
+import code.api.v2_0_0.{EntitlementJSONs, JSONFactory200}
import code.api.v3_0_0.JSONFactory300
import code.api.v3_0_0.JSONFactory300.createAggregateMetricJson
import code.api.v3_1_0.ConsentJsonV310
@@ -24,12 +27,13 @@ import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{app
import code.userlocks.UserLocksProvider
import code.users.Users
import code.util.Helper
+import code.views.Views
import code.views.system.{AccountAccess, ViewDefinition}
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.dto.CustomerAndAttribute
import com.openbankproject.commons.model.enums.{AtmAttributeType, UserAttributeType}
-import com.openbankproject.commons.model.{AtmId, AtmT, BankId}
+import com.openbankproject.commons.model.{AtmId, AtmT, BankId, Permission}
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
import net.liftweb.common.{Box, Full}
import net.liftweb.http.S
@@ -110,13 +114,14 @@ trait APIMethods510 {
lazy val waitingForGodot: OBPEndpoint = {
case "waiting-for-godot" :: Nil JsonGet _ => {
cc =>
+ implicit val timeout = EndpointTimeout(Constant.mediumEndpointTimeoutInMillis) // Set endpoint timeout explicitly
for {
httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url)
- } yield {
- val sleep: String = httpParams.filter(_.name == "sleep").headOption
+ sleep: String = httpParams.filter(_.name == "sleep").headOption
.map(_.values.headOption.getOrElse("0")).getOrElse("0")
- val sleepInMillis: Long = tryo(sleep.trim.toLong).getOrElse(0)
- Thread.sleep(sleepInMillis)
+ sleepInMillis: Long = tryo(sleep.trim.toLong).getOrElse(0)
+ _ <- Future(Thread.sleep(sleepInMillis))
+ } yield {
(JSONFactory510.waitingForGodot(sleepInMillis), HttpCode.`200`(cc.callContext))
}
}
@@ -285,6 +290,43 @@ trait APIMethods510 {
}
}
}
+
+
+
+ staticResourceDocs += ResourceDoc(
+ getEntitlementsAndPermissions,
+ implementedInApiVersion,
+ "getEntitlementsAndPermissions",
+ "GET",
+ "/users/USER_ID/entitlements-and-permissions",
+ "Get Entitlements and Permissions for a User",
+ s"""
+ |
+ |
+ """.stripMargin,
+ EmptyBody,
+ userJsonV300,
+ List(
+ $UserNotLoggedIn,
+ UserNotFoundByUserId,
+ UserHasMissingRoles,
+ UnknownError),
+ List(apiTagRole, apiTagEntitlement, apiTagUser),
+ Some(List(canGetEntitlementsForAnyUserAtAnyBank)))
+
+
+ lazy val getEntitlementsAndPermissions: OBPEndpoint = {
+ case "users" :: userId :: "entitlements-and-permissions" :: Nil JsonGet _ => {
+ cc =>
+ for {
+ (user, callContext) <- NewStyle.function.getUserByUserId(userId, cc.callContext)
+ entitlements <- NewStyle.function.getEntitlementsByUserId(userId, callContext)
+ } yield {
+ val permissions: Option[Permission] = Views.views.vend.getPermissionForUser(user).toOption
+ (JSONFactory300.createUserInfoJSON (user, entitlements, permissions), HttpCode.`200`(callContext))
+ }
+ }
+ }
staticResourceDocs += ResourceDoc(
diff --git a/obp-api/src/main/scala/code/metrics/MappedMetrics.scala b/obp-api/src/main/scala/code/metrics/MappedMetrics.scala
index 333ec61a8d..7395907da0 100644
--- a/obp-api/src/main/scala/code/metrics/MappedMetrics.scala
+++ b/obp-api/src/main/scala/code/metrics/MappedMetrics.scala
@@ -4,22 +4,18 @@ import java.sql.{PreparedStatement, Timestamp}
import java.util.Date
import java.util.UUID.randomUUID
-import code.api.Constant
import code.api.cache.Caching
import code.api.util._
import code.model.MappedConsumersProvider
import code.util.Helper.MdcLoggable
import code.util.{MappedUUID, UUIDString}
import com.openbankproject.commons.ExecutionContext.Implicits.global
-import com.openbankproject.commons.util.ApiVersion
import com.tesobe.CacheKeyFromArguments
import net.liftweb.common.Box
+import net.liftweb.db.DB
import net.liftweb.mapper.{Index, _}
import net.liftweb.util.Helpers.tryo
import org.apache.commons.lang3.StringUtils
-import scalikejdbc.{ConnectionPool, ConnectionPoolSettings, MultipleConnectionPoolContext}
-import scalikejdbc.DB.CPContext
-import scalikejdbc.{DB => scalikeDB, _}
import scala.collection.immutable
import scala.collection.immutable.List
@@ -90,8 +86,16 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
metric.save
}
- private def trueOrFalse(condition: Boolean) = if (condition) sqls"1=1" else sqls"0=1"
- private def falseOrTrue(condition: Boolean) = if (condition) sqls"0=1" else sqls"1=1"
+ private def trueOrFalse(condition: Boolean): String = if (condition) s"1=1" else s"0=1"
+ private def falseOrTrue(condition: Boolean): String = if (condition) s"0=1" else s"1=1"
+
+ private def sqlFriendly(value : Option[String]): String = {
+ value.isDefined match {
+ case true => s"'$value'"
+ case false => "null"
+
+ }
+ }
// override def getAllGroupedByUserId(): Map[String, List[APIMetric]] = {
// //TODO: do this all at the db level using an actual group by query
@@ -196,25 +200,25 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
}
}
}
-
- private def extendLikeQuery(params: List[String], isLike: Boolean) = {
- val isLikeQuery = if (isLike) sqls"" else sqls"NOT"
+
+ private def extendLikeQuery(params: List[String], isLike: Boolean): String = {
+ val isLikeQuery = if (isLike) s"" else s"NOT"
if (params.length == 1)
- sqls"${params.head}"
+ s"'${params.head}'"
else
{
- val sqlList: immutable.Seq[SQLSyntax] = for (i <- 1 to (params.length - 2)) yield
+ val sqlList: immutable.Seq[String] = for (i <- 1 to (params.length - 2)) yield
{
- sqls" and url ${isLikeQuery} LIKE ('${params(i)}')"
+ s" and url ${isLikeQuery} LIKE ('${params(i)}')"
}
val sqlSingleLine = if (sqlList.length>1)
sqlList.reduce(_+_)
else
- sqls""
+ s""
- sqls"${params.head})"+ sqlSingleLine + sqls" and url ${isLikeQuery} LIKE (${params.last}"
+ s"${params.head})"+ sqlSingleLine + s" and url ${isLikeQuery} LIKE ('${params.last}''"
}
}
@@ -231,24 +235,7 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
stmt.setString(startLine+i, excludeFiledValues.toList(i))
}
}
-
- /**
- * this connection pool context corresponding db.url in default.props
- */
- implicit lazy val context: CPContext = {
- val settings = ConnectionPoolSettings(
- initialSize = 5,
- maxSize = 20,
- connectionTimeoutMillis = 3000L,
- validationQuery = "select 1",
- connectionPoolFactoryName = "commons-dbcp2"
- )
- val (dbUrl, user, password) = DBUtil.getDbConnectionParameters
- val dbName = "DB_NAME" // corresponding props db.url DB
- ConnectionPool.add(dbName, dbUrl, user, password, settings)
- val connectionPool = ConnectionPool.get(dbName)
- MultipleConnectionPoolContext(ConnectionPool.DEFAULT_NAME -> connectionPool)
- }
+
// TODO Cache this as long as fromDate and toDate are in the past (before now)
def getAllAggregateMetricsBox(queryParams: List[OBPQueryParam], isNewVersion: Boolean): Box[List[AggregateMetrics]] = {
@@ -280,68 +267,71 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
val includeImplementedByPartialFunctions = queryParams.collect { case OBPIncludeImplementedByPartialFunctions(value) => value }.headOption
val excludeUrlPatternsList= excludeUrlPatterns.getOrElse(List(""))
- val excludeAppNamesList = excludeAppNames.getOrElse(List(""))
- val excludeImplementedByPartialFunctionsList = excludeImplementedByPartialFunctions.getOrElse(List(""))
+ val excludeAppNamesList = excludeAppNames.getOrElse(List("")).map(i => s"'$i'").mkString(",")
+ val excludeImplementedByPartialFunctionsList =
+ excludeImplementedByPartialFunctions.getOrElse(List("")).map(i => s"'$i'").mkString(",")
val excludeUrlPatternsQueries = extendLikeQuery(excludeUrlPatternsList, false)
val includeUrlPatternsList= includeUrlPatterns.getOrElse(List(""))
- val includeAppNamesList = includeAppNames.getOrElse(List(""))
- val includeImplementedByPartialFunctionsList = includeImplementedByPartialFunctions.getOrElse(List(""))
+ val includeAppNamesList = includeAppNames.getOrElse(List("")).map(i => s"'$i'").mkString(",")
+ val includeImplementedByPartialFunctionsList =
+ includeImplementedByPartialFunctions.getOrElse(List("")).map(i => s"'$i'").mkString(",")
val includeUrlPatternsQueries = extendLikeQuery(includeUrlPatternsList, true)
- val includeUrlPatternsQueriesSql = sqls"$includeUrlPatternsQueries"
+ val includeUrlPatternsQueriesSql = s"$includeUrlPatternsQueries"
- val result = scalikeDB readOnly { implicit session =>
+ val result = {
val sqlQuery = if(isNewVersion) // in the version, we use includeXxx instead of excludeXxx, the performance should be better.
- sql"""SELECT count(*), avg(duration), min(duration), max(duration)
+ s"""SELECT count(*), avg(duration), min(duration), max(duration)
FROM metric
- WHERE date_c >= ${new Timestamp(fromDate.get.getTime)}
- AND date_c <= ${new Timestamp(toDate.get.getTime)}
- AND (${trueOrFalse(consumerId.isEmpty)} or consumerid = ${consumerId.getOrElse("")})
- AND (${trueOrFalse(userId.isEmpty)} or userid = ${userId.getOrElse("")})
- AND (${trueOrFalse(implementedByPartialFunction.isEmpty)} or implementedbypartialfunction = ${implementedByPartialFunction.getOrElse("")})
- AND (${trueOrFalse(implementedInVersion.isEmpty)} or implementedinversion = ${implementedInVersion.getOrElse("")})
- AND (${trueOrFalse(url.isEmpty)} or url = ${url.getOrElse("")})
- AND (${trueOrFalse(appName.isEmpty)} or appname = ${appName.getOrElse("")})
- AND (${trueOrFalse(verb.isEmpty)} or verb = ${verb.getOrElse("")})
+ WHERE date_c >= '${new Timestamp(fromDate.get.getTime)}'
+ AND date_c <= '${new Timestamp(toDate.get.getTime)}'
+ AND (${trueOrFalse(consumerId.isEmpty)} or consumerid = ${sqlFriendly(consumerId)})
+ AND (${trueOrFalse(userId.isEmpty)} or userid = ${sqlFriendly(userId)})
+ AND (${trueOrFalse(implementedByPartialFunction.isEmpty)} or implementedbypartialfunction = ${sqlFriendly(implementedByPartialFunction)})
+ AND (${trueOrFalse(implementedInVersion.isEmpty)} or implementedinversion = ${sqlFriendly(implementedInVersion)})
+ AND (${trueOrFalse(url.isEmpty)} or url = ${sqlFriendly(url)})
+ AND (${trueOrFalse(appName.isEmpty)} or appname = ${sqlFriendly(appName)})
+ AND (${trueOrFalse(verb.isEmpty)} or verb = ${sqlFriendly(verb)})
AND (${falseOrTrue(anon.isDefined && anon.equals(Some(true)))} or userid = 'null')
AND (${falseOrTrue(anon.isDefined && anon.equals(Some(false)))} or userid != 'null')
- AND (${trueOrFalse(correlationId.isEmpty)} or correlationId = ${correlationId.getOrElse("")})
+ AND (${trueOrFalse(correlationId.isEmpty)} or correlationId = ${sqlFriendly(correlationId)})
AND (${trueOrFalse(includeUrlPatterns.isEmpty) } or (url LIKE ($includeUrlPatternsQueriesSql)))
AND (${trueOrFalse(includeAppNames.isEmpty) } or (appname in ($includeAppNamesList)))
AND (${trueOrFalse(includeImplementedByPartialFunctions.isEmpty) } or implementedbypartialfunction in ($includeImplementedByPartialFunctionsList))
""".stripMargin
else
- sql"""SELECT count(*), avg(duration), min(duration), max(duration)
+ s"""SELECT count(*), avg(duration), min(duration), max(duration)
FROM metric
- WHERE date_c >= ${new Timestamp(fromDate.get.getTime)}
- AND date_c <= ${new Timestamp(toDate.get.getTime)}
- AND (${trueOrFalse(consumerId.isEmpty)} or consumerid = ${consumerId.getOrElse("")})
- AND (${trueOrFalse(userId.isEmpty)} or userid = ${userId.getOrElse("")})
- AND (${trueOrFalse(implementedByPartialFunction.isEmpty)} or implementedbypartialfunction = ${implementedByPartialFunction.getOrElse("")})
- AND (${trueOrFalse(implementedInVersion.isEmpty)} or implementedinversion = ${implementedInVersion.getOrElse("")})
- AND (${trueOrFalse(url.isEmpty)} or url = ${url.getOrElse("")})
- AND (${trueOrFalse(appName.isEmpty)} or appname = ${appName.getOrElse("")})
- AND (${trueOrFalse(verb.isEmpty)} or verb = ${verb.getOrElse("")})
+ WHERE date_c >= '${new Timestamp(fromDate.get.getTime)}'
+ AND date_c <= '${new Timestamp(toDate.get.getTime)}'
+ AND (${trueOrFalse(consumerId.isEmpty)} or consumerid = ${sqlFriendly(consumerId)})
+ AND (${trueOrFalse(userId.isEmpty)} or userid = ${sqlFriendly(userId)})
+ AND (${trueOrFalse(implementedByPartialFunction.isEmpty)} or implementedbypartialfunction = ${sqlFriendly(implementedByPartialFunction)})
+ AND (${trueOrFalse(implementedInVersion.isEmpty)} or implementedinversion = ${sqlFriendly(implementedInVersion)})
+ AND (${trueOrFalse(url.isEmpty)} or url = ${sqlFriendly(url)})
+ AND (${trueOrFalse(appName.isEmpty)} or appname = ${sqlFriendly(appName)})
+ AND (${trueOrFalse(verb.isEmpty)} or verb = ${sqlFriendly(verb)})
AND (${falseOrTrue(anon.isDefined && anon.equals(Some(true)))} or userid = 'null')
AND (${falseOrTrue(anon.isDefined && anon.equals(Some(false)))} or userid != 'null')
- AND (${trueOrFalse(correlationId.isEmpty)} or correlationId = ${correlationId.getOrElse("")})
+ AND (${trueOrFalse(correlationId.isEmpty)} or correlationId = ${sqlFriendly(correlationId)})
AND (${trueOrFalse(excludeUrlPatterns.isEmpty) } or (url NOT LIKE ($excludeUrlPatternsQueries)))
AND (${trueOrFalse(excludeAppNames.isEmpty) } or appname not in ($excludeAppNamesList))
AND (${trueOrFalse(excludeImplementedByPartialFunctions.isEmpty) } or implementedbypartialfunction not in ($excludeImplementedByPartialFunctionsList))
""".stripMargin
- logger.debug("code.metrics.MappedMetrics.getAllAggregateMetricsBox.sqlQuery --: " +sqlQuery.statement)
- val sqlResult = sqlQuery.map(
+ val (_, rows) = DB.runQuery(sqlQuery, List())
+ logger.debug("code.metrics.MappedMetrics.getAllAggregateMetricsBox.sqlQuery --: " + sqlQuery)
+ val sqlResult = rows.map(
rs => // Map result to case class
AggregateMetrics(
- rs.stringOpt(1).map(_.toInt).getOrElse(0),
- rs.stringOpt(2).map(avg => "%.2f".format(avg.toDouble).toDouble).getOrElse(0),
- rs.stringOpt(3).map(_.toDouble).getOrElse(0),
- rs.stringOpt(4).map(_.toDouble).getOrElse(0)
+ tryo(rs(0).toInt).getOrElse(0),
+ tryo("%.2f".format(rs(1).toDouble).toDouble).getOrElse(0),
+ tryo(rs(2).toDouble).getOrElse(0),
+ tryo(rs(3).toDouble).getOrElse(0)
)
- ).list().apply()
- logger.debug("code.metrics.MappedMetrics.getAllAggregateMetricsBox.sqlResult --: "+sqlResult.toString)
+ )
+ logger.debug("code.metrics.MappedMetrics.getAllAggregateMetricsBox.sqlResult --: " + sqlResult)
sqlResult
}
tryo(result)
@@ -373,7 +363,7 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
val userId = queryParams.collect { case OBPUserId(value) => value }.headOption
val url = queryParams.collect { case OBPUrl(value) => value }.headOption
val appName = queryParams.collect { case OBPAppName(value) => value }.headOption
- val excludeAppNames = queryParams.collect { case OBPExcludeAppNames(value) => value }.headOption
+ val excludeAppNames: Option[List[String]] = queryParams.collect { case OBPExcludeAppNames(value) => value }.headOption
val implementedByPartialFunction = queryParams.collect { case OBPImplementedByPartialFunction(value) => value }.headOption
val implementedInVersion = queryParams.collect { case OBPImplementedInVersion(value) => value }.headOption
val verb = queryParams.collect { case OBPVerb(value) => value }.headOption
@@ -385,47 +375,51 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
val limit = queryParams.collect { case OBPLimit(value) => value }.headOption.getOrElse(10)
val excludeUrlPatternsList= excludeUrlPatterns.getOrElse(List(""))
- val excludeAppNamesNumberList = excludeAppNames.getOrElse(List(""))
- val excludeImplementedByPartialFunctionsNumberList = excludeImplementedByPartialFunctions.getOrElse(List(""))
+ val excludeAppNamesNumberList = excludeAppNames.getOrElse(List("")).map(i => s"'$i'").mkString(",")
+ val excludeImplementedByPartialFunctionsNumberList =
+ excludeImplementedByPartialFunctions.getOrElse(List("")).map(i => s"'$i'").mkString(",")
- val excludeUrlPatternsQueries = extendLikeQuery(excludeUrlPatternsList, false)
+ val excludeUrlPatternsQueries: String = extendLikeQuery(excludeUrlPatternsList, false)
val (dbUrl, _, _) = DBUtil.getDbConnectionParameters
- val result: List[TopApi] = scalikeDB readOnly { implicit session =>
+ val result: List[TopApi] = {
// MS SQL server has the specific syntax for limiting number of rows
- val msSqlLimit = if (dbUrl.contains("sqlserver")) sqls"TOP ($limit)" else sqls""
+ val msSqlLimit = if (dbUrl.contains("sqlserver")) s"TOP ($limit)" else s""
// TODO Make it work in case of Oracle database
- val otherDbLimit = if (dbUrl.contains("sqlserver")) sqls"" else sqls"LIMIT $limit"
- val sqlResult =
- sql"""SELECT ${msSqlLimit} count(*), metric.implementedbypartialfunction, metric.implementedinversion
+ val otherDbLimit = if (dbUrl.contains("sqlserver")) s"" else s"LIMIT $limit"
+ val sqlQuery: String =
+ s"""SELECT ${msSqlLimit} count(*), metric.implementedbypartialfunction, metric.implementedinversion
FROM metric
WHERE
- date_c >= ${new Timestamp(fromDate.get.getTime)} AND
- date_c <= ${new Timestamp(toDate.get.getTime)}
- AND (${trueOrFalse(consumerId.isEmpty)} or consumerid = ${consumerId.getOrElse("")})
- AND (${trueOrFalse(userId.isEmpty)} or userid = ${userId.getOrElse("")})
- AND (${trueOrFalse(implementedByPartialFunction.isEmpty)} or implementedbypartialfunction = ${implementedByPartialFunction.getOrElse("")})
- AND (${trueOrFalse(implementedInVersion.isEmpty)} or implementedinversion = ${implementedInVersion.getOrElse("")})
- AND (${trueOrFalse(url.isEmpty)} or url = ${url.getOrElse("")})
- AND (${trueOrFalse(appName.isEmpty)} or appname = ${appName.getOrElse("")})
- AND (${trueOrFalse(verb.isEmpty)} or verb = ${verb.getOrElse("")})
- AND (${falseOrTrue(anon.isDefined && anon.equals(Some(true)))} or userid = 'null')
- AND (${falseOrTrue(anon.isDefined && anon.equals(Some(false)))} or userid != 'null')
- AND (${trueOrFalse(excludeUrlPatterns.isEmpty) } or (url NOT LIKE ($excludeUrlPatternsQueries)))
- AND (${trueOrFalse(excludeAppNames.isEmpty) } or appname not in ($excludeAppNamesNumberList))
- AND (${trueOrFalse(excludeImplementedByPartialFunctions.isEmpty) } or implementedbypartialfunction not in ($excludeImplementedByPartialFunctionsNumberList))
+ date_c >= '${new Timestamp(fromDate.get.getTime)}' AND
+ date_c <= '${new Timestamp(toDate.get.getTime)}'
+ AND (${trueOrFalse(consumerId.isEmpty)} or consumerid = ${consumerId.getOrElse("null")})
+ AND (${trueOrFalse(userId.isEmpty)} or userid = ${userId.getOrElse("null")})
+ AND (${trueOrFalse(implementedByPartialFunction.isEmpty)} or implementedbypartialfunction = ${implementedByPartialFunction.getOrElse("null")})
+ AND (${trueOrFalse(implementedInVersion.isEmpty)} or implementedinversion = ${implementedInVersion.getOrElse("null")})
+ AND (${trueOrFalse(url.isEmpty)} or url = ${url.getOrElse("null")})
+ AND (${trueOrFalse(appName.isEmpty)} or appname = ${appName.getOrElse("null")})
+ AND (${trueOrFalse(verb.isEmpty)} or verb = ${verb.getOrElse("null")})
+ AND (${falseOrTrue(anon.isDefined && anon.equals(Some(true)))} or userid = null)
+ AND (${falseOrTrue(anon.isDefined && anon.equals(Some(false)))} or userid != null)
+ AND (${trueOrFalse(excludeUrlPatterns.isEmpty)} or (url NOT LIKE ($excludeUrlPatternsQueries)))
+ AND (${trueOrFalse(excludeAppNames.isEmpty)} or appname not in ($excludeAppNamesNumberList))
+ AND (${trueOrFalse(excludeImplementedByPartialFunctions.isEmpty)} or implementedbypartialfunction not in ($excludeImplementedByPartialFunctionsNumberList))
GROUP BY metric.implementedbypartialfunction, metric.implementedinversion
ORDER BY count(*) DESC
${otherDbLimit}
""".stripMargin
- .map(
- rs => // Map result to case class
- TopApi(
- rs.string(1).toInt,
- rs.string(2),
- rs.string(3))
- ).list.apply()
+
+ val (_, rows) = DB.runQuery(sqlQuery, List())
+ val sqlResult =
+ rows.map { rs => // Map result to case class
+ TopApi(
+ rs(0).toInt,
+ rs(1),
+ rs(2)
+ )
+ }
sqlResult
}
tryo(result)
@@ -458,38 +452,39 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
val duration = queryParams.collect { case OBPDuration(value) => value }.headOption
val excludeUrlPatterns = queryParams.collect { case OBPExcludeUrlPatterns(value) => value }.headOption
val excludeImplementedByPartialFunctions = queryParams.collect { case OBPExcludeImplementedByPartialFunctions(value) => value }.headOption
- val limit = queryParams.collect { case OBPLimit(value) => value }.headOption
+ val limit = queryParams.collect { case OBPLimit(value) => value }.headOption.getOrElse("500")
val excludeUrlPatternsList = excludeUrlPatterns.getOrElse(List(""))
- val excludeAppNamesList = excludeAppNames.getOrElse(List(""))
- val excludeImplementedByPartialFunctionsList = excludeImplementedByPartialFunctions.getOrElse(List(""))
+ val excludeAppNamesList = excludeAppNames.getOrElse(List("")).map(i => s"'$i'").mkString(",")
+ val excludeImplementedByPartialFunctionsList =
+ excludeImplementedByPartialFunctions.getOrElse(List("")).map(i => s"'$i'").mkString(",")
- val excludeUrlPatternsQueries = extendLikeQuery(excludeUrlPatternsList, false)
+ val excludeUrlPatternsQueries: String = extendLikeQuery(excludeUrlPatternsList, false)
val (dbUrl, _, _) = DBUtil.getDbConnectionParameters
// MS SQL server has the specific syntax for limiting number of rows
- val msSqlLimit = if (dbUrl.contains("sqlserver")) sqls"TOP ($limit)" else sqls""
+ val msSqlLimit = if (dbUrl.contains("sqlserver")) s"TOP ($limit)" else s""
// TODO Make it work in case of Oracle database
- val otherDbLimit = if (dbUrl.contains("sqlserver")) sqls"" else sqls"LIMIT $limit"
+ val otherDbLimit: String = if (dbUrl.contains("sqlserver")) s"" else s"LIMIT $limit"
- val result: List[TopConsumer] = scalikeDB readOnly { implicit session =>
- val sqlResult =
- sql"""SELECT ${msSqlLimit} count(*) as count, consumer.id as consumerprimaryid, metric.appname as appname,
+ val result: List[TopConsumer] = {
+ val sqlQuery =
+ s"""SELECT ${msSqlLimit} count(*) as count, consumer.id as consumerprimaryid, metric.appname as appname,
consumer.developeremail as email, consumer.consumerid as consumerid
FROM metric, consumer
WHERE metric.appname = consumer.name
- AND date_c >= ${new Timestamp(fromDate.get.getTime)}
- AND date_c <= ${new Timestamp(toDate.get.getTime)}
- AND (${trueOrFalse(consumerId.isEmpty)} or consumer.consumerid = ${consumerId.getOrElse("")})
- AND (${trueOrFalse(userId.isEmpty)} or userid = ${userId.getOrElse("")})
- AND (${trueOrFalse(implementedByPartialFunction.isEmpty)} or implementedbypartialfunction = ${implementedByPartialFunction.getOrElse("")})
- AND (${trueOrFalse(implementedInVersion.isEmpty)} or implementedinversion = ${implementedInVersion.getOrElse("")})
- AND (${trueOrFalse(url.isEmpty)} or url = ${url.getOrElse("")})
- AND (${trueOrFalse(appName.isEmpty)} or appname = ${appName.getOrElse("")})
- AND (${trueOrFalse(verb.isEmpty)} or verb = ${verb.getOrElse("")})
- AND (${falseOrTrue(anon.isDefined && anon.equals(Some(true)))} or userid = 'null')
- AND (${falseOrTrue(anon.isDefined && anon.equals(Some(false)))} or userid != 'null')
+ AND date_c >= '${new Timestamp(fromDate.get.getTime)}'
+ AND date_c <= '${new Timestamp(toDate.get.getTime)}'
+ AND (${trueOrFalse(consumerId.isEmpty)} or consumer.consumerid = ${sqlFriendly(consumerId)})
+ AND (${trueOrFalse(userId.isEmpty)} or userid = ${sqlFriendly(userId)})
+ AND (${trueOrFalse(implementedByPartialFunction.isEmpty)} or implementedbypartialfunction = ${sqlFriendly(implementedByPartialFunction)})
+ AND (${trueOrFalse(implementedInVersion.isEmpty)} or implementedinversion = ${sqlFriendly(implementedInVersion)})
+ AND (${trueOrFalse(url.isEmpty)} or url = ${sqlFriendly(url)})
+ AND (${trueOrFalse(appName.isEmpty)} or appname = ${sqlFriendly(appName)})
+ AND (${trueOrFalse(verb.isEmpty)} or verb = ${sqlFriendly(verb)})
+ AND (${falseOrTrue(anon.isDefined && anon.equals(Some(true)))} or userid = null)
+ AND (${falseOrTrue(anon.isDefined && anon.equals(Some(false)))} or userid != null)
AND (${trueOrFalse(excludeUrlPatterns.isEmpty) } or (url NOT LIKE ($excludeUrlPatternsQueries)))
AND (${trueOrFalse(excludeAppNames.isEmpty) } or appname not in ($excludeAppNamesList))
AND (${trueOrFalse(excludeImplementedByPartialFunctions.isEmpty) } or implementedbypartialfunction not in ($excludeImplementedByPartialFunctionsList))
@@ -497,14 +492,16 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
ORDER BY count DESC
${otherDbLimit}
""".stripMargin
- .map(
- rs =>
- TopConsumer(
- rs.string(1).toInt,
- rs.string(5),
- rs.string(3),
- rs.string(4))
- ).list.apply()
+ val (_, rows) = DB.runQuery(sqlQuery, List())
+ val sqlResult =
+ rows.map { rs => // Map result to case class
+ TopConsumer(
+ rs(0).toInt,
+ rs(4),
+ rs(2),
+ rs(3)
+ )
+ }
sqlResult
}
tryo(result)
diff --git a/obp-api/src/main/scala/code/scheduler/DatabaseDriverScheduler.scala b/obp-api/src/main/scala/code/scheduler/DatabaseDriverScheduler.scala
index 3be284c9c0..c31fe50868 100644
--- a/obp-api/src/main/scala/code/scheduler/DatabaseDriverScheduler.scala
+++ b/obp-api/src/main/scala/code/scheduler/DatabaseDriverScheduler.scala
@@ -5,7 +5,7 @@ import java.util.concurrent.TimeUnit
import code.actorsystem.ObpLookupSystem
import code.util.Helper.MdcLoggable
-import net.liftweb.db.DB
+import net.liftweb.db.{DB, SuperConnection}
import scala.concurrent.duration._
@@ -25,11 +25,26 @@ object DatabaseDriverScheduler extends MdcLoggable {
}
)
}
+
+ def logWarnings(conn: SuperConnection) = {
+ var warning = conn.getWarnings()
+ if (warning != null) {
+ logger.warn("---Warning---")
+ while (warning != null)
+ {
+ logger.warn("Message: " + warning.getMessage())
+ logger.warn("SQLState: " + warning.getSQLState())
+ logger.warn("Vendor error code: " + warning.getErrorCode())
+ warning = warning.getNextWarning()
+ }
+ }
+ }
def clearAllMessages() = {
DB.use(net.liftweb.util.DefaultConnectionIdentifier) {
conn =>
try {
+ logWarnings(conn)
conn.clearWarnings()
logger.warn("DatabaseDriverScheduler.clearAllMessages - DONE")
} catch {
diff --git a/obp-api/src/test/scala/code/api/v5_1_0/UserTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/UserTest.scala
index 6a9d1c702b..44c380ac02 100644
--- a/obp-api/src/test/scala/code/api/v5_1_0/UserTest.scala
+++ b/obp-api/src/test/scala/code/api/v5_1_0/UserTest.scala
@@ -1,10 +1,12 @@
package code.api.v5_1_0
+import java.util.UUID
+
import code.api.util.APIUtil.OAuth._
-import code.api.util.ApiRole.CanGetAnyUser
+import code.api.util.ApiRole.{CanGetAnyUser, CanGetEntitlementsForAnyUserAtAnyBank}
import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn, attemptedToOpenAnEmptyBox}
-import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0
-import code.api.v4_0_0.{UserIdJsonV400, UserJsonV400}
+import code.api.v3_0_0.UserJsonV300
+import code.api.v4_0_0.UserJsonV400
import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0
import code.entitlement.Entitlement
import code.model.UserX
@@ -14,8 +16,6 @@ import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.ApiVersion
import org.scalatest.Tag
-import java.util.UUID
-
class UserTest extends V510ServerSetup {
/**
* Test tags
@@ -26,6 +26,7 @@ class UserTest extends V510ServerSetup {
*/
object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString)
object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.getUserByProviderAndUsername))
+ object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.getEntitlementsAndPermissions))
feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) {
@@ -62,5 +63,46 @@ class UserTest extends V510ServerSetup {
Users.users.vend.deleteResourceUser(user.id.get)
}
}
+
+
+
+ feature(s"test $ApiEndpoint2 version $VersionOfApi - Unauthorized access") {
+ scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) {
+ When("We make a request v5.1.0")
+ val request = (v5_1_0_Request / "users" / "USER_ID" / "entitlements-and-permissions").GET
+ val response = makeGetRequest(request)
+ Then("We should get a 401")
+ response.code should equal(401)
+ response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
+ }
+ }
+ feature(s"test $ApiEndpoint2 version $VersionOfApi - Authorized access") {
+ scenario("We will call the endpoint with user credentials but without a proper entitlement", ApiEndpoint1, VersionOfApi) {
+ val user = UserX.createResourceUser(defaultProvider, Some("user.name.1"), None, Some("user.name.1"), None, Some(UUID.randomUUID.toString), None).openOrThrowException(attemptedToOpenAnEmptyBox)
+ When("We make a request v5.1.0")
+ val request = (v5_1_0_Request / "users" / user.userId / "entitlements-and-permissions").GET <@(user1)
+ val response = makeGetRequest(request)
+ Then("error should be " + UserHasMissingRoles + CanGetEntitlementsForAnyUserAtAnyBank)
+ response.code should equal(403)
+ response.body.extract[ErrorMessage].message should be (UserHasMissingRoles + CanGetEntitlementsForAnyUserAtAnyBank)
+ // Clean up
+ Users.users.vend.deleteResourceUser(user.id.get)
+ }
+ }
+ feature(s"test $ApiEndpoint2 version $VersionOfApi - Authorized access") {
+ scenario("We will call the endpoint with user credentials and a proper entitlement", ApiEndpoint1, VersionOfApi) {
+ Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetEntitlementsForAnyUserAtAnyBank.toString)
+ val user = UserX.createResourceUser(defaultProvider, Some("user.name.1"), None, Some("user.name.1"), None, Some(UUID.randomUUID.toString), None).openOrThrowException(attemptedToOpenAnEmptyBox)
+ When("We make a request v5.1.0")
+ val request = (v5_1_0_Request / "users" / user.userId / "entitlements-and-permissions").GET <@(user1)
+ val response = makeGetRequest(request)
+ Then("We get successful response")
+ response.code should equal(200)
+ response.body.extract[UserJsonV300]
+ // Clean up
+ Users.users.vend.deleteResourceUser(user.id.get)
+ }
+ }
+
}