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

com.criteo.cuttle.Authentication.scala Maven / Gradle / Ivy

There is a newer version: 0.12.4
Show newest version
package com.criteo.cuttle

import java.util.Base64

import cats.effect.IO
import lol.http._

import scala.util.Try

/**
  * The cuttle API is private for any write operation while it is publicly
  * open for any read only operation. It allows to make it easy to build tooling
  * that monitor any running cuttle scheduler while restricting access to potentially
  * dangerous operations.
  *
  * The UI access itself requires authentication.
  */
object Auth {

  /**
    * An [[Authenticator]] takes care of extracting the User from an HTTP request.
    */
  trait Authenticator {

    private[cuttle] def apply(s: AuthenticatedService): PartialService =
      new PartialFunction[Request, IO[Response]] {
        override def isDefinedAt(request: Request): Boolean =
          s.isDefinedAt(request)

        override def apply(request: Request): IO[Response] =
          authenticate(request)
            .fold(IO.pure, user => {
              // pass through when authenticated
              s(request)(user)
            })
      }

    /**
      * Authenticate an HTTP request.
      *
      * @param request the HTTP request to be authenticated.
      * @return either an authenticated user or an error response.
      */
    def authenticate(request: Request): Either[Response, User]
  }

  /**
    * A connected [[User]].
    */
  case class User(userId: String)

  /**
    * Default implementation of Authenticator that authenticate any request
    * as Guest. It basically disables the authentication.
    */
  case object GuestAuth extends Authenticator {
    override def authenticate(r: Request): Either[Response, User] = Right(User("Guest"))
  }

  /**
    * Implementation of [[Authenticator]] that rely on HTTP Basic auth.
    *
    * @param credentialsValidator validate the (user,password) credentials.
    * @param userVisibleRealm The user visible realm.
    */
  case class BasicAuth(
    credentialsValidator: ((String, String)) => Boolean,
    userVisibleRealm: String = "cuttle_users"
  ) extends Authenticator {

    val scheme = "Basic"
    val unauthorizedResponse =
      Response(401).addHeaders(h"WWW-Authenticate" -> HttpString(s"""Basic realm="${userVisibleRealm}""""))

    /**
      * HTTP Basic auth implementation.
      * @param r request to be authenticated
      * @return either an authenticated user or an unauthorized response
      */
    override def authenticate(r: Request): Either[Response, User] =
      r.headers
        .get(h"Authorization")
        .flatMap({
          case s if s.toString().startsWith(scheme) => {
            val base64credentials = s.toString().drop(scheme.size).trim()
            BasicAuth.decodeBase64Credentials(base64credentials)
          }
          case _ => None
        })
        .collect({
          case (l, p) if credentialsValidator((l, p)) => User(l)
        })
        .toRight(unauthorizedResponse)
  }

  private[cuttle] object BasicAuth {
    def decodeBase64Credentials(credentials: String): Option[(String, String)] =
      Try(Base64.getDecoder.decode(credentials)).toOption
        .flatMap((decoded: Array[Byte]) => {
          val splitted = new String(decoded, "utf-8").trim().split(":", 2)
          if (splitted.size == 1) {
            None
          } else {
            Some((splitted(0) -> splitted(1)))
          }
        })
  }

  /** A [[lol.http.PartialService PartialService]] that requires an authenticated
    * user in request handler's scope. */
  type AuthenticatedService = PartialFunction[Request, (User => IO[Response])]

  private[cuttle] def defaultWith(response: IO[Response]): PartialService = {
    case _ => response
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy