All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zalando.zhewbacca.IAMClient.scala Maven / Gradle / Ivy

The newest version!
package org.zalando.zhewbacca

import java.util.concurrent.atomic.AtomicInteger
import javax.inject.{Inject, Singleton}

import akka.actor.ActorSystem
import akka.pattern.CircuitBreaker
import org.zalando.zhewbacca.metrics.PlugableMetrics
import play.api.http.Status._
import play.api.libs.ws.WSClient
import play.api.{Configuration, Logger}

import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal

/**
  * Retrieves TokenInfo for given OAuth2 token using IAM API.
  *
  * Class applies a Circuit Breaker pattern, so it must be a singleton in the client's code. Implementation
  * depends on Play infrastructure so it will work only in a context of running application.
  *
  * @param config Play config to get configuration parameters for WS client and circuit breaker
  */
@Singleton
class IAMClient @Inject() (
    config: Configuration,
    plugableMetrics: PlugableMetrics,
    ws: WSClient,
    actorSystem: ActorSystem,
    implicit val ec: ExecutionContext
) extends ((OAuth2Token) => Future[Option[TokenInfo]]) {

  val logger: Logger = Logger("security.IAMClient")

  val METRICS_BREAKER_CLOSED = 0
  val METRICS_BREAKER_OPEN = 1
  val circuitStatus = new AtomicInteger()

  plugableMetrics.gauge {
    circuitStatus.get
  }

  val authEndpoint = config.getString("authorisation.iam.endpoint").getOrElse(
    throw new IllegalArgumentException("Authorisation: IAM endpoint is not configured")
  )

  val breakerMaxFailures = config.getInt("authorisation.iam.cb.maxFailures").getOrElse(
    throw new IllegalArgumentException("Authorisation: Circuit Breaker max failures is not configured")
  )

  val breakerCallTimeout = config.getInt("authorisation.iam.cb.callTimeout").getOrElse(
    throw new IllegalArgumentException("Authorisation: Circuit Breaker call timeout is not configured")
  ).millis

  val breakerResetTimeout = config.getInt("authorisation.iam.cb.resetTimeout").getOrElse(
    throw new IllegalArgumentException("Authorisation: Circuit Breaker reset timeout is not configured")
  ).millis

  lazy val breaker: CircuitBreaker = new CircuitBreaker(
    actorSystem.scheduler,
    breakerMaxFailures,
    breakerCallTimeout,
    breakerResetTimeout
  ).onHalfOpen {
    circuitStatus.set(METRICS_BREAKER_OPEN)
  }.onOpen {
    circuitStatus.set(METRICS_BREAKER_OPEN)
  }.onClose {
    circuitStatus.set(METRICS_BREAKER_CLOSED)
  }

  override def apply(token: OAuth2Token): Future[Option[TokenInfo]] = {
    breaker.withCircuitBreaker(
      plugableMetrics.timing(ws.url(authEndpoint).withQueryString(("access_token", token.value)).get())
    ).map { response =>
        response.status match {
          case OK => Some(response.json.as[TokenInfo])
          case _ => None
        }
      } recover {
        case NonFatal(e) =>
          logger.error(s"Exception occurred during validation of token '${token.toSafeString}': $e")
          None // consider any exception as invalid token
      }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy