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

com.gu.permissions.PermissionsProvider.scala Maven / Gradle / Ivy

package com.gu.permissions

import java.util.Locale
import java.util.concurrent.{Executors, TimeUnit}

import com.amazonaws.services.s3.AmazonS3ClientBuilder
import org.slf4j.LoggerFactory
import play.api.libs.json.Json

import scala.concurrent.duration.FiniteDuration
import scala.util.control.NonFatal

trait PermissionsProvider {
  def storeIsEmpty: Boolean
  def hasPermission(permission: PermissionDefinition, user: String): Boolean
  def listPermissions(user: String): Map[PermissionDefinition, Boolean]
  def allUserEmails(): Set[String]
}

case class UserEntry(
    permission: PermissionDefinition,
    active: Boolean,
    isCasualNotOnShift: Boolean = false
)

class CachedPermissionsProvider(cache: PermissionsProvider.Cache)
    extends PermissionsProvider {
  private val logger = LoggerFactory.getLogger(this.getClass)

  final override def storeIsEmpty: Boolean = cache.isEmpty

  final override def hasPermission(
      permission: PermissionDefinition,
      user: String
  ): Boolean = {
    val maybeUserPermission: Option[UserEntry] =
      get(user).find(b => b.permission == permission)

    val userHasPermission = maybeUserPermission.exists(_.active)

    val userIsCasualAndNotOnShift =
      maybeUserPermission.exists(_.isCasualNotOnShift)

    if (userHasPermission && userIsCasualAndNotOnShift) {
      logger.info(
        s"[PermissionsProvider.hasPermission] User $user has permission ${permission.name} but is a casual not currently on shift."
      )
    }

    userHasPermission
  }

  final override def listPermissions(
      user: String
  ): Map[PermissionDefinition, Boolean] = get(user).map {
    case UserEntry(permission, active, _) =>
      permission -> active
  }.toMap

  final override def allUserEmails(): Set[String] = cache.keySet

  private def get(user: String): List[UserEntry] = {
    cache.getOrElse(user.toLowerCase(Locale.UK), List.empty)
  }
}

class S3PermissionsProvider(
    s3Bucket: String,
    s3Key: String,
    refreshFrequency: FiniteDuration,
    s3Client: PermissionsS3
) extends PermissionsProvider {
  private val logger = LoggerFactory.getLogger(this.getClass)

  private val refreshScheduler = Executors.newScheduledThreadPool(1)
  @volatile private var cache: PermissionsProvider =
    new CachedPermissionsProvider(Map.empty)

  final override def storeIsEmpty: Boolean = cache.storeIsEmpty
  final override def hasPermission(
      permission: PermissionDefinition,
      user: String
  ): Boolean = cache.hasPermission(permission, user)
  final override def listPermissions(
      user: String
  ): Map[PermissionDefinition, Boolean] = cache.listPermissions(user)
  final override def allUserEmails(): Set[String] = cache.allUserEmails()

  def start(): Unit = {
    refresh()

    refreshScheduler.scheduleAtFixedRate(
      new Runnable() {
        override def run(): Unit = {
          refresh()
        }
      },
      refreshFrequency.toMillis,
      refreshFrequency.toMillis,
      TimeUnit.MILLISECONDS
    )
  }

  private def refresh(): Unit = try {
    logger.info(s"Load permissions from s3 bucket: s3://$s3Bucket/$s3Key")

    val (inputStream, lastModified) = s3Client.getObject(s3Bucket, s3Key)
    val json = Json.parse(inputStream)

    val definitions = json.as[List[PermissionWithUsers]]
    this.cache = new CachedPermissionsProvider(
      PermissionsProvider.buildCache(definitions)
    )

    logger.info(
      s"Permissions successfully retrieved from S3, last modified: $lastModified"
    )
  } catch {
    case NonFatal(err) =>
      logger.error("Could not refresh permissions from S3", err)
  }
}

object PermissionsProvider {
  type Cache = Map[String, List[UserEntry]]
  private val empty = Map.empty[String, List[UserEntry]]

  def apply(config: PermissionsConfig): PermissionsProvider = {
    val fileKey = PermissionsConfig.getPermissionsFileKey(config)

    val s3Client = AmazonS3ClientBuilder
      .standard()
      .withRegion(config.region)
      .withCredentials(config.awsCredentials)
      .build()
    val provider = new S3PermissionsProvider(
      config.s3Bucket,
      fileKey,
      config.refreshFrequency,
      PermissionsS3(s3Client)
    )

    // kick off a refresh and schedule refreshing in the background
    provider.start()
    provider
  }

  def buildCache(definitions: List[PermissionWithUsers]): Cache = {
    definitions.foldLeft(empty) {
      case (acc, PermissionWithUsers(permission, overrides)) =>
        overrides.foldLeft(acc) {
          case (acc, UserSetting(user, active, isCasualNotOnShift)) =>
            val normalisedUser = user.toLowerCase(Locale.UK)

            val before = acc.getOrElse(normalisedUser, List.empty)
            val after =
              before :+ UserEntry(permission, active, isCasualNotOnShift)

            acc + (normalisedUser -> after)
        }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy