Skip to content

Commit

Permalink
cli wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Zwiterrion committed Sep 18, 2024
1 parent 6f5ab66 commit 0be390f
Show file tree
Hide file tree
Showing 25 changed files with 1,250 additions and 551 deletions.
1 change: 1 addition & 0 deletions cli/src/commands/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ daikoku login
# PULL
daikoku pull apis
daikoku pull apis <ID>
daikoku pull mails

# VERSION
daikoku version
Expand Down
137 changes: 127 additions & 10 deletions daikoku/app/actions/actions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@ package fr.maif.otoroshi.daikoku.actions

import org.apache.pekko.http.scaladsl.util.FastFuture
import cats.implicits.catsSyntaxOptionId
import com.auth0.jwt.JWT
import com.google.common.base.Charsets
import fr.maif.otoroshi.daikoku.ctrls.CmsApiActionContext
import fr.maif.otoroshi.daikoku.domain.TeamPermission.{Administrator, ApiEditor}
import fr.maif.otoroshi.daikoku.domain._
import fr.maif.otoroshi.daikoku.env.Env
import fr.maif.otoroshi.daikoku.env.{
Env,
LocalCmsApiConfig,
OtoroshiCmsApiConfig
}
import fr.maif.otoroshi.daikoku.login.{IdentityAttrs, TenantHelper}
import fr.maif.otoroshi.daikoku.utils.Errors
import fr.maif.otoroshi.daikoku.utils.RequestImplicits.EnhancedRequestHeader
import play.api.Logger
import play.api.libs.json.{JsString, JsValue, Json}
import play.api.mvc._

import java.util.Base64
import scala.collection.concurrent.TrieMap
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Success, Try}

object tenantSecurity {
def userCanCreateApi(tenant: Tenant, user: User)(implicit
Expand Down Expand Up @@ -95,23 +104,131 @@ case class DaikokuActionMaybeWithoutUserContext[A](
}
}

trait ApiActionContext[A] {
def request: Request[A]
def user: User
def tenant: Tenant
def ctx: TrieMap[String, String] = new TrieMap[String, String]()
}

case class DaikokuActionContext[A](
request: Request[A],
user: User,
tenant: Tenant,
session: UserSession,
impersonator: Option[User],
isTenantAdmin: Boolean,
apiCreationPermitted: Boolean = false,
ctx: TrieMap[String, String] = new TrieMap[String, String]()
) {
request: Request[A],
user: User,
tenant: Tenant,
session: UserSession,
impersonator: Option[User],
isTenantAdmin: Boolean,
apiCreationPermitted: Boolean = false,
override val ctx: TrieMap[String, String] = new TrieMap[String, String]()
) extends ApiActionContext[A] {
def setCtxValue(key: String, value: Any): Unit = {
if (value != null) {
ctx.put(key, value.toString)
}
}
}

class CmsApiAction(val parser: BodyParser[AnyContent], env: Env)
extends ActionBuilder[CmsApiActionContext, AnyContent]
with ActionFunction[Request, CmsApiActionContext] {

implicit lazy val ec: ExecutionContext = env.defaultExecutionContext

def decodeBase64(encoded: String): String =
new String(Base64.getUrlDecoder.decode(encoded), Charsets.UTF_8)
private def extractUsernamePassword(
header: String
): Option[(String, String)] = {
val base64 = header.replace("Basic ", "").replace("basic ", "")
Option(base64)
.map(decodeBase64)
.map(_.split(":").toSeq)
.flatMap(a =>
a.headOption.flatMap(head => a.lastOption.map(last => (head, last)))
)
}

override def invokeBlock[A](
request: Request[A],
block: CmsApiActionContext[A] => Future[Result]
): Future[Result] = {
TenantHelper.withTenant(request, env) { tenant =>
env.config.cmsApiConfig match {
case OtoroshiCmsApiConfig(headerName, algo) =>
request.headers.get(headerName) match {
case Some(value) =>
Try(JWT.require(algo).build().verify(value)) match {
case Success(decoded) if !decoded.getClaim("apikey").isNull =>
block(CmsApiActionContext[A](request, tenant))
case _ =>
Errors.craftResponseResult(
"No api key provided",
Results.Unauthorized,
request,
None,
env
)
}
case _ =>
Errors.craftResponseResult(
"No api key provided",
Results.Unauthorized,
request,
None,
env
)
}
case LocalCmsApiConfig(_) =>
request.headers.get("Authorization") match {
case Some(auth) if auth.startsWith("Basic ") =>
extractUsernamePassword(auth) match {
case None =>
Errors.craftResponseResult(
"No api key provided",
Results.Unauthorized,
request,
None,
env
)
case Some((clientId, clientSecret)) =>
env.dataStore.apiSubscriptionRepo
.forTenant(tenant)
.findNotDeleted(
Json.obj(
"apiKey.clientId" -> clientId,
"apiKey.clientSecret" -> clientSecret
)
)
.map(_.length == 1)
.flatMap({
case done if done =>
block(CmsApiActionContext[A](request, tenant))
case _ =>
Errors.craftResponseResult(
"No api key provided",
Results.Unauthorized,
request,
None,
env
)
})
}
case _ =>
Errors.craftResponseResult(
"No api key provided",
Results.Unauthorized,
request,
None,
env
)
}
}
}
}

override protected def executionContext: ExecutionContext = ec
}

class DaikokuAction(val parser: BodyParser[AnyContent], env: Env)
extends ActionBuilder[DaikokuActionContext, AnyContent]
with ActionFunction[Request, DaikokuActionContext] {
Expand Down
26 changes: 2 additions & 24 deletions daikoku/app/controllers/ApiController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1016,29 +1016,7 @@ class ApiController(
s"@{user.name} has fetch all apis"
)
)(ctx.tenant.id.value, ctx) { (tenant, _) =>
env.dataStore.apiRepo.forTenant(tenant)
.findAll()
.map(apis => {
val fields: Seq[String] = ctx.request.getQueryString("fields").map(_.split(",").toSeq).getOrElse(Seq.empty[String])
val hasFields = fields.nonEmpty
if (hasFields) {
Ok(JsArray(apis.map(api => {
val jsonAPI = api.asJson
val content = jsonAPI match {
case arr @ JsArray(_) =>
JsArray(arr.value.map { item =>
JsonOperationsHelper.filterJson(item.as[JsObject], fields)
})
case obj @ JsObject(_) => JsonOperationsHelper.filterJson(obj, fields)
case _ => jsonAPI
}

content
})))
} else {
Ok(SeqApiFormat.writes(apis))
}
})
apiService.getApis(ctx)
}
}

Expand Down Expand Up @@ -4418,7 +4396,7 @@ class ApiController(
env.dataStore.apiRepo
.findAllVersions(tenant = ctx.tenant, id = apiId)
.map { apis =>
ctx.setCtxValue("api.name", apis.head.name)
ctx.setCtxValue("api.name", apis.headOption.map(_.name).getOrElse("unknown api name"))
ctx.setCtxValue("api.id", apiId)
Ok(
SeqVersionFormat.writes(
Expand Down
Loading

0 comments on commit 0be390f

Please sign in to comment.