-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #180 from croessner/features
Features
- Loading branch information
Showing
12 changed files
with
736 additions
and
159 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package config | ||
|
||
import ( | ||
"net" | ||
"strings" | ||
"sync" | ||
) | ||
|
||
var mu = &sync.RWMutex{} | ||
|
||
// SoftWhitelistProvider defines the methods for managing a soft whitelist of networks associated with usernames. | ||
// The interface allows checking the existence of a whitelist, retrieving, setting, and deleting networks. | ||
type SoftWhitelistProvider interface { | ||
// HasSoftWhitelist checks if there is at least one entry in the soft whitelist, returning true if it exists, otherwise false. | ||
HasSoftWhitelist() bool | ||
|
||
// Get retrieves the list of networks associated with the given username from the soft whitelist. | ||
Get(username string) []string | ||
|
||
// Set adds a specified network to a user's whitelist if the network is valid and the username is not empty. | ||
Set(username, network string) | ||
|
||
// Delete removes a specified network from the user's soft whitelist identified by the provided username. | ||
Delete(username, network string) | ||
} | ||
|
||
// SoftWhitelist is a type that represents a map linking a string key to a slice of string values. | ||
// Typically used to associate users with a list of CIDR networks. | ||
type SoftWhitelist map[string][]string | ||
|
||
// NewSoftWhitelist creates and returns a new instance of SoftWhitelist initialized as an empty map of string slices. | ||
func NewSoftWhitelist() SoftWhitelist { | ||
return make(SoftWhitelist) | ||
} | ||
|
||
func (s SoftWhitelist) String() string { | ||
if s == nil { | ||
return "SoftWhitelist: <nil>" | ||
} | ||
|
||
for k, v := range s { | ||
return "SoftWhitelist: {SoftWhitelist[" + k + "]: " + strings.Join(v, ", ") + "}" | ||
} | ||
|
||
return "SoftWhitelist: {SoftWhitelist: <empty>}" | ||
} | ||
|
||
// HasSoftWhitelist checks if the SoftWhitelist is non-nil and contains at least one entry. | ||
func (s SoftWhitelist) HasSoftWhitelist() bool { | ||
if s == nil { | ||
return false | ||
} | ||
|
||
mu.RLock() | ||
|
||
defer mu.RUnlock() | ||
|
||
return len(s) > 0 | ||
} | ||
|
||
// isValidNetwork checks if the provided network string is a valid CIDR notation. | ||
// It returns true if the network is valid, otherwise false. | ||
func (s SoftWhitelist) isValidNetwork(network string) bool { | ||
_, _, err := net.ParseCIDR(network) | ||
|
||
return err == nil | ||
} | ||
|
||
// Set adds a specified network to a user's whitelist if the network is valid and the username is not empty. | ||
func (s SoftWhitelist) Set(username, network string) { | ||
if s == nil { | ||
return | ||
} | ||
|
||
mu.Lock() | ||
|
||
defer mu.Unlock() | ||
|
||
if len(username) == 0 { | ||
return | ||
} | ||
|
||
if s.isValidNetwork(network) { | ||
if s[username] == nil { | ||
s[username] = make([]string, 0) | ||
} | ||
|
||
s[username] = append(s[username], network) | ||
} | ||
} | ||
|
||
// Get retrieves the list of networks associated with the specified username from the SoftWhitelist. | ||
// If the SoftWhitelist is nil or the username does not exist, it returns nil. | ||
func (s SoftWhitelist) Get(username string) []string { | ||
if s == nil { | ||
return nil | ||
} | ||
|
||
mu.RLock() | ||
|
||
defer mu.RUnlock() | ||
|
||
for k, v := range s { | ||
if k == username { | ||
return v | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Delete removes the specified network from the user's whitelist in the SoftWhitelist. If the network is the only entry, | ||
// the user is removed from the whitelist. The function does nothing if the whitelist is nil or if the user does not exist. | ||
func (s SoftWhitelist) Delete(username, network string) { | ||
if s == nil { | ||
return | ||
} | ||
|
||
mu.Lock() | ||
|
||
defer mu.Unlock() | ||
|
||
networks, exists := s[username] | ||
if !exists { | ||
return | ||
} | ||
|
||
if len(networks) > 1 { | ||
for i, n := range networks { | ||
if n == network { | ||
networks = append(networks[:i], networks[i+1:]...) | ||
|
||
break | ||
} | ||
} | ||
|
||
s[username] = networks | ||
} else { | ||
if s[username][0] == network { | ||
delete(s, username) | ||
} | ||
} | ||
} | ||
|
||
var _ SoftWhitelistProvider = (*SoftWhitelist)(nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
package config | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestSoftWhitelist_String(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
s SoftWhitelist | ||
want string | ||
}{ | ||
{"NilSoftWhitelist", nil, "SoftWhitelist: <nil>"}, | ||
{"EmptySoftWhitelist", SoftWhitelist{}, "SoftWhitelist: {SoftWhitelist: <empty>}"}, | ||
{ | ||
"NonEmptySoftWhitelist", | ||
SoftWhitelist{"user1": {"192.168.1.0/24"}}, | ||
"SoftWhitelist: {SoftWhitelist[user1]: 192.168.1.0/24}", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := tt.s.String(); got != tt.want { | ||
t.Errorf("String() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestSoftWhitelist_HasSoftWhitelist(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
s SoftWhitelist | ||
want bool | ||
}{ | ||
{"NilSoftWhitelist", nil, false}, | ||
{"EmptySoftWhitelist", SoftWhitelist{}, false}, | ||
{"NonEmptySoftWhitelist", SoftWhitelist{"user1": {"192.168.1.0/24"}}, true}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := tt.s.HasSoftWhitelist(); got != tt.want { | ||
t.Errorf("HasSoftWhitelist() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestSoftWhitelist_isValidNetwork(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
s SoftWhitelist | ||
network string | ||
want bool | ||
}{ | ||
{"ValidIPv4CIDR", SoftWhitelist{}, "192.168.1.0/24", true}, | ||
{"InvalidCIDR", SoftWhitelist{}, "192.168.1.0", false}, | ||
{"InvalidFormat", SoftWhitelist{}, "invalid", false}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := tt.s.isValidNetwork(tt.network); got != tt.want { | ||
t.Errorf("isValidNetwork() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestSoftWhitelist_Set(t *testing.T) { | ||
s := NewSoftWhitelist() | ||
s.Set("user1", "192.168.1.0/24") | ||
|
||
t.Run("SetValid", func(t *testing.T) { | ||
if got := s.Get("user1"); len(got) != 1 || got[0] != "192.168.1.0/24" { | ||
t.Errorf("Expected network to be added, got %v", got) | ||
} | ||
}) | ||
|
||
s.Set("user1", "10.0.0.0/8") | ||
t.Run("SetAdditionalNetwork", func(t *testing.T) { | ||
if got := s.Get("user1"); len(got) != 2 || got[1] != "10.0.0.0/8" { | ||
t.Errorf("Expected additional network to be added, got %v", got) | ||
} | ||
}) | ||
|
||
s.Set("", "10.0.0.0/8") | ||
t.Run("SetEmptyUsername", func(t *testing.T) { | ||
if got := s.Get(""); got != nil { | ||
t.Errorf("Expected no networks for empty username, got %v", got) | ||
} | ||
}) | ||
|
||
s.Set("user2", "invalid") | ||
t.Run("SetInvalidNetwork", func(t *testing.T) { | ||
if got := s.Get("user2"); got != nil { | ||
t.Errorf("Expected no networks for invalid network, got %v", got) | ||
} | ||
}) | ||
} | ||
|
||
func TestSoftWhitelist_Get(t *testing.T) { | ||
s := NewSoftWhitelist() | ||
s.Set("user1", "192.168.1.0/24") | ||
|
||
tests := []struct { | ||
name string | ||
s SoftWhitelist | ||
username string | ||
want []string | ||
}{ | ||
{"GetExistingUser", s, "user1", []string{"192.168.1.0/24"}}, | ||
{"GetNonExistingUser", s, "user2", nil}, | ||
{"GetFromNilWhitelist", nil, "user1", nil}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := tt.s.Get(tt.username); !equalSlices(got, tt.want) { | ||
t.Errorf("Get() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestNewSoftAllow(t *testing.T) { | ||
t.Run("NewSoftWhitelist", func(t *testing.T) { | ||
if got := NewSoftWhitelist(); got == nil || len(got) != 0 { | ||
t.Errorf("NewSoftWhitelist() = %v, want empty SoftWhitelist", got) | ||
} | ||
}) | ||
} | ||
|
||
func equalSlices(a, b []string) bool { | ||
if len(a) != len(b) { | ||
return false | ||
} | ||
|
||
for i := range a { | ||
if a[i] != b[i] { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
func TestSoftWhitelist_Delete(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
s SoftWhitelist | ||
username string | ||
network string | ||
want map[string][]string | ||
}{ | ||
{"DeleteFromNilWhitelist", nil, "user1", "192.168.1.0/24", nil}, | ||
{"DeleteFromEmptyWhitelist", NewSoftWhitelist(), "user1", "192.168.1.0/24", map[string][]string{}}, | ||
{"DeleteNonExistentNetwork", SoftWhitelist{"user1": {"192.168.1.0/24"}}, "user1", "10.0.0.0/8", SoftWhitelist{"user1": {"192.168.1.0/24"}}}, | ||
{"DeleteExistingNetwork", SoftWhitelist{"user1": {"192.168.1.0/24", "10.0.0.0/8"}}, "user1", "192.168.1.0/24", SoftWhitelist{"user1": {"10.0.0.0/8"}}}, | ||
{"DeleteOnlyNetwork", SoftWhitelist{"user1": {"192.168.1.0/24"}}, "user1", "192.168.1.0/24", map[string][]string{}}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
tt.s.Delete(tt.username, tt.network) | ||
if !equalMaps(tt.s, tt.want) { | ||
t.Errorf("Delete() result = %v, want %v", tt.s, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func equalMaps(a, b SoftWhitelist) bool { | ||
if a == nil && b == nil { | ||
return true | ||
} | ||
|
||
if a == nil || b == nil { | ||
return false | ||
} | ||
|
||
if len(a) != len(b) { | ||
return false | ||
} | ||
|
||
for k, v := range a { | ||
bv, ok := b[k] | ||
if !ok || !equalSlices(v, bv) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} |
Oops, something went wrong.