controllers.adminapi.ApiKeysController.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otoroshi_2.12 Show documentation
Show all versions of otoroshi_2.12 Show documentation
Lightweight api management on top of a modern http reverse proxy
The newest version!
package otoroshi.controllers.adminapi
import otoroshi.actions.ApiAction
import otoroshi.env.Env
import otoroshi.models.ApiKey
import otoroshi.utils.controllers.{
AdminApiHelper,
ApiError,
BulkControllerHelper,
CrudControllerHelper,
EntityAndContext,
JsonApiError,
NoEntityAndContext,
OptionalEntityAndContext,
SeqEntityAndContext
}
import otoroshi.utils.syntax.implicits._
import play.api.Logger
import play.api.libs.json._
import play.api.mvc.{AbstractController, ControllerComponents, RequestHeader, Results}
import otoroshi.security.IdGenerator
import otoroshi.utils.json.JsonPatchHelpers.patchJson
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
class ApiKeysFromServiceController(val ApiAction: ApiAction, val cc: ControllerComponents)(implicit val env: Env)
extends AbstractController(cc)
with AdminApiHelper {
implicit lazy val ec = env.otoroshiExecutionContext
implicit lazy val mat = env.otoroshiMaterializer
lazy val logger = Logger("otoroshi-apikeys-fs-api")
def apiKeyQuotas(serviceId: String, clientId: String) =
ApiAction.async { ctx =>
env.datastores.serviceDescriptorDataStore.findById(serviceId).flatMap {
case None => NotFound(Json.obj("error" -> s"Service with id: '$serviceId' not found")).asFuture
case Some(desc) if !ctx.canUserRead(desc) => ctx.fforbidden
case Some(desc) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserRead(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnService(desc.id) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for service with id: '$serviceId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnService(desc.id) => {
sendAudit(
"ACCESS_SERVICE_APIKEY_QUOTAS",
s"User accessed an apikey quotas from a service descriptor",
Json.obj("serviceId" -> serviceId, "clientId" -> clientId),
ctx
)
apiKey.remainingQuotas().map(rq => Ok(rq.toJson))
}
}
}
}
def resetApiKeyQuotas(serviceId: String, clientId: String) =
ApiAction.async { ctx =>
env.datastores.serviceDescriptorDataStore.findById(serviceId).flatMap {
case None => NotFound(Json.obj("error" -> s"Service with id: '$serviceId' not found")).asFuture
case Some(desc) if !ctx.canUserWrite(desc) => ctx.fforbidden
case Some(desc) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for service with id: '$serviceId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) => {
sendAudit(
"RESET_SERVICE_APIKEY_QUOTAS",
s"User reset an apikey quotas for a service descriptor",
Json.obj("serviceId" -> serviceId, "clientId" -> clientId),
ctx
)
env.datastores.apiKeyDataStore.resetQuotas(apiKey).map(rq => Ok(rq.toJson))
}
}
}
}
def createApiKey(serviceId: String) =
ApiAction.async(parse.json) { ctx =>
val body: JsObject = ((ctx.request.body \ "clientId").asOpt[String] match {
case None => ctx.request.body.as[JsObject] ++ Json.obj("clientId" -> IdGenerator.lowerCaseToken(16))
case Some(b) => ctx.request.body.as[JsObject]
}) ++ ((ctx.request.body \ "clientSecret").asOpt[String] match {
case None => Json.obj("clientSecret" -> IdGenerator.lowerCaseToken(64))
case Some(b) => Json.obj()
})
env.datastores.serviceDescriptorDataStore.findById(serviceId).flatMap {
case None => NotFound(Json.obj("error" -> s"Service with id $serviceId not found")).asFuture
case Some(desc) if !ctx.canUserWrite(desc) => ctx.fforbidden
case Some(desc) => {
val oldGroup = (body \ "authorizedGroup").asOpt[String].map(g => "group_" + g).toSeq
val entities = (Seq("service_" + serviceId) ++ oldGroup).distinct
val apiKeyJson = ((body \ "authorizedEntities").asOpt[Seq[String]] match {
case None => body ++ Json.obj("authorizedEntities" -> Json.arr("service_" + serviceId))
case Some(sid) if !sid.contains(s"service_${serviceId}") =>
body ++ Json.obj("authorizedEntities" -> (entities ++ sid).distinct)
case Some(sid) if sid.contains(s"service_${serviceId}") => body
case Some(_) => body
}) - "authorizedGroup"
ApiKey.fromJsonSafe(apiKeyJson) match {
case JsError(e) => BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(apiKey, _) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case JsSuccess(apiKey, _) =>
env.datastores.apiKeyDataStore.findById(apiKey.clientId).flatMap {
case Some(_) => BadRequest(Json.obj("error" -> "Apikey already exists")).asFuture
case None => {
apiKey.save().map {
case false => InternalServerError(Json.obj("error" -> "ApiKey not stored ..."))
case true => {
sendAuditAndAlert(
"CREATE_APIKEY",
s"User created an ApiKey",
"ApiKeyCreatedAlert",
Json.obj(
"desc" -> desc.toJson,
"apikey" -> apiKey.toJson
),
ctx
)
env.datastores.apiKeyDataStore.addFastLookupByService(serviceId, apiKey).map { _ =>
env.datastores.apiKeyDataStore.findAll()
}
Created(apiKey.toJson)
}
}
}
}
}
}
}
}
def updateApiKey(serviceId: String, clientId: String) =
ApiAction.async(parse.json) { ctx =>
env.datastores.serviceDescriptorDataStore
.findById(serviceId)
.flatMap {
case None => NotFound(Json.obj("error" -> s"Service with id: '$serviceId' not found")).asFuture
case Some(desc) if !ctx.canUserWrite(desc) => ctx.fforbidden
case Some(desc) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for service with id: '$serviceId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) => {
ApiKey.fromJsonSafe(ctx.request.body) match {
case JsError(e) => BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(newApiKey, _) if newApiKey.clientId != clientId =>
BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(newApiKey, _) if newApiKey.clientId == clientId => {
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => BadRequest(Json.obj("error" -> "Apikey not found")).asFuture
case Some(oldApik) if !ctx.canUserWrite(oldApik) =>
BadRequest(Json.obj("error" -> "you cannot access this resource")).asFuture
case Some(_) => {
sendAuditAndAlert(
"UPDATE_APIKEY",
s"User updated an ApiKey",
"ApiKeyUpdatedAlert",
Json.obj(
"desc" -> desc.toJson,
"apikey" -> apiKey.toJson
),
ctx
)
newApiKey.save().map(_ => Ok(newApiKey.toJson))
}
}
}
}
}
}
}
}
def patchApiKey(serviceId: String, clientId: String) =
ApiAction.async(parse.json) { ctx =>
env.datastores.serviceDescriptorDataStore.findById(serviceId).flatMap {
case None => NotFound(Json.obj("error" -> s"Service with id: '$serviceId' not found")).asFuture
case Some(desc) if !ctx.canUserWrite(desc) => ctx.fforbidden
case Some(desc) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for service with id: '$serviceId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) => {
val currentApiKeyJson = apiKey.toJson
val newApiKeyJson = patchJson(ctx.request.body, currentApiKeyJson)
ApiKey.fromJsonSafe(newApiKeyJson) match {
case JsError(e) => BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(newApiKey, _) if newApiKey.clientId != clientId =>
BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(newApiKey, _) if newApiKey.clientId == clientId => {
sendAuditAndAlert(
"UPDATE_APIKEY",
s"User updated an ApiKey",
"ApiKeyUpdatedAlert",
Json.obj(
"desc" -> desc.toJson,
"apikey" -> apiKey.toJson
),
ctx
)
newApiKey.save().map(_ => Ok(newApiKey.toJson))
}
}
}
}
}
}
def deleteApiKey(serviceId: String, clientId: String) =
ApiAction.async { ctx =>
env.datastores.serviceDescriptorDataStore.findById(serviceId).flatMap {
case None => NotFound(Json.obj("error" -> s"Service with id: '$serviceId' not found")).asFuture
case Some(desc) if !ctx.canUserWrite(desc) => ctx.fforbidden
case Some(desc) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for service with id: '$serviceId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) => {
sendAuditAndAlert(
"DELETE_APIKEY",
s"User deleted an ApiKey",
"ApiKeyDeletedAlert",
Json.obj(
"desc" -> desc.toJson,
"apikey" -> apiKey.toJson
),
ctx
)
env.datastores.apiKeyDataStore.deleteFastLookupByService(serviceId, apiKey)
apiKey.delete().map(res => Ok(Json.obj("deleted" -> true)))
}
}
}
}
def apiKeys(serviceId: String) =
ApiAction.async { ctx =>
val paginationPage: Int = ctx.request.queryString.get("page").flatMap(_.headOption).map(_.toInt).getOrElse(1)
val paginationPageSize: Int =
ctx.request.queryString.get("pageSize").flatMap(_.headOption).map(_.toInt).getOrElse(Int.MaxValue)
val paginationPosition = (paginationPage - 1) * paginationPageSize
val clientId: Option[String] = ctx.request.queryString.get("clientId").flatMap(_.headOption)
val name: Option[String] = ctx.request.queryString.get("name").flatMap(_.headOption)
val group: Option[String] = ctx.request.queryString.get("group").flatMap(_.headOption)
val enabled: Option[String] = ctx.request.queryString.get("enabled").flatMap(_.headOption)
val hasFilters = clientId.orElse(name).orElse(group).orElse(name).orElse(enabled).isDefined
env.datastores.apiKeyDataStore.findByService(serviceId).fold {
case Failure(_) => NotFound(Json.obj("error" -> s"ApiKeys for service with id: '$serviceId' does not exist"))
case Success(apiKeys) => {
sendAudit(
"ACCESS_SERVICE_APIKEYS",
s"User accessed apikeys from a service descriptor",
Json.obj("serviceId" -> serviceId),
ctx
)
if (hasFilters) {
Ok(
JsArray(
apiKeys
.filter(ctx.canUserRead)
.filter {
case keys if group.isDefined && keys.authorizedOnGroup(group.get) => true
case keys if clientId.isDefined && keys.clientId == clientId.get => true
case keys if name.isDefined && keys.clientName == name.get => true
case keys if enabled.isDefined && keys.enabled == enabled.get.toBoolean => true
case _ => false
}
.drop(paginationPosition)
.take(paginationPageSize)
.map(_.toJson)
)
)
} else {
Ok(JsArray(apiKeys.filter(ctx.canUserRead).map(_.toJson)))
}
}
}
}
def allApiKeys() =
ApiAction.async { ctx =>
sendAudit(
"ACCESS_ALL_APIKEYS",
s"User accessed all apikeys",
Json.obj(),
ctx
)
val paginationPage: Int = ctx.request.queryString.get("page").flatMap(_.headOption).map(_.toInt).getOrElse(1)
val paginationPageSize: Int =
ctx.request.queryString.get("pageSize").flatMap(_.headOption).map(_.toInt).getOrElse(Int.MaxValue)
val paginationPosition = (paginationPage - 1) * paginationPageSize
val clientId: Option[String] = ctx.request.queryString.get("clientId").flatMap(_.headOption)
val name: Option[String] = ctx.request.queryString.get("name").flatMap(_.headOption)
val group: Option[String] = ctx.request.queryString.get("group").flatMap(_.headOption)
val enabled: Option[String] = ctx.request.queryString.get("enabled").flatMap(_.headOption)
val hasFilters = clientId.orElse(name).orElse(group).orElse(name).orElse(enabled).isDefined
env.datastores.apiKeyDataStore.streamedFindAndMat(_ => true, 50, paginationPage, paginationPageSize).map { keys =>
if (hasFilters) {
Ok(
JsArray(
keys
.filter(ctx.canUserRead)
.filter {
case keys if group.isDefined && keys.authorizedOnGroup(group.get) => true
case keys if clientId.isDefined && keys.clientId == clientId.get => true
case keys if name.isDefined && keys.clientName == name.get => true
case keys if enabled.isDefined && keys.enabled == enabled.get.toBoolean => true
case _ => false
}
.map(_.toJson)
)
)
} else {
Ok(JsArray(keys.filter(ctx.canUserRead).map(_.toJson)))
}
}
}
def apiKey(serviceId: String, clientId: String) =
ApiAction.async { ctx =>
env.datastores.serviceDescriptorDataStore.findById(serviceId).flatMap {
case None => NotFound(Json.obj("error" -> s"Service with id: '$serviceId' not found")).asFuture
case Some(desc) if !ctx.canUserRead(desc) => ctx.fforbidden
case Some(desc) =>
env.datastores.apiKeyDataStore.findById(clientId).map {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found"))
case Some(apiKey) if !ctx.canUserRead(apiKey) => ctx.forbidden
case Some(apiKey) if !apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for service with id: '$serviceId'")
)
case Some(apiKey) if apiKey.authorizedOnServiceOrGroups(desc.id, desc.groups) => {
sendAudit(
"ACCESS_SERVICE_APIKEY",
s"User accessed an apikey from a service descriptor",
Json.obj("serviceId" -> serviceId, "clientId" -> clientId),
ctx
)
Ok(apiKey.toJson)
}
}
}
}
}
class ApiKeysFromGroupController(val ApiAction: ApiAction, val cc: ControllerComponents)(implicit val env: Env)
extends AbstractController(cc)
with AdminApiHelper {
implicit lazy val ec = env.otoroshiExecutionContext
implicit lazy val mat = env.otoroshiMaterializer
lazy val logger = Logger("otoroshi-apikeys-fg-api")
def apiKeyFromGroupQuotas(groupId: String, clientId: String) =
ApiAction.async { ctx =>
env.datastores.serviceGroupDataStore.findById(groupId).flatMap {
case None => NotFound(Json.obj("error" -> s"Group with id: '$groupId' not found")).asFuture
case Some(group) if !ctx.canUserRead(group) => ctx.fforbidden
case Some(group) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserRead(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnGroup(group.id) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for group with id: '$groupId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnGroup(group.id) => {
sendAudit(
"ACCESS_SERVICE_APIKEY_QUOTAS",
s"User accessed an apikey quotas from a service descriptor",
Json.obj("groupId" -> groupId, "clientId" -> clientId),
ctx
)
apiKey.remainingQuotas().map(rq => Ok(rq.toJson))
}
}
}
}
def resetApiKeyFromGroupQuotas(groupId: String, clientId: String) =
ApiAction.async { ctx =>
env.datastores.serviceGroupDataStore.findById(groupId).flatMap {
case None => NotFound(Json.obj("error" -> s"Group with id: '$groupId' not found")).asFuture
case Some(group) if !ctx.canUserWrite(group) => ctx.fforbidden
case Some(group) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnGroup(group.id) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for group with id: '$groupId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnGroup(group.id) => {
sendAudit(
"RESET_SERVICE_APIKEY_QUOTAS",
s"User accessed an apikey quotas from a service descriptor",
Json.obj("groupId" -> groupId, "clientId" -> clientId),
ctx
)
env.datastores.apiKeyDataStore.resetQuotas(apiKey).map(rq => Ok(rq.toJson))
}
}
}
}
def apiKeysFromGroup(groupId: String) =
ApiAction.async { ctx =>
val paginationPage: Int = ctx.request.queryString.get("page").flatMap(_.headOption).map(_.toInt).getOrElse(1)
val paginationPageSize: Int =
ctx.request.queryString.get("pageSize").flatMap(_.headOption).map(_.toInt).getOrElse(Int.MaxValue)
val paginationPosition = (paginationPage - 1) * paginationPageSize
val clientId: Option[String] = ctx.request.queryString.get("clientId").flatMap(_.headOption)
val name: Option[String] = ctx.request.queryString.get("name").flatMap(_.headOption)
val group: Option[String] = ctx.request.queryString.get("group").flatMap(_.headOption)
val enabled: Option[String] = ctx.request.queryString.get("enabled").flatMap(_.headOption)
val hasFilters = clientId.orElse(name).orElse(group).orElse(name).orElse(enabled).isDefined
env.datastores.apiKeyDataStore.findByGroup(groupId).fold {
case Failure(_) => NotFound(Json.obj("error" -> s"ApiKeys for group with id: '$groupId' does not exist"))
case Success(apiKeys) => {
sendAudit(
"ACCESS_SERVICE_APIKEYS",
s"User accessed apikeys from a group",
Json.obj("groupId" -> groupId),
ctx
)
if (hasFilters) {
Ok(
JsArray(
apiKeys
.filter(ctx.canUserRead)
.filter {
case keys if group.isDefined && keys.authorizedOnGroup(group.get) => true
case keys if clientId.isDefined && keys.clientId == clientId.get => true
case keys if name.isDefined && keys.clientName == name.get => true
case keys if enabled.isDefined && keys.enabled == enabled.get.toBoolean => true
case _ => false
}
.drop(paginationPosition)
.take(paginationPageSize)
.map(_.toJson)
)
)
} else {
Ok(JsArray(apiKeys.filter(ctx.canUserRead).map(_.toJson)))
}
}
}
}
def apiKeyFromGroup(groupId: String, clientId: String) =
ApiAction.async { ctx =>
env.datastores.serviceGroupDataStore.findById(groupId).flatMap {
case None => NotFound(Json.obj("error" -> s"Group with id: '$groupId' not found")).asFuture
case Some(group) if !ctx.canUserRead(group) => ctx.fforbidden
case Some(group) =>
env.datastores.apiKeyDataStore.findById(clientId).map {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found"))
case Some(apiKey) if !ctx.canUserRead(apiKey) => ctx.forbidden
case Some(apiKey) if !apiKey.authorizedOnGroup(group.id) =>
NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for group with id: '$groupId'"))
case Some(apiKey) if apiKey.authorizedOnGroup(group.id) => {
sendAudit(
"ACCESS_SERVICE_APIKEY",
s"User accessed an apikey from a service descriptor",
Json.obj("groupId" -> groupId, "clientId" -> clientId),
ctx
)
Ok(apiKey.toJson)
}
}
}
}
def createApiKeyFromGroup(groupId: String) =
ApiAction.async(parse.json) { ctx =>
val body: JsObject = ((ctx.request.body \ "clientId").asOpt[String] match {
case None => ctx.request.body.as[JsObject] ++ Json.obj("clientId" -> IdGenerator.lowerCaseToken(16))
case Some(b) => ctx.request.body.as[JsObject]
}) ++ ((ctx.request.body \ "clientSecret").asOpt[String] match {
case None => Json.obj("clientSecret" -> IdGenerator.lowerCaseToken(64))
case Some(b) => Json.obj()
})
env.datastores.serviceGroupDataStore.findById(groupId).flatMap {
case None => NotFound(Json.obj("error" -> s"Service group not found")).asFuture
case Some(group) if !ctx.canUserWrite(group) => ctx.fforbidden
case Some(group) => {
val oldGroup = (body \ "authorizedGroup").asOpt[String].map(g => "group_" + g).toSeq
val entities = (Seq("group_" + group.id) ++ oldGroup).distinct
val apiKeyJson = ((body \ "authorizedEntities").asOpt[Seq[String]] match {
case None => body ++ Json.obj("authorizedEntities" -> Json.arr("group_" + group.id))
case Some(groupId) if !groupId.contains(s"group_${group.id}") =>
body ++ Json.obj("authorizedEntities" -> (entities ++ groupId).distinct)
case Some(groupId) if groupId.contains(s"group_${group.id}") => body
}) - "authorizedGroup"
ApiKey.fromJsonSafe(apiKeyJson) match {
case JsError(e) => BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(apiKey, _) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case JsSuccess(apiKey, _) =>
env.datastores.apiKeyDataStore.findById(apiKey.clientId).flatMap {
case Some(_) => BadRequest(Json.obj("error" -> "Apikey already exists")).asFuture
case None => {
apiKey.save().map {
case false => InternalServerError(Json.obj("error" -> "ApiKey not stored ..."))
case true => {
sendAuditAndAlert(
"CREATE_APIKEY",
s"User created an ApiKey",
"ApiKeyCreatedAlert",
Json.obj(
"group" -> group.toJson,
"apikey" -> apiKey.toJson
),
ctx
)
env.datastores.apiKeyDataStore.addFastLookupByGroup(groupId, apiKey).map { _ =>
env.datastores.apiKeyDataStore.findAll()
}
Created(apiKey.toJson)
}
}
}
}
}
}
}
}
def updateApiKeyFromGroup(groupId: String, clientId: String) =
ApiAction.async(parse.json) { ctx =>
env.datastores.serviceGroupDataStore.findById(groupId).flatMap {
case None => NotFound(Json.obj("error" -> s"Service Group with id: '$groupId' not found")).asFuture
case Some(group) if !ctx.canUserWrite(group) => ctx.fforbidden
case Some(group) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnGroup(group.id) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for group with id: '$groupId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnGroup(group.id) => {
ApiKey.fromJsonSafe(ctx.request.body) match {
case JsError(e) => BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(newApiKey, _) if newApiKey.clientId != clientId =>
BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(newApiKey, _) if newApiKey.clientId == clientId => {
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => BadRequest(Json.obj("error" -> "Apikey not found")).asFuture
case Some(oldApik) if !ctx.canUserWrite(oldApik) =>
BadRequest(Json.obj("error" -> "you cannot access this resource")).asFuture
case Some(_) => {
sendAuditAndAlert(
"UPDATE_APIKEY",
s"User updated an ApiKey",
"ApiKeyUpdatedAlert",
Json.obj(
"group" -> group.toJson,
"apikey" -> apiKey.toJson
),
ctx
)
newApiKey.save().map(_ => Ok(newApiKey.toJson))
}
}
}
}
}
}
}
}
def patchApiKeyFromGroup(groupId: String, clientId: String) =
ApiAction.async(parse.json) { ctx =>
env.datastores.serviceGroupDataStore.findById(groupId).flatMap {
case None => NotFound(Json.obj("error" -> s"Service Group with id: '$groupId' not found")).asFuture
case Some(group) if !ctx.canUserWrite(group) => ctx.fforbidden
case Some(group) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnGroup(group.id) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for group with id: '$groupId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnGroup(group.id) => {
val currentApiKeyJson = apiKey.toJson
val newApiKeyJson = patchJson(ctx.request.body, currentApiKeyJson)
ApiKey.fromJsonSafe(newApiKeyJson) match {
case JsError(e) => BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(newApiKey, _) if newApiKey.clientId != clientId =>
BadRequest(Json.obj("error" -> "Bad ApiKey format")).asFuture
case JsSuccess(newApiKey, _) if newApiKey.clientId == clientId => {
sendAuditAndAlert(
"UPDATE_APIKEY",
s"User updated an ApiKey",
"ApiKeyUpdatedAlert",
Json.obj(
"group" -> group.toJson,
"apikey" -> apiKey.toJson
),
ctx
)
newApiKey.save().map(_ => Ok(newApiKey.toJson))
}
}
}
}
}
}
def deleteApiKeyFromGroup(groupId: String, clientId: String) =
ApiAction.async { ctx =>
env.datastores.serviceGroupDataStore.findById(groupId).flatMap {
case None => NotFound(Json.obj("error" -> s"Group with id: '$groupId' not found")).asFuture
case Some(group) if !ctx.canUserWrite(group) => ctx.fforbidden
case Some(group) =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case Some(apiKey) if !apiKey.authorizedOnGroup(group.id) =>
NotFound(
Json.obj("error" -> s"ApiKey with clientId '$clientId' not found for group with id: '$groupId'")
).asFuture
case Some(apiKey) if apiKey.authorizedOnGroup(group.id) => {
sendAuditAndAlert(
"DELETE_APIKEY",
s"User deleted an ApiKey",
"ApiKeyDeletedAlert",
Json.obj(
"group" -> group.toJson,
"apikey" -> apiKey.toJson
),
ctx
)
env.datastores.apiKeyDataStore.deleteFastLookupByGroup(groupId, apiKey)
apiKey.delete().map(res => Ok(Json.obj("deleted" -> true)))
}
}
}
}
}
class ApiKeysController(val ApiAction: ApiAction, val cc: ControllerComponents)(implicit val env: Env)
extends AbstractController(cc)
with BulkControllerHelper[ApiKey, JsValue]
with CrudControllerHelper[ApiKey, JsValue]
with AdminApiHelper {
implicit lazy val ec = env.otoroshiExecutionContext
implicit lazy val mat = env.otoroshiMaterializer
lazy val logger = Logger("otoroshi-apikeys-api")
override def isApikey: Boolean = true
override def singularName: String = "apikey"
override def buildError(status: Int, message: String): ApiError[JsValue] =
JsonApiError(status, play.api.libs.json.JsString(message))
override def readEntity(json: JsValue): Either[JsValue, ApiKey] =
ApiKey._fmt.reads(json).asEither match {
case Left(e) => Left(JsError.toJson(e))
case Right(r) => Right(r)
}
override def writeEntity(entity: ApiKey): JsValue = ApiKey._fmt.writes(entity)
override def findByIdOps(
id: String,
req: RequestHeader
)(implicit env: Env, ec: ExecutionContext): Future[Either[ApiError[JsValue], OptionalEntityAndContext[ApiKey]]] = {
env.datastores.apiKeyDataStore.findById(id).map { opt =>
Right(
OptionalEntityAndContext(
entity = opt,
action = "ACCESS_APIKEY",
message = "User accessed a apikey",
metadata = Json.obj("ApiKeyId" -> id),
alert = "ApiKeyAccessed"
)
)
}
}
override def findAllOps(
req: RequestHeader
)(implicit env: Env, ec: ExecutionContext): Future[Either[ApiError[JsValue], SeqEntityAndContext[ApiKey]]] = {
env.datastores.apiKeyDataStore.findAll().map { seq =>
Right(
SeqEntityAndContext(
entity = seq,
action = "ACCESS_ALL_APIKEYS",
message = "User accessed all apikeys",
metadata = Json.obj(),
alert = "ApiKeysAccessed"
)
)
}
}
override def createEntityOps(
entity: ApiKey,
req: RequestHeader
)(implicit env: Env, ec: ExecutionContext): Future[Either[ApiError[JsValue], EntityAndContext[ApiKey]]] = {
env.datastores.apiKeyDataStore.set(entity).map {
case true => {
Right(
EntityAndContext(
entity = entity,
action = "CREATE_APIKEY",
message = "User created a apikey",
metadata = entity.toJson.as[JsObject],
alert = "ApiKeyCreatedAlert"
)
)
}
case false => {
Left(
JsonApiError(
500,
Json.obj("error" -> "apikey not stored ...")
)
)
}
}
}
override def updateEntityOps(
entity: ApiKey,
req: RequestHeader
)(implicit env: Env, ec: ExecutionContext): Future[Either[ApiError[JsValue], EntityAndContext[ApiKey]]] = {
env.datastores.apiKeyDataStore.set(entity).map {
case true => {
Right(
EntityAndContext(
entity = entity,
action = "UPDATE_APIKEY",
message = "User updated a apikey",
metadata = entity.toJson.as[JsObject],
alert = "ApiKeyUpdatedAlert"
)
)
}
case false => {
Left(
JsonApiError(
500,
Json.obj("error" -> "apikey not stored ...")
)
)
}
}
}
override def deleteEntityOps(
id: String,
req: RequestHeader
)(implicit env: Env, ec: ExecutionContext): Future[Either[ApiError[JsValue], NoEntityAndContext[ApiKey]]] = {
env.datastores.apiKeyDataStore.delete(id).map {
case true => {
Right(
NoEntityAndContext(
action = "DELETE_APIKEY",
message = "User deleted a apikey",
metadata = Json.obj("ApiKeyId" -> id),
alert = "ApiKeyDeletedAlert"
)
)
}
case false => {
Left(
JsonApiError(
500,
Json.obj("error" -> "apikey not deleted ...")
)
)
}
}
}
def getBearerValue(clientId: String) =
ApiAction.async { ctx =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserRead(apiKey) => ctx.fforbidden
case Some(apiKey) => {
sendAudit(
"ACCESS_SERVICE_APIKEY_SINGLE",
s"User accessed an apikey quotas from a service descriptor",
Json.obj("clientId" -> clientId),
ctx
)
Ok(
Json
.obj(
"bearer" -> apiKey.toBearer
)
.applyOnWithOpt(apiKey.rotation.nextSecret) { case (json, next) =>
json ++ Json.obj("bearer_next" -> apiKey.toNextBearer)
}
.applyOnWithOpt(ctx.request.getQueryString("newSecret")) { case (json, newSecret) =>
json ++ Json.obj("bearer_new" -> apiKey.copy(clientSecret = newSecret).toBearer)
}
).vfuture
}
}
}
def apiKeyQuotas(clientId: String) =
ApiAction.async { ctx =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserRead(apiKey) => ctx.fforbidden
case Some(apiKey) => {
sendAudit(
"ACCESS_SERVICE_APIKEY_QUOTAS",
s"User accessed an apikey quotas from a service descriptor",
Json.obj("clientId" -> clientId),
ctx
)
apiKey.remainingQuotas().map(rq => Ok(rq.toJson))
}
}
}
def resetApiKeyQuotas(clientId: String) =
ApiAction.async { ctx =>
env.datastores.apiKeyDataStore.findById(clientId).flatMap {
case None => NotFound(Json.obj("error" -> s"ApiKey with clientId '$clientId' not found")).asFuture
case Some(apiKey) if !ctx.canUserWrite(apiKey) => ctx.fforbidden
case Some(apiKey) => {
sendAudit(
"RESET_SERVICE_APIKEY_QUOTAS",
s"User reset an apikey quotas for a service descriptor",
Json.obj("clientId" -> clientId),
ctx
)
env.datastores.apiKeyDataStore.resetQuotas(apiKey).map(rq => Ok(rq.toJson))
}
}
}
override def extractId(entity: ApiKey): String = entity.clientId
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy