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

play.filters.ip.IPFilter.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 
 */

package play.filters.ip

import java.net.InetAddress
import java.util.{ Arrays => JArrays }
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton

import play.api.http.HttpErrorHandler
import play.api.http.HttpErrorInfo
import play.api.http.Status
import play.api.inject.bind
import play.api.inject.SimpleModule
import play.api.libs.streams.Accumulator
import play.api.mvc._
import play.api.Configuration
import play.api.Logger
import play.core.j.JavaHttpErrorHandlerAdapter

/**
 * A filter to black-/whitelist IP addresses.
 *
 * For documentation on configuring this filter, please see the Play documentation at
 * [[https://www.playframework.com/documentation/latest/IPFilter]]
 *
 * @param config A ip filter configuration object
 * @param httpErrorHandler handling failed token error.
 */
@Singleton
class IPFilter @Inject() (config: IPFilterConfig, httpErrorHandler: HttpErrorHandler) extends EssentialFilter {

  private val logger = Logger(getClass)

  // Java API
  def this(
      config: IPFilterConfig,
      errorHandler: play.http.HttpErrorHandler
  ) = {
    this(config, new JavaHttpErrorHandlerAdapter(errorHandler))
  }

  override def apply(next: EssentialAction): EssentialAction = EssentialAction { req =>
    if (this.config.ipAllowed(req)) {
      next(req)
    } else {
      logger.warn(s"Access denied to ${req.path} for IP ${req.remoteAddress}.")
      Accumulator.done(
        httpErrorHandler.onClientError(
          req.addAttr(HttpErrorHandler.Attrs.HttpErrorInfo, HttpErrorInfo("ip-filter")),
          this.config.accessDeniedHttpStatusCode,
          s"IP not allowed: ${req.remoteAddress}"
        )
      )
    }
  }
}

case class IPFilterConfig(
    accessDeniedHttpStatusCode: Int = Status.FORBIDDEN,
    ipAllowed: RequestHeader => Boolean = _ => false
)

object IPFilterConfig {

  /**
   * Parses out the IPFilterConfig from play.api.Configuration (usually this means application.conf).
   */
  def fromConfiguration(conf: Configuration): IPFilterConfig = {
    val ipConfig                   = conf.get[Configuration]("play.filters.ip")
    val accessDeniedHttpStatusCode = ipConfig.getOptional[Int]("accessDeniedHttpStatusCode").getOrElse(Status.FORBIDDEN)
    val whiteList =
      ipConfig.getOptional[Seq[String]]("whiteList").getOrElse(Seq.empty).map(InetAddress.getByName(_).getAddress())
    val blackList =
      ipConfig.getOptional[Seq[String]]("blackList").getOrElse(Seq.empty).map(InetAddress.getByName(_).getAddress())

    /*
     * We need to compare IP addresses by bytes, not by string representations.
     * That's because in IPv6 following addresses are all the same:
     * "2001:cdba:0000:0000:0000:0000:3257:9652"
     * "2001:cdba:0:0:0:0:3257:9652"
     * "2001:cdba::3257:9652"
     * You can easily test this in jshell with java.net.InetAddress.getByName("").getAddress();
     */
    @inline def allowIP(req: RequestHeader): Boolean = {
      if (whiteList.isEmpty) {
        if (blackList.isEmpty) {
          true // default case, both whitelist and blacklist are empty so all IPs are allowed.
        } else {
          // The blacklist is defined, so we accept the IP if it's not blacklisted.
          blackList.forall(!JArrays.equals(_, req.connection.remoteAddress.getAddress))
        }
      } else {
        // The whitelist is defined. We accept the IP if there is a matching whitelist entry.
        whiteList.exists(JArrays.equals(_, req.connection.remoteAddress.getAddress))
      }
    }

    val whitelistModifiers = ipConfig.get[Seq[String]]("routeModifiers.whiteList")
    val blacklistModifiers = ipConfig.get[Seq[String]]("routeModifiers.blackList")

    @inline def checkRouteModifiers(rh: RequestHeader): Boolean = {
      import play.api.routing.Router.RequestImplicits._
      if (whitelistModifiers.isEmpty) {
        blacklistModifiers.isEmpty || blacklistModifiers.exists(rh.hasRouteModifier)
      } else {
        !whitelistModifiers.exists(rh.hasRouteModifier)
      }
    }

    val ipAllowed: RequestHeader => Boolean = { rh => !checkRouteModifiers(rh) || allowIP(rh) }

    IPFilterConfig(
      accessDeniedHttpStatusCode,
      ipAllowed,
    )
  }

}

@Singleton
class IPFilterConfigProvider @Inject() (conf: Configuration) extends Provider[IPFilterConfig] {
  lazy val get: IPFilterConfig = IPFilterConfig.fromConfiguration(conf)
}

class IPFilterModule
    extends SimpleModule(
      bind[IPFilterConfig].toProvider[IPFilterConfigProvider],
      bind[IPFilter].toSelf
    )

/**
 * The IP filter components.
 */
trait IPFilterComponents {
  def configuration: Configuration

  def httpErrorHandler: HttpErrorHandler

  lazy val ipFilterConfig: IPFilterConfig = IPFilterConfig.fromConfiguration(configuration)
  lazy val ipFilter: IPFilter             = new IPFilter(ipFilterConfig, httpErrorHandler)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy