geoip

package
v0.0.0-...-202847b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 1, 2023 License: Apache-2.0 Imports: 17 Imported by: 3

Documentation

Overview

Package geoip detects the country by an IP address and provides alternative handlers.

This package is compatible to IPv4 and IPv6. Uses the MaxMind database, or MaxMind WebService or alternative country/city detectors.

The detected country and all its attributes can be added to a context.

Index

Examples

Constants

This section is empty.

Variables

DefaultAlternativeHandler gets called when detected Country cannot be found within the list of allowed countries. This handler can be overridden to provide a fallback for all scopes. To set a alternative handler for a website or store use the With*() options. This function gets called in WithIsCountryAllowedByIP.

Status is StatusServiceUnavailable

Functions

This section is empty.

Types

type Country

type Country struct {
	// IP contains the request IP address even if we run behind a proxy
	IP   net.IP `json:"ip,omitempty"`
	City struct {
		Confidence int               `json:"confidence,omitempty"`
		GeoNameID  uint              `json:"geoname_id,omitempty"`
		Names      map[string]string `json:"names,omitempty"`
	} `json:"city,omitempty"`
	Continent struct {
		Code      string            `json:"code,omitempty"`
		GeoNameID uint              `json:"geoname_id,omitempty"`
		Names     map[string]string `json:"names,omitempty"`
	} `json:"continent,omitempty"`
	Country struct {
		Confidence int               `json:"confidence,omitempty"`
		GeoNameID  uint              `json:"geoname_id,omitempty"`
		IsoCode    string            `json:"iso_code,omitempty"`
		Names      map[string]string `json:"names,omitempty"`
	} `json:"country,omitempty"`
	Location struct {
		AccuracyRadius    int     `json:"accuracy_radius,omitempty"`
		AverageIncome     int     `json:"average_income,omitempty"`
		Latitude          float64 `json:"latitude,omitempty"`
		Longitude         float64 `json:"longitude,omitempty"`
		MetroCode         int     `json:"metro_code,omitempty"`
		PopulationDensity int     `json:"population_density,omitempty"`
		TimeZone          string  `json:"time_zone,omitempty"`
	} `json:"location,omitempty"`
	Postal struct {
		Code       string `json:"code,omitempty"`
		Confidence int    `json:"confidence,omitempty"`
	} `json:"postal,omitempty"`
	RegisteredCountry struct {
		GeoNameID uint              `json:"geoname_id,omitempty"`
		IsoCode   string            `json:"iso_code,omitempty"`
		Names     map[string]string `json:"names,omitempty"`
	} `json:"registered_country,omitempty"`
	RepresentedCountry struct {
		GeoNameID uint              `json:"geoname_id,omitempty"`
		IsoCode   string            `json:"iso_code,omitempty"`
		Names     map[string]string `json:"names,omitempty"`
		Type      string            `json:"type,omitempty"`
	} `json:"represented_country,omitempty"`
	Subdivision []struct {
		Confidence int               `json:"confidence,omitempty"`
		GeoNameID  uint              `json:"geoname_id,omitempty"`
		IsoCode    string            `json:"iso_code,omitempty"`
		Names      map[string]string `json:"names,omitempty"`
	} `json:"subdivisions,omitempty"`
	Traits struct {
		AutonomousSystemNumber       int    `json:"autonomous_system_number,omitempty"`
		AutonomousSystemOrganization string `json:"autonomous_system_organization,omitempty"`
		Domain                       string `json:"domain,omitempty"`
		IsAnonymousProxy             bool   `json:"is_anonymous_proxy,omitempty"`
		IsSatelliteProvider          bool   `json:"is_satellite_provider,omitempty"`
		Isp                          string `json:"isp,omitempty"`
		IPAddress                    string `json:"ip_address,omitempty"`
		Organization                 string `json:"organization,omitempty"`
		UserType                     string `json:"user_type,omitempty"`
	} `json:"traits,omitempty"`
	MaxMind struct {
		QueriesRemaining int `json:"queries_remaining,omitempty"`
	} `json:"maxmind,omitempty"`
}

The Country structure corresponds to the data in the GeoIP2/GeoLite2 Country databases.

func FromContextCountry

func FromContextCountry(ctx context.Context) (*Country, bool)

FromContextCountry returns the geoip.Country in ctx if it exists or an error. The error has been previously set by WithContextError. An error can be for the first request, with a new IP address to fill the cache, of behaviour NotValid but all subsequent requests are of behaviour NotFound.

type Finder

type Finder interface {
	// Country todo add context for cancelling
	FindCountry(net.IP) (*Country, error)
	// Close closes the underlying object
	Close() error
}

Finder finds a Country by an IP address. Supports IPv4 and IPv6 addresses. We call this find because focusing on getting a single, exact match.

type IsAllowedFunc

type IsAllowedFunc func(_ scope.TypeID, _ *Country, allowedCountries []string) error

IsAllowedFunc checks in middleware WithIsCountryAllowedByIP if the country is allowed to process the request. The StringSlice contains a list of ISO country names fetched from the config.ScopedGetter. Return nil to indicate that the request can continue.

type Option

type Option func(*Service) error

Option can be used as an argument in NewService to configure it with different settings.

func OptionsError

func OptionsError(err error) []Option

OptionsError helper function to be used within the backend package or other sub-packages whose functions may return an OptionFactoryFunc.

func WithAllowedCountryCodes

func WithAllowedCountryCodes(isoCountryCodes []string, scopeIDs ...scope.TypeID) Option

WithAllowedCountryCodes sets a list of ISO countries to be validated against. Only to be used with function WithIsCountryAllowedByIP()

func WithAlternativeHandler

func WithAlternativeHandler(altHndlr mw.ErrorHandler, scopeIDs ...scope.TypeID) Option

WithAlternativeHandler sets for a scope the alternative handler if an IP address has been access denied. Only to be used with function WithIsCountryAllowedByIP()

func WithAlternativeRedirect

func WithAlternativeRedirect(urlStr string, code int, scopeIDs ...scope.TypeID) Option

WithAlternativeRedirect sets for a scope the error handler on a Service if an IP address has been access denied. Only to be used with function WithIsCountryAllowedByIP()

func WithCheckAllow

func WithCheckAllow(f IsAllowedFunc, scopeIDs ...scope.TypeID) Option

WithCheckAllow sets your custom function which checks if the country of an IP address should access to granted, or the next middleware handler in the chain gets called. Only to be used with function WithIsCountryAllowedByIP()

func WithCountryFinder

func WithCountryFinder(cr Finder) Option

WithCountryFinder applies a custom CountryRetriever. Sets the retriever atomically and only once.

func WithDebugLog

func WithDebugLog(w io.Writer) Option

WithDebugLog creates a new standard library based logger with debug mode enabled. The passed writer must be thread safe.

func WithDefaultConfig

func WithDefaultConfig(scopeIDs ...scope.TypeID) Option

WithDefaultConfig applies the default GeoIP configuration settings based for a specific scope. This function overwrites any previous set options.

Default values are:

  • Alternative Handler: variable DefaultAlternativeHandler
  • Logger black hole
  • Check allow: If allowed countries are empty, all countries are allowed

func WithDisable

func WithDisable(isDisabled bool, scopeIDs ...scope.TypeID) Option

WithDisable disables the current service and calls the next HTTP handler.

The variadic "scopeIDs" argument define to which scope the value gets applied and from which parent scope should be inherited. Setting no "scopeIDs" sets the value to the default scope. Setting one scope.TypeID defines the primary scope to which the value will be applied. Subsequent scope.TypeID are defining the fall back parent scopes to inherit the default or previously applied configuration from.

func WithErrorHandler

func WithErrorHandler(eh mw.ErrorHandler, scopeIDs ...scope.TypeID) Option

WithErrorHandler adds a custom error handler. Gets called in the http.Handler after the scope can be extracted from the context.Context and the configuration has been found and is valid. The default error handler prints the error to the user and returns a http.StatusServiceUnavailable.

The variadic "scopeIDs" argument define to which scope the value gets applied and from which parent scope should be inherited. Setting no "scopeIDs" sets the value to the default scope. Setting one scope.TypeID defines the primary scope to which the value will be applied. Subsequent scope.TypeID are defining the fall back parent scopes to inherit the default or previously applied configuration from.

func WithLogger

func WithLogger(l log.Logger) Option

WithLogger convenient helper function to apply a logger to the Service type.

func WithMarkPartiallyApplied

func WithMarkPartiallyApplied(partially bool, scopeIDs ...scope.TypeID) Option

WithMarkPartiallyApplied if set to true marks a configuration for a scope as partially applied with functional options set via source code. The internal service knows that it must trigger additionally the OptionFactoryFunc to load configuration from a backend. Useful in the case where parts of the configurations are coming from backend storages and other parts like http handler have been set via code. This function should only be applied in case you work with WithOptionFactory().

The variadic "scopeIDs" argument define to which scope the value gets applied and from which parent scope should be inherited. Setting no "scopeIDs" sets the value to the default scope. Setting one scope.TypeID defines the primary scope to which the value will be applied. Subsequent scope.TypeID are defining the fall back parent scopes to inherit the default or previously applied configuration from.

func WithOptionFactory

func WithOptionFactory(f OptionFactoryFunc) Option

WithOptionFactory applies a function which lazily loads the options from a slow backend (config.Getter) depending on the incoming scope within a request. For example applies the backend configuration to the service.

Once this option function has been set all other manually set option functions, which accept a scope and a scope ID as an argument, will NOT be overwritten by the new values retrieved from the configuration service.

cfgStruct, err := backendgeoip.NewConfigStructure()
if err != nil {
	panic(err)
}
be := backendgeoip.New(cfgStruct)

srv := geoip.MustNewService(
	geoip.WithOptionFactory(be.PrepareOptions()),
)

func WithRootConfig

func WithRootConfig(cg config.Getter) Option

WithRootConfig sets the root configuration service to retrieve the scoped base configuration. If you set the option WithOptionFactory() then the option WithRootConfig() does not need to be set as it won't get used.

func WithServiceErrorHandler

func WithServiceErrorHandler(eh mw.ErrorHandler) Option

WithServiceErrorHandler sets the error handler on the Service object. Convenient helper function.

type OptionFactories

type OptionFactories struct {
	// contains filtered or unexported fields
}

OptionFactories allows to register multiple OptionFactoryFunc identified by their names. Those OptionFactoryFuncs will be loaded in the backend package depending on the configured name under a certain path. This type is embedded in the backendgeoip.Configuration type.

func NewOptionFactories

func NewOptionFactories() *OptionFactories

NewOptionFactories creates a new struct and initializes the internal map for the registration of different option factories.

func (*OptionFactories) Deregister

func (of *OptionFactories) Deregister(name string)

Deregister removes a functional option factory from the internal register.

func (*OptionFactories) Lookup

func (of *OptionFactories) Lookup(name string) (OptionFactoryFunc, error)

Lookup returns a functional option factory identified by name or an error if the entry doesn't exists. May return a NotFound error behaviour.

func (*OptionFactories) Names

func (of *OptionFactories) Names() []string

Names returns an unordered list of names of all registered functional option factories.

func (*OptionFactories) Register

func (of *OptionFactories) Register(name string, factory OptionFactoryFunc)

Register adds another functional option factory to the internal register. Overwrites existing entries.

type OptionFactoryFunc

type OptionFactoryFunc func(config.Scoped) []Option

OptionFactoryFunc a closure around a scoped configuration to figure out which options should be returned depending on the scope brought to you during a request.

type ScopedConfig

type ScopedConfig struct {

	// AllowedCountries a slice which contains all allowed countries. An
	// incoming request for a scope checks if the country for an IP is contained
	// within this slice. Empty slice means that all countries are allowed. The
	// slice is owned by the callee.
	AllowedCountries []string
	// IsAllowedFunc checks in middleware WithIsCountryAllowedByIP if the country is
	// allowed to process the request.
	IsAllowedFunc // func(s scope.Hash, c *Country, allowedCountries []string) error
	// AlternativeHandler if ip/country is denied we call this handler.
	AlternativeHandler mw.ErrorHandler
	// contains filtered or unexported fields
}

ScopedConfig scoped based configuration and should not be embedded into your own types. Call ScopedConfig.ScopeID to know to which scope this configuration has been bound to.

func (*ScopedConfig) IsAllowed

func (sc *ScopedConfig) IsAllowed(c *Country) error

IsAllowed checks if the country is allowed. An empty AllowedCountries fields allows all countries.

type Service

type Service struct {

	// Finder finds a country by an IP address. If nil panics during execution
	// in the middleware. This field gets protected by a mutex to allowing
	// setting the field during requests.
	Finder
	// contains filtered or unexported fields
}

Service represents a service manager for GeoIP detection and restriction. Please consider the law in your country if you would like to implement geo-blocking.

func MustNew

func MustNew(opts ...Option) *Service

MustNew same as New() but panics on error. Use only during app start up process.

func New

func New(opts ...Option) (*Service, error)

New creates a new GeoIP service to be used as a middleware or standalone.

func (*Service) ClearCache

func (s *Service) ClearCache() error

ClearCache clears the internal map storing all scoped configurations. You must reapply all functional options. TODO(CyS) all previously applied options will be automatically reapplied.

func (*Service) Close

func (s *Service) Close() error

Close closes the underlying GeoIP CountryRetriever service and resets the internal loading state of the GeoIP flag. It does not yet clear the internal cache.

func (*Service) ConfigByScope

func (s *Service) ConfigByScope(websiteID, storeID int64) (ScopedConfig, error)

ConfigByScope creates a new scoped configuration depending on the Service.useWebsite flag. If useWebsite==true the scoped configuration contains only the website->default scope despite setting a store scope. If an OptionFactory is set the configuration gets loaded from the backend. A nil root config causes a panic.

func (*Service) ConfigByScopeID

func (s *Service) ConfigByScopeID(current scope.TypeID, parent scope.TypeID) (scpCfg ScopedConfig, _ error)

ConfigByScopeID returns the correct configuration for a scope and may fall back to the next higher scope: store -> website -> default. If `current` TypeID is Store, then the `parent` can only be Website or Default. If an entry for a scope cannot be found the next higher scope gets looked up and the pointer of the next higher scope gets assigned to the current scope. This prevents redundant configurations and enables us to change one scope configuration with an impact on all other scopes which depend on the parent scope. A zero `parent` triggers no further look ups. This function does not load any configuration (config.Getter related) from the backend and accesses the internal map of the Service directly.

Important: a "current" scope cannot have multiple "parent" scopes.

func (*Service) ConfigByScopedGetter

func (s *Service) ConfigByScopedGetter(scpGet config.Scoped) (ScopedConfig, error)

ConfigByScopedGetter returns the internal configuration depending on the ScopedGetter. Mainly used within the middleware. If you have applied the option WithOptionFactory() the configuration will be pulled out only one time from the backend configuration service. The field optionInflight handles the guaranteed atomic single loading for each scope.

func (*Service) CountryByIP

func (s *Service) CountryByIP(r *http.Request) (*Country, error)

CountryByIP searches a country by an IP address and returns the found country. It only needs the functional options WithGeoIP*().

func (*Service) DebugCache

func (s *Service) DebugCache(w io.Writer) error

DebugCache uses Sprintf to write an ordered list (by scope.TypeID) into a writer. Only usable for debugging.

func (*Service) Options

func (s *Service) Options(opts ...Option) error

Options applies option at creation time or refreshes them.

func (*Service) WithCountryByIP

func (s *Service) WithCountryByIP(next http.Handler) http.Handler

WithCountryByIP is a simple middleware which detects the country via an IP address. With the detected country a new tree context.Context gets created. Use FromContextCountry() to extract the country or an error. If you don't like the middleware consider using the function CountryByIP().

func (*Service) WithIsCountryAllowedByIP

func (s *Service) WithIsCountryAllowedByIP(next http.Handler) http.Handler

WithIsCountryAllowedByIP queries the AllowedCountries slice to retrieve a list of countries for a scope and then uses the function IsAllowedFunc to check if a country is allowed for an IP address. If a country should not access the next handler within the middleware chain it will call an alternative handler to e.g. show a different page or perform a redirect. Use FromContextCountry() to extract the country or an error. Tis middleware allows geo blocking.

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"path/filepath"

	"github.com/corestoreio/log"
	"github.com/corestoreio/pkg/config/cfgmock"
	"github.com/corestoreio/pkg/net/geoip"
	"github.com/corestoreio/pkg/net/geoip/backendgeoip"
	"github.com/corestoreio/pkg/net/geoip/maxmindfile"
	"github.com/corestoreio/pkg/net/mw"
	"github.com/corestoreio/pkg/store/scope"
)

func main() {
	// The scope. Those two values are now hard coded because we cannot access
	// here the database to the website, store_group and store tables.
	const (
		websiteID = 1
		storeID   = 2
	)

	logBuf := new(log.MutexBuffer)

	cfgStruct, err := backendgeoip.NewConfigStructure()
	if err != nil {
		panic(fmt.Sprintf("%+v", err))
	}
	backend := backendgeoip.New(cfgStruct)
	backend.Register(maxmindfile.NewOptionFactory(backend.MaxmindLocalFile))

	// This configuration says that any incoming request whose IP address does
	// not belong to the countries Germany (DE), Austria (AT) or Switzerland
	// (CH) gets redirected to the URL byebye.de.io with the redirect code 307.
	//
	// We're using here the cfgmock.NewService which is an in-memory
	// configuration service. Normally you would use the MySQL database or
	// consul or etcd.
	cfgSrv := cfgmock.NewService(cfgmock.PathValue{
		// @see structure.go why scope.Store and scope.Website can be used.
		backend.DataSource.MustFQ():                              `file`, // file triggers the lookup in
		backend.MaxmindLocalFile.MustFQ():                        filepath.Join("testdata", "GeoIP2-Country-Test.mmdb"),
		backend.AlternativeRedirect.MustFQStore(storeID):         `https://byebye.de.io`,
		backend.AlternativeRedirectCode.MustFQWebsite(websiteID): 307,
		backend.AllowedCountries.MustFQStore(storeID):            "DE,AT,CH",
	})

	geoSrv := geoip.MustNew(
		geoip.WithRootConfig(cfgSrv),
		geoip.WithDebugLog(logBuf),
		geoip.WithOptionFactory(backend.PrepareOptionFactory()),
		// Just for testing and in this example, we let the HTTP Handler panicking on any
		// error. You should not do that in production apps.
		geoip.WithServiceErrorHandler(mw.ErrorWithPanic),
	)

	// Set up the incoming request from the outside world. The scope in the
	// context gets set via scope.RunModeCalculater or via JSON web token or via
	// session.
	req := httptest.NewRequest("GET", "https://corestore.io/", nil)
	req = req.WithContext(scope.WithContext(req.Context(), websiteID, storeID))
	req.RemoteAddr = `2a02:d180::` // IP address range of Germany
	rec := httptest.NewRecorder()

	lastHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		c, ok := geoip.FromContextCountry(r.Context())
		if !ok {
			panic("A programmer made an error")
		}
		fmt.Fprintf(w, "Got allowed country DE:%s EN:%s", c.Country.Names["de"], c.Country.Names["en"])
	})
	geoSrv.WithIsCountryAllowedByIP(lastHandler).ServeHTTP(rec, req)
	fmt.Println(rec.Body.String())

	// Change the request to an IP address outside the DACH region
	req.RemoteAddr = `2a02:d200::` // Finland
	rec = httptest.NewRecorder()
	// re-run the middleware and access is denied despite the scope in the context
	// is the same.
	geoSrv.WithIsCountryAllowedByIP(lastHandler).ServeHTTP(rec, req)
	fmt.Println(rec.Body.String())
}
Output:

Got allowed country DE:Deutschland EN:Germany
<a href="https://byebye.de.io">Temporary Redirect</a>.

type StoreFinder

type StoreFinder interface {
	// DefaultStoreID returns the default active store ID and its website ID
	// depending on the run mode. Error behaviour is mostly of type NotValid.
	DefaultStoreID(runMode scope.TypeID) (websiteID, storeID uint32, err error)
	// StoreIDbyCode returns, depending on the runMode, for a storeCode its
	// active store ID and its website ID. An empty runMode hash falls back to
	// select the default website with its default group and the slice of
	// default stores. A not-found error behaviour gets returned if the code
	// cannot be found. If the runMode equals to scope.DefaultTypeID, the returned
	// ID is always 0 and error is nil.
	StoreIDbyCode(runMode scope.TypeID, storeCode string) (websiteID, storeID uint32, err error)
}

StoreFinder see store.Finder for a description. Usage of this interface in WithRunMode() middleware.

Directories

Path Synopsis
Package backendgeoip defines the backend configuration options and element slices.
Package backendgeoip defines the backend configuration options and element slices.
Package maxmindfile provides an OptionFactoryFunc for the backendgeopip package.
Package maxmindfile provides an OptionFactoryFunc for the backendgeopip package.
Package maxmindwebservice provides an OptionFactoryFunc for the backendgeopip package.
Package maxmindwebservice provides an OptionFactoryFunc for the backendgeopip package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL