Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TASK] TRK-3577 [TASK] TRK-3577 N - Device ID masking GDPR #34

Merged
merged 7 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions gdpr/protect_device_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gdpr

const (
hiddenRune = '*'
)

// ProtectDeviceID hides last two character from passed device id and returns string with protected value
func ProtectDeviceID(val string) string {
if val == "" {
return val
}

r := []rune(val)
l := len(r)

// If someone passes string with less than 2 characters, we don't protect it.
if l < 2 {
Skandalik marked this conversation as resolved.
Show resolved Hide resolved
return val
}

r[l-1] = hiddenRune
r[l-2] = hiddenRune

return string(r)
}
58 changes: 58 additions & 0 deletions gdpr/protect_device_id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package gdpr_test

import (
"strconv"
"testing"

. "github.com/msales/gox/gdpr"
)

func Test_ProtectDeviceID(t *testing.T) {
tests := []struct {
name string
value string
want string
}{
{
name: "empty value",
value: "",
want: "",
},
{
name: "correct value to protect",
value: "some_value",
want: "some_val**",
},
{
name: "only numbers",
value: "12345",
want: "123**",
},
{
name: "string with 2 chars",
value: "11",
want: "**",
},
{
name: "string with less than 2 chars",
value: "1",
want: "1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ProtectDeviceID(tt.value); got != tt.want {
t.Errorf("Got %+v, want %+v", got, tt.want)
}
})
}
}

func BenchmarkProtectDeviceID(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
ProtectDeviceID(strconv.Itoa(i))
}
}
59 changes: 59 additions & 0 deletions gdpr/protect_ip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package gdpr

import (
"encoding/binary"
"net"

"github.com/msales/gox/netx"
)

// IP contains constraint for accepted types of IPs for protection.
type IP interface {
net.IP | netx.IP | uint32 | string
}

// ProtectIP hides last octet from passed uint32 IPV4 value and returns string with protected value
func ProtectIP[T IP](ip T) string {
switch cIP := any(ip).(type) {
// Every other type should end up in this case.
case net.IP:
// if IP is v6, we don't do anything with it.
if cIP.To4() == nil {
return ""
}

// if ip is somehow empty, return empty string
if len(cIP) == 0 {
return ""
}

// change last octet of IP v4 to 0 and guard
cIP[15] = 0
return cIP.String()

// netx.IP is just wrapper over net.IP type, so it's easy to protect against it.
case netx.IP:
return ProtectIP(cIP.ToIP())

// uint32 is number representation of IP used by gox/netx package. 0 is the only edge value to check
case uint32:
if cIP == 0 {
return ""
}

b := make([]byte, 4)
binary.BigEndian.PutUint32(b[:], cIP)
return ProtectIP(net.IPv4(b[0], b[1], b[2], b[3]))

// string is raw representation of IP, downside is that we have to parse it first.
case string:
if cIP == "" {
return ""
}

parsed := net.ParseIP(cIP)
return ProtectIP(parsed)
}

return ""
}
181 changes: 181 additions & 0 deletions gdpr/protect_ip_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package gdpr_test

import (
"net"
"strconv"
"testing"

. "github.com/msales/gox/gdpr"
"github.com/msales/gox/netx"
)

func Test_ProtectIP_netIP(t *testing.T) {
tests := []struct {
name string
ip net.IP
want string
}{
{
name: "nil",
ip: nil,
want: "",
},
{
name: "empty",
ip: net.IP{},
want: "",
},
{
name: "net.IP type",
ip: net.IPv4(42, 21, 37, 213),
want: "42.21.37.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ProtectIP(tt.ip); got != tt.want {
t.Errorf("Got %+v, want %+v", got, tt.want)
}
})
}
}

func Test_ProtectIP_netxIP(t *testing.T) {
tests := []struct {
name string
ip netx.IP
want string
}{
{
name: "nil",
ip: nil,
want: "",
},
{
name: "empty",
ip: netx.IP{},
want: "",
},
{
name: "net.IP type",
ip: netx.IP(net.IPv4(42, 21, 37, 213)),
want: "42.21.37.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ProtectIP(tt.ip); got != tt.want {
t.Errorf("Got %+v, want %+v", got, tt.want)
}
})
}
}

func Test_ProtectIP_String(t *testing.T) {
tests := []struct {
name string
ip string
want string
}{
{
name: "empty string",
ip: "",
want: "",
},
{
name: "zero ip",
ip: "0.0.0.0",
want: "0.0.0.0",
},
{
name: "raw ip v4",
ip: "42.21.37.213",
want: "42.21.37.0",
},
{
name: "local ip v6",
ip: "::1",
want: "::1",
},
{
name: "raw ip v6",
ip: "2001:0000:130F:0000:0000:09C0:876A:130B",
want: "2001:0000:130F:0000:0000:09C0:0000:0000",
Skandalik marked this conversation as resolved.
Show resolved Hide resolved
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ProtectIP(tt.ip); got != tt.want {
t.Errorf("Got %+v, want %+v", got, tt.want)
}
})
}
}

func Test_ProtectIP_Uint(t *testing.T) {
tests := []struct {
name string
ip uint32
want string
}{
{
name: "0 value",
ip: 0,
want: "",
},
{
name: "uint ip",
ip: ipToUint(net.IPv4(42, 21, 37, 213)),
want: "42.21.37.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ProtectIP(tt.ip); got != tt.want {
t.Errorf("Got %+v, want %+v", got, tt.want)
}
})
}
}

func ipToUint(ip net.IP) uint32 {
xIP := netx.IP(ip)
return xIP.Uint32()
}

func BenchmarkProtectIP_Uint(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
ProtectIP(uint32(i))
}
}

func BenchmarkProtectIP_String(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
v := strconv.Itoa(i)
ProtectIP(v + "." + v + "." + v + "." + v)
}
}

func BenchmarkProtectIP_netIP(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
ProtectIP(net.IPv4(byte(i), byte(i), byte(i), byte(i)))
}
}

func BenchmarkProtectIP_netxIP(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
ProtectIP(netx.IP(net.IPv4(byte(i), byte(i), byte(i), byte(i))))
}
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/msales/gox

go 1.21
go 1.22.0

toolchain go1.22.1
Loading