Skip to content

Commit

Permalink
feat: improve geolocation handling for middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
mszekiel committed Sep 14, 2023
1 parent 1f94853 commit 22d49c0
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 19 deletions.
22 changes: 22 additions & 0 deletions httpx/client_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import (
"strings"
)

type GeoLocation struct {
City string
Region string
Country string
}

func GetClientIPAddressesWithoutInternalIPs(ipAddresses []string) (string, error) {
var res string

Expand Down Expand Up @@ -36,3 +42,19 @@ func ClientIP(r *http.Request) string {
return r.RemoteAddr
}
}

func ClientGeoLocation(r *http.Request) GeoLocation {
var clientGeoLocation GeoLocation

if r.Header.Get("Cf-Ipcity") != "" {
clientGeoLocation.City = r.Header.Get("Cf-Ipcity")
}
if r.Header.Get("Cf-Region-Code") != "" {
clientGeoLocation.Region = r.Header.Get("Cf-Region-Code")
}
if r.Header.Get("Cf-Ipcountry") != "" {
clientGeoLocation.Country = r.Header.Get("Cf-Ipcountry")
}

return clientGeoLocation
}
32 changes: 32 additions & 0 deletions httpx/client_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,35 @@ func TestClientIP(t *testing.T) {
assert.Equal(t, "1.0.0.4", ClientIP(req))
})
}

func TestClientGeoLocation(t *testing.T) {
req := http.Request{
Header: http.Header{},
}
req.Header.Add("cf-ipcity", "Berlin")
req.Header.Add("cf-ipcountry", "Germany")
req.Header.Add("cf-region-code", "BE")

t.Run("cf-ipcity", func(t *testing.T) {
req := req.Clone(context.Background())
assert.Equal(t, "Berlin", ClientGeoLocation(req).City)
})

t.Run("cf-ipcountry", func(t *testing.T) {
req := req.Clone(context.Background())
assert.Equal(t, "Germany", ClientGeoLocation(req).Country)
})

t.Run("cf-region-code", func(t *testing.T) {
req := req.Clone(context.Background())
assert.Equal(t, "BE", ClientGeoLocation(req).Region)
})

t.Run("empty", func(t *testing.T) {
req := req.Clone(context.Background())
req.Header.Del("cf-ipcity")
req.Header.Del("cf-ipcountry")
req.Header.Del("cf-region-code")
assert.Equal(t, GeoLocation{}, ClientGeoLocation(req))
})
}
14 changes: 5 additions & 9 deletions otelx/semconv/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,12 @@ func AttributesFromContext(ctx context.Context) []attribute.KeyValue {
}

func Middleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
var clientGeoLocation []string
if r.Header.Get("Cf-Ipcity") != "" {
clientGeoLocation = append(clientGeoLocation, r.Header.Get("Cf-Ipcity"))
}
if r.Header.Get("Cf-Ipcountry") != "" {
clientGeoLocation = append(clientGeoLocation, r.Header.Get("Cf-Ipcountry"))
}

ctx := ContextWithAttributes(r.Context(),
AttrClientIP(httpx.ClientIP(r)),
AttrGeoLocation(clientGeoLocation),
append(
AttrGeoLocation(httpx.ClientGeoLocation(r)),
AttrClientIP(httpx.ClientIP(r)),
)...,
)

next(rw, r.WithContext(ctx))
Expand Down
17 changes: 13 additions & 4 deletions otelx/semconv/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"

"github.com/ory/x/httpx"
)

func TestAttributesFromContext(t *testing.T) {
Expand All @@ -21,13 +23,20 @@ func TestAttributesFromContext(t *testing.T) {
assert.Len(t, AttributesFromContext(ctx), 1)

uid1, uid2 := uuid.Must(uuid.NewV4()), uuid.Must(uuid.NewV4())
ctx = ContextWithAttributes(ctx, AttrIdentityID(uid1), AttrClientIP("127.0.0.1"), AttrIdentityID(uid2), AttrGeoLocation([]string{"Berlin", "Germany"}))
location := httpx.GeoLocation{
City: "Berlin",
Country: "Germany",
Region: "BE",
}
ctx = ContextWithAttributes(ctx, append(AttrGeoLocation(location), AttrIdentityID(uid1), AttrClientIP("127.0.0.1"), AttrIdentityID(uid2))...)
attrs := AttributesFromContext(ctx)
assert.Len(t, attrs, 4, "should deduplicate")
assert.Equal(t, []attribute.KeyValue{
assert.Len(t, attrs, 6, "should deduplicate")
assert.EqualValues(t, []attribute.KeyValue{
attribute.String(AttributeKeyNID.String(), nid.String()),
attribute.String(AttributeKeyGeoLocationCity.String(), "Berlin"),
attribute.String(AttributeKeyGeoLocationCountry.String(), "Germany"),
attribute.String(AttributeKeyGeoLocationRegion.String(), "BE"),
attribute.String(AttributeKeyClientIP.String(), "127.0.0.1"),
attribute.String(AttributeKeyIdentityID.String(), uid2.String()),
attribute.StringSlice(AttributeKeyGeoLocation.String(), []string{"Berlin", "Germany"}),
}, attrs, "last duplicate attribute wins")
}
28 changes: 22 additions & 6 deletions otelx/semconv/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package semconv
import (
"github.com/gofrs/uuid"
otelattr "go.opentelemetry.io/otel/attribute"

"github.com/ory/x/httpx"
)

type Event string
Expand All @@ -22,10 +24,12 @@ func (a AttributeKey) String() string {
}

const (
AttributeKeyIdentityID AttributeKey = "IdentityID"
AttributeKeyNID AttributeKey = "ProjectID"
AttributeKeyClientIP AttributeKey = "ClientIP"
AttributeKeyGeoLocation AttributeKey = "GeoLocation"
AttributeKeyIdentityID AttributeKey = "IdentityID"
AttributeKeyNID AttributeKey = "ProjectID"
AttributeKeyClientIP AttributeKey = "ClientIP"
AttributeKeyGeoLocationCity AttributeKey = "GeoLocationCity"
AttributeKeyGeoLocationRegion AttributeKey = "GeoLocationRegion"
AttributeKeyGeoLocationCountry AttributeKey = "GeoLocationCountry"
)

func AttrIdentityID(val uuid.UUID) otelattr.KeyValue {
Expand All @@ -40,6 +44,18 @@ func AttrClientIP(val string) otelattr.KeyValue {
return otelattr.String(AttributeKeyClientIP.String(), val)
}

func AttrGeoLocation(val []string) otelattr.KeyValue {
return otelattr.StringSlice(AttributeKeyGeoLocation.String(), val)
func AttrGeoLocation(val httpx.GeoLocation) []otelattr.KeyValue {
var geoLocationAttributes []otelattr.KeyValue

if val.City != "" {
geoLocationAttributes = append(geoLocationAttributes, otelattr.String(AttributeKeyGeoLocationCity.String(), val.City))
}
if val.Country != "" {
geoLocationAttributes = append(geoLocationAttributes, otelattr.String(AttributeKeyGeoLocationCountry.String(), val.Country))
}
if val.Region != "" {
geoLocationAttributes = append(geoLocationAttributes, otelattr.String(AttributeKeyGeoLocationRegion.String(), val.Region))
}

return geoLocationAttributes
}

0 comments on commit 22d49c0

Please sign in to comment.