Skip to content

Commit

Permalink
Release/0.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dilyand authored Jun 15, 2020
2 parents 30b9265 + 02ee40f commit 659a2d7
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
Version 0.7.0 (2020-06-06)
--------------------------
Add support for anonymous IP database (#132)

Version 0.6.1 (2019-11-25)
--------------------------
Default isInEuropeanUnion to false in case of NoSuchMethodError (#123)
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ can also configure an LRU (Least Recently Used) cache of variable size

## Installation

The latest version of scala-maxmind-iplookups is **0.6.1** and is compatible with Scala 2.12.
The latest version of scala-maxmind-iplookups is **0.7.0** and is compatible with Scala 2.12.

Add this to your SBT config:

```scala
val maxmindIpLookups = "com.snowplowanalytics" %% "scala-maxmind-iplookups" % "0.6.1"
val maxmindIpLookups = "com.snowplowanalytics" %% "scala-maxmind-iplookups" % "0.7.0"
```

Retrieve the `GeoLite2-City.mmdb` file from the [MaxMind downloads page][maxmind-downloads]
Expand Down Expand Up @@ -253,7 +253,7 @@ As such we recommend upgrading to version 0.4.0 as soon as possible

## Copyright and license

Copyright 2012-2019 Snowplow Analytics Ltd.
Copyright 2012-2020 Snowplow Analytics Ltd.

Licensed under the [Apache License, Version 2.0][license] (the "License");
you may not use this software except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ lazy val root = project
.settings(
organization := "com.snowplowanalytics",
name := "scala-maxmind-iplookups",
version := "0.6.1",
version := "0.7.0",
description := "Scala wrapper for MaxMind GeoIP2 library",
scalaVersion := "2.12.8",
javacOptions := BuildSettings.javaCompilerOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2019 Snowplow Analytics Ltd. All rights reserved.
* Copyright (c) 2012-2020 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
Expand Down Expand Up @@ -34,6 +34,7 @@ sealed trait CreateIpLookups[F[_]] {
* @param ispFile ISP lookup database file
* @param domainFile Domain lookup database file
* @param connectionTypeFile Connection type lookup database file
* @param anonymousFile Anonymous lookup database file
* @param memCache Whether to use MaxMind's CHMCache
* @param lruCacheSize Maximum size of LruMap cache
*/
Expand All @@ -42,6 +43,7 @@ sealed trait CreateIpLookups[F[_]] {
ispFile: Option[File] = None,
domainFile: Option[File] = None,
connectionTypeFile: Option[File] = None,
anonymousFile: Option[File] = None,
memCache: Boolean = true,
lruCacheSize: Int = 10000
): F[IpLookups[F]]
Expand All @@ -52,6 +54,7 @@ sealed trait CreateIpLookups[F[_]] {
* @param ispFile ISP lookup database filepath
* @param domainFile Domain lookup database filepath
* @param connectionTypeFile Connection type lookup database filepath
* @param anonymousFile Anonymous lookup database filepath
* @param memCache Whether to use MaxMind's CHMCache
* @param lruCacheSize Maximum size of LruMap cache
*/
Expand All @@ -60,13 +63,15 @@ sealed trait CreateIpLookups[F[_]] {
ispFile: Option[String] = None,
domainFile: Option[String] = None,
connectionTypeFile: Option[String] = None,
anonymousFile: Option[String] = None,
memCache: Boolean = true,
lruCacheSize: Int = 10000
): F[IpLookups[F]] = createFromFiles(
geoFile.map(new File(_)),
ispFile.map(new File(_)),
domainFile.map(new File(_)),
connectionTypeFile.map(new File(_)),
anonymousFile.map(new File(_)),
memCache,
lruCacheSize
)
Expand All @@ -83,6 +88,7 @@ object CreateIpLookups {
ispFile: Option[File] = None,
domainFile: Option[File] = None,
connectionTypeFile: Option[File] = None,
anonymousFile: Option[File] = None,
memCache: Boolean = true,
lruCacheSize: Int = 10000
): F[IpLookups[F]] =
Expand All @@ -99,6 +105,7 @@ object CreateIpLookups {
ispFile,
domainFile,
connectionTypeFile,
anonymousFile,
memCache,
lruCache
)
Expand All @@ -114,6 +121,7 @@ object CreateIpLookups {
ispFile: Option[File] = None,
domainFile: Option[File] = None,
connectionTypeFile: Option[File] = None,
anonymousFile: Option[File] = None,
memCache: Boolean = true,
lruCacheSize: Int = 10000
): Eval[IpLookups[Eval]] =
Expand All @@ -130,6 +138,7 @@ object CreateIpLookups {
ispFile,
domainFile,
connectionTypeFile,
anonymousFile,
memCache,
lruCache
)
Expand All @@ -145,6 +154,7 @@ object CreateIpLookups {
ispFile: Option[File] = None,
domainFile: Option[File] = None,
connectionTypeFile: Option[File] = None,
anonymousFile: Option[File] = None,
memCache: Boolean = true,
lruCacheSize: Int = 10000
): Id[IpLookups[Id]] = {
Expand All @@ -159,6 +169,7 @@ object CreateIpLookups {
ispFile,
domainFile,
connectionTypeFile,
anonymousFile,
memCache,
lruCache
)
Expand All @@ -181,6 +192,7 @@ class IpLookups[F[_]: Monad] private[iplookups] (
ispFile: Option[File],
domainFile: Option[File],
connectionTypeFile: Option[File],
anonymousFile: Option[File],
memCache: Boolean,
lru: Option[LruMap[F, String, IpLookupResult]]
)(
Expand All @@ -194,6 +206,7 @@ class IpLookups[F[_]: Monad] private[iplookups] (
private val domainService = getService(domainFile).map((_, ReaderFunctions.domain))
private val connectionTypeService =
getService(connectionTypeFile).map((_, ReaderFunctions.connectionType))
private val anonymousService = getService(anonymousFile)

/**
* Get a LookupService from a database file
Expand All @@ -212,7 +225,7 @@ class IpLookups[F[_]: Monad] private[iplookups] (

/**
* Creates an Either from an IPLookup
* @param service ISP, domain or connection type LookupService
* @param service ISP, domain, connection or anonymous type LookupService
* @return the result of the lookup
*/
private def getLookup(
Expand Down Expand Up @@ -248,6 +261,16 @@ class IpLookups[F[_]: Monad] private[iplookups] (
case _ => Monad[F].pure(None)
}

private def getAnonymousIpLookup(
ipAddress: Either[Throwable, InetAddress]
): F[Option[Either[Throwable, AnonymousIp]]] = (ipAddress, anonymousService) match {
case (Right(ipA), Some(gs)) =>
SR.getAnonymousValue(gs, ipA)
.map(loc => loc.map(AnonymousIp(_)).some)
case (Left(f), _) => Monad[F].pure(Some(Left(f)))
case _ => Monad[F].pure(None)
}

/**
* This version does not use the LRU cache.
* Concurrently looks up information
Expand All @@ -267,7 +290,8 @@ class IpLookups[F[_]: Monad] private[iplookups] (
org <- getLookup(ipAddress, orgService)
domain <- getLookup(ipAddress, domainService)
connectionType <- getLookup(ipAddress, connectionTypeService)
} yield IpLookupResult(ipLocation, isp, org, domain, connectionType)
anonymous <- getAnonymousIpLookup(ipAddress)
} yield IpLookupResult(ipLocation, isp, org, domain, connectionType, anonymous)

/**
* Returns the MaxMind location for this IP address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import cats.effect.Sync
import cats.syntax.either._
import com.maxmind.geoip2.DatabaseReader
import com.maxmind.geoip2.model.CityResponse
import com.maxmind.geoip2.model.AnonymousIpResponse

import model._

Expand All @@ -34,6 +35,12 @@ sealed trait SpecializedReader[F[_]] {
db: DatabaseReader,
ip: InetAddress
): F[Either[Throwable, CityResponse]]

def getAnonymousValue(
db: DatabaseReader,
ip: InetAddress
): F[Either[Throwable, AnonymousIpResponse]]

}

object SpecializedReader {
Expand All @@ -50,6 +57,13 @@ object SpecializedReader {
ip: InetAddress
): F[Either[Throwable, CityResponse]] =
Sync[F].delay { Either.catchNonFatal(db.city(ip)) }

def getAnonymousValue(
db: DatabaseReader,
ip: InetAddress,
): F[Either[Throwable, AnonymousIpResponse]] =
Sync[F].delay { Either.catchNonFatal(db.anonymousIp(ip)) }

}

implicit def evalSpecializedReader: SpecializedReader[Eval] = new SpecializedReader[Eval] {
Expand All @@ -65,6 +79,13 @@ object SpecializedReader {
ip: InetAddress
): Eval[Either[Throwable, CityResponse]] =
Eval.later { Either.catchNonFatal(db.city(ip)) }

def getAnonymousValue(
db: DatabaseReader,
ip: InetAddress
): Eval[Either[Throwable, AnonymousIpResponse]] =
Eval.later { Either.catchNonFatal(db.anonymousIp(ip)) }

}

implicit def idSpecializedReader: SpecializedReader[Id] = new SpecializedReader[Id] {
Expand All @@ -80,6 +101,12 @@ object SpecializedReader {
ip: InetAddress
): Id[Either[Throwable, CityResponse]] =
Either.catchNonFatal(db.city(ip))

def getAnonymousValue(
db: DatabaseReader,
ip: InetAddress
): Id[Either[Throwable, AnonymousIpResponse]] =
Either.catchNonFatal(db.anonymousIp(ip))
}
}

Expand Down
49 changes: 45 additions & 4 deletions src/main/scala/com.snowplowanalytics.maxmind.iplookups/model.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2019 Snowplow Analytics Ltd. All rights reserved.
* Copyright (c) 2012-2020 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
Expand All @@ -23,6 +23,7 @@ import cats.instances.either._

import com.maxmind.geoip2.DatabaseReader
import com.maxmind.geoip2.model.CityResponse
import com.maxmind.geoip2.model.AnonymousIpResponse

object model {
type ReaderFunction = (DatabaseReader, InetAddress) => String
Expand All @@ -46,6 +47,16 @@ object model {
accuracyRadius: Int
)

/** A case class wrapper around the MaxMind AnonymousIp class. */
final case class AnonymousIp(
ipAddress: String,
isAnonymous: Boolean,
isAnonymousVpn: Boolean,
isHostingProvider: Boolean,
isPublicProxy: Boolean,
isTorExitNode: Boolean
)

/** Companion class contains a constructor which takes a MaxMind CityResponse. */
object IpLocation {

Expand Down Expand Up @@ -80,25 +91,55 @@ object model {
}
}

/** Companion class contains a constructor which takes a MaxMind AnonymousIp. */
object AnonymousIp {

/**
* Constructs an AnonymousIp instance from a MaxMind AnonymousIp instance.
* @param anonymousIP MaxMind AnonymousIp object
* @return AnonymousIp
*/
def apply(anonymousIpResponse: AnonymousIpResponse): AnonymousIp = {

AnonymousIp(
ipAddress = anonymousIpResponse.getIpAddress,
isAnonymous = anonymousIpResponse.isAnonymous,
isAnonymousVpn = anonymousIpResponse.isAnonymousVpn,
isHostingProvider = anonymousIpResponse.isHostingProvider,
isPublicProxy = anonymousIpResponse.isPublicProxy,
isTorExitNode = anonymousIpResponse.isTorExitNode
)
}

}

/** Result of MaxMind lookups */
final case class IpLookupResult(
ipLocation: Option[Either[Throwable, IpLocation]],
isp: Option[Either[Throwable, String]],
organization: Option[Either[Throwable, String]],
domain: Option[Either[Throwable, String]],
connectionType: Option[Either[Throwable, String]]
connectionType: Option[Either[Throwable, String]],
anonymousIp: Option[Either[Throwable, AnonymousIp]]
) {
// Combine all errors if any
def results: ValidatedNel[
Throwable,
(Option[IpLocation], Option[String], Option[String], Option[String], Option[String])] = {
(
Option[IpLocation],
Option[String],
Option[String],
Option[String],
Option[String],
Option[AnonymousIp])] = {
val location = ipLocation.sequence[Error, IpLocation].toValidatedNel
val provider = isp.sequence[Error, String].toValidatedNel
val org = organization.sequence[Error, String].toValidatedNel
val dom = domain.sequence[Error, String].toValidatedNel
val connection = connectionType.sequence[Error, String].toValidatedNel
val anonymous = anonymousIp.sequence[Error, AnonymousIp].toValidatedNel

(location, provider, org, dom, connection).tupled
(location, provider, org, dom, connection, anonymous).tupled
}
}
}
Binary file not shown.
Loading

0 comments on commit 659a2d7

Please sign in to comment.