Skip to content
Snippets Groups Projects
Commit b10fcc52 authored by Josh Brooks's avatar Josh Brooks
Browse files

Add banned manager for usernames

parent f4f91b07
No related branches found
No related tags found
4 merge requests!50Revert "update deps",!48Release,!42Add username field to users table for raw username,!41Xx 3692/banned users
......@@ -54,6 +54,15 @@ permAddress: "0.0.0.0:12345"
twilioSid: "sid"
twilioToken: "token"
twilioVerification: "verification"
# Banned users which follow the regex codepath.
# Usernames should be separated by a Linux newline character ("\n").
bannedRegexList: "bannedRegexList.txt"
# Simple banned username list. Any name exactly matching in this list will not be allowed as a username.
# Usernames should be separated by a Linux newline character ("\n").
bannedUserList: "bannedUserList.txt"
```
## Running
......
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package banned
import (
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"regexp"
"strings"
"testing"
)
// Manager contains two lists of banned/reserved usernames. It handles
// checking for banned usernames on user registration.
type Manager struct {
// A simple banned user lookup. Any user that exactly matches something
// in this map will be considered banned/reserved.
bannedUserList map[string]struct{}
// A more complex banned user lookup. Contains a list of regular expressions,
// and any username which matches any of these regular expressions will be
// considered banned/reserved.
bannedRegexList []*regexp.Regexp
}
// NewManager constructs the banned.Manager object. NewManager is passed in
// two text files containing a list where values are separated by the
// Linux newline ("\n"). NewManager will parse these two lists separately to create a
// Manager.bannedUserList and a Manager.bannedRegexList respectively.
func NewManager(bannedUserFile, bannedRegexFile string) (*Manager, error) {
// Construct a map of banned/reserved usernames
bannedUsers := make(map[string]struct{})
if bannedUserFile != "" {
bannedUserList := strings.Split(bannedUserFile, "\n")
for _, bannedUser := range bannedUserList {
if bannedUser == "" { // Skip any empty lines
continue
}
bannedUsers[bannedUser] = struct{}{}
}
}
// Construct a regex list for banned/reserved usernames
bannedRegexList := make([]*regexp.Regexp, 0)
if bannedRegexFile != "" {
regexList := strings.Split(bannedRegexFile, "\n")
for _, bannedRegex := range regexList {
if bannedRegex == "" { // Skip any empty lines
continue
}
// Compile regex expression
regex, err := regexp.Compile(bannedRegex)
if err != nil {
return nil, errors.Errorf("Failed to compile banned user regex %q: %v", bannedRegex, err)
}
bannedRegexList = append(bannedRegexList, regex)
}
}
return &Manager{
bannedUserList: bannedUsers,
bannedRegexList: bannedRegexList,
}, nil
}
// IsBanned checks if the username is in Manager's bannedUserList or
// matched to any banned regular expression.
func (m *Manager) IsBanned(username string) bool {
_, exists := m.bannedUserList[username]
if exists {
return exists
}
return m.isRegexBanned(username)
}
// isRegexBanned checks is the username matches any banned regular expression.
func (m *Manager) isRegexBanned(username string) bool {
for _, regex := range m.bannedRegexList {
if regex.MatchString(username) {
return true
}
}
return false
}
// SetBannedTest is a testing only helper function which sets a username
// in Manager's bannedUserList.
func (m *Manager) SetBannedTest(username string, t *testing.T) {
if t == nil {
jww.FATAL.Panic("Cannot use this outside of testing")
}
m.bannedUserList[username] = struct{}{}
}
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package banned
import (
"reflect"
"regexp"
"testing"
)
func TestNewManager(t *testing.T) {
expectedManager := &Manager{
bannedUserList: map[string]struct{}{
"Privategrity": {},
"Privategrity_Corp": {},
},
bannedRegexList: []*regexp.Regexp{
regexp.MustCompile("xx"),
regexp.MustCompile("xx.*?network"),
},
}
bannedUserList := ""
for key := range expectedManager.bannedUserList {
bannedUserList += key + "\n"
}
bannedRegexList := ""
for _, regex := range expectedManager.bannedRegexList {
bannedRegexList += regex.String() + "\n"
}
m, err := NewManager(bannedUserList, bannedRegexList)
if err != nil {
t.Fatalf("NewManager error: %v", err)
}
if !reflect.DeepEqual(m, expectedManager) {
t.Errorf("Constructed manager does not match expected output."+
"\nExpected: %+v"+
"\nReceived: %+v", expectedManager, m)
}
}
func TestManager_IsBanned_GoodUsername(t *testing.T) {
bannedUserList := "Privategrity\nPrivategrity_Corp"
bannedRegexList := "xx\nxx.*?network"
m, err := NewManager(bannedUserList, bannedRegexList)
if err != nil {
t.Fatalf("NewManager error: %v", err)
}
goodUsernames := []string{
"john_doe",
"private",
"network",
"Privategrity!??!Corporation",
}
for _, goodUsername := range goodUsernames {
if m.IsBanned(goodUsername) {
t.Errorf("Username %q was recognized as banned when it should not be", goodUsername)
}
}
}
//
func TestManager_IsBanned_BadUsername(t *testing.T) {
bannedUserList := "Privategrity\nPrivategrity_Corp"
bannedRegexList := "xx\nxx.*?network"
m, err := NewManager(bannedUserList, bannedRegexList)
if err != nil {
t.Fatalf("NewManager error: %v", err)
}
badUsernames := []string{
"xxfsdfsdfsdklfjnetwork",
"Privategrity",
"Privategrity_Corp",
"exxplostion",
}
for _, badUsername := range badUsernames {
if !m.IsBanned(badUsername) {
t.Errorf("Username %q was not recognized as banned when it should be", badUsername)
}
}
}
......@@ -51,6 +51,12 @@ func InitParams(vip *viper.Viper) params.General {
jww.WARN.Printf("Failed to read banned user list: %v", err)
}
// Load banned user CSV
bannedRegexList, err := utils.ReadFile(viper.GetString("bannedRegexList"))
if err != nil {
jww.WARN.Printf("Failed to read banned regex list: %v", err)
}
// Only require proto user path if session does not exist
var protoUserJson []byte
protoUserPath := ""
......@@ -112,5 +118,6 @@ func InitParams(vip *viper.Viper) params.General {
Twilio: twilioparams,
ProtoUserJson: protoUserJson,
BannedUserList: string(bannedUserList),
BannedRegexList: string(bannedRegexList),
}
}
......@@ -9,6 +9,7 @@ import (
"gitlab.com/elixxir/client/interfaces/params"
"gitlab.com/elixxir/client/single"
"gitlab.com/elixxir/comms/mixmessages"
"gitlab.com/elixxir/user-discovery-bot/banned"
"gitlab.com/elixxir/user-discovery-bot/cmix"
"gitlab.com/elixxir/user-discovery-bot/io"
"gitlab.com/elixxir/user-discovery-bot/storage"
......@@ -19,7 +20,6 @@ import (
"gitlab.com/xx_network/primitives/ndf"
"gitlab.com/xx_network/primitives/utils"
"os"
"strings"
"time"
)
......@@ -44,17 +44,8 @@ var rootCmd = &cobra.Command{
initLog()
p := InitParams(viper.GetViper())
// Process CSV into a map
bannedUsers := make(map[string]struct{})
if p.BannedUserList != "" {
bannedUserList := strings.Split(p.BannedUserList, ",")
for _, bannedUser := range bannedUserList {
bannedUsers[bannedUser] = struct{}{}
}
}
// Initialize storage
storage, err := storage.NewStorage(p.DbUsername, p.DbPassword, p.DbName, p.DbAddress, p.DbPort, bannedUsers)
storage, err := storage.NewStorage(p.DbUsername, p.DbPassword, p.DbName, p.DbAddress, p.DbPort)
if err != nil {
jww.FATAL.Panicf("Failed to initialize storage interface: %+v", err)
}
......@@ -76,8 +67,13 @@ var rootCmd = &cobra.Command{
}
permCert, err := tls.ExtractPublicKey(cert)
bannedManager, err := banned.NewManager(p.BannedUserList, p.BannedRegexList)
if err != nil {
jww.FATAL.Panicf("Failed to construct ban manager: %v", err)
}
// Set up manager with the ability to contact permissioning
manager := io.NewManager(p.IO, &id.UDB, permCert, twilioManager, storage)
manager := io.NewManager(p.IO, &id.UDB, permCert, twilioManager, bannedManager, storage)
hostParams := connect.GetDefaultHostParams()
hostParams.AuthEnabled = false
permHost, err := manager.Comms.AddHost(&id.Permissioning,
......
......@@ -14,6 +14,7 @@ type General struct {
Ndf string
PermCert []byte
BannedUserList string
BannedRegexList string
Database
IO
......
......@@ -13,6 +13,7 @@ import (
"fmt"
pb "gitlab.com/elixxir/comms/mixmessages"
"gitlab.com/elixxir/comms/udb"
"gitlab.com/elixxir/user-discovery-bot/banned"
"gitlab.com/elixxir/user-discovery-bot/interfaces/params"
"gitlab.com/elixxir/user-discovery-bot/storage"
"gitlab.com/elixxir/user-discovery-bot/twilio"
......@@ -27,14 +28,17 @@ type Manager struct {
PermissioningPublicKey *rsa.PublicKey
Storage *storage.Storage
Twilio *twilio.Manager
Banned *banned.Manager
}
// Create a new UserDiscovery Manager given a set of Params
func NewManager(p params.IO, id *id.ID, permissioningCert *rsa.PublicKey, twilio *twilio.Manager, storage *storage.Storage) *Manager {
func NewManager(p params.IO, id *id.ID, permissioningCert *rsa.PublicKey,
twilio *twilio.Manager, banned *banned.Manager, storage *storage.Storage) *Manager {
m := &Manager{
Storage: storage,
PermissioningPublicKey: permissioningCert,
Twilio: twilio,
Banned: banned,
}
m.Comms = udb.StartServer(id, fmt.Sprintf("0.0.0.0:%s", p.Port),
newImplementation(m), p.Cert, p.Key)
......@@ -46,7 +50,7 @@ func newImplementation(m *Manager) *udb.Implementation {
impl := udb.NewImplementation()
impl.Functions.RegisterUser = func(registration *pb.UDBUserRegistration) (*messages.Ack, error) {
return registerUser(registration, m.PermissioningPublicKey, m.Storage)
return registerUser(registration, m.PermissioningPublicKey, m.Storage, m.Banned)
}
impl.Functions.RemoveUser = func(msg *pb.FactRemovalRequest) (*messages.Ack, error) {
......
package io
import (
"gitlab.com/elixxir/user-discovery-bot/banned"
"gitlab.com/elixxir/user-discovery-bot/interfaces/params"
"gitlab.com/elixxir/user-discovery-bot/storage"
"gitlab.com/elixxir/user-discovery-bot/twilio"
......@@ -17,8 +18,12 @@ func TestNewManager(t *testing.T) {
}
store := storage.NewTestDB(t)
tm := twilio.NewMockManager(store)
bannedManager, err := banned.NewManager("", "")
if err != nil {
t.Fatalf("Failed to construct ban manager: %v", err)
}
m := NewManager(p, id.NewIdFromString("zezima", id.User, t), nil, tm, store)
m := NewManager(p, id.NewIdFromString("zezima", id.User, t), nil, tm, bannedManager, store)
if m == nil || reflect.TypeOf(m) != reflect.TypeOf(&Manager{}) {
t.Errorf("Did not receive a manager")
}
......
......@@ -14,6 +14,7 @@ import (
"gitlab.com/elixxir/crypto/hash"
"gitlab.com/elixxir/crypto/registration"
"gitlab.com/elixxir/primitives/fact"
"gitlab.com/elixxir/user-discovery-bot/banned"
"gitlab.com/elixxir/user-discovery-bot/storage"
"gitlab.com/xx_network/comms/messages"
"gitlab.com/xx_network/crypto/signature/rsa"
......@@ -23,7 +24,7 @@ import (
// Endpoint which handles a users attempt to register
func registerUser(msg *pb.UDBUserRegistration, permPublicKey *rsa.PublicKey,
store *storage.Storage) (*messages.Ack, error) {
store *storage.Storage, bannedManager *banned.Manager) (*messages.Ack, error) {
// Nil checks
if msg == nil || msg.Frs == nil || msg.Frs.Fact == nil ||
......@@ -58,7 +59,7 @@ func registerUser(msg *pb.UDBUserRegistration, permPublicKey *rsa.PublicKey,
// }
// Check if the username is banned
if store.IsBanned(username) {
if bannedManager.IsBanned(username) {
// Return same error message as if the user was already taken
return &messages.Ack{}, errors.Errorf("Username %s is already taken. "+
"Please try again", username)
......
......@@ -15,6 +15,7 @@ import (
"gitlab.com/elixxir/crypto/hash"
"gitlab.com/elixxir/crypto/registration"
"gitlab.com/elixxir/primitives/fact"
"gitlab.com/elixxir/user-discovery-bot/banned"
"gitlab.com/elixxir/user-discovery-bot/storage"
"gitlab.com/xx_network/crypto/signature/rsa"
"gitlab.com/xx_network/crypto/tls"
......@@ -111,7 +112,12 @@ func TestRegisterUser(t *testing.T) {
t.FailNow()
}
_, err = registerUser(registerMsg, cert, store)
bannedManager, err := banned.NewManager("", "")
if err != nil {
t.Fatalf("Failed to construct ban manager: %v", err)
}
_, err = registerUser(registerMsg, cert, store, bannedManager)
if err != nil {
t.Errorf("Failed happy path: %v", err)
}
......@@ -186,9 +192,12 @@ func TestRegisterUser_Banned(t *testing.T) {
t.FailNow()
}
store.SetBanned(registerMsg.IdentityRegistration.Username, t)
bannedManager, err := banned.NewManager(registerMsg.IdentityRegistration.Username, "")
if err != nil {
t.Fatalf("Failed to construct ban manager: %v", err)
}
_, err = registerUser(registerMsg, cert, store)
_, err = registerUser(registerMsg, cert, store, bannedManager)
if err == nil {
t.Errorf("Failed happy path: %v", err)
}
......@@ -213,13 +222,19 @@ func TestRegisterUser_InvalidSignatures(t *testing.T) {
t.Fatalf("Could not parse precanned time: %v", err.Error())
}
// Construct dummy ban manager
bannedManager, err := banned.NewManager("", "")
if err != nil {
t.Fatalf("Failed to construct ban manager: %v", err)
}
// Set an invalid identity signature, check that error occurred
registerMsg, err := buildUserRegistrationMessage(clientId, clientKey, testTime, t)
if err != nil {
t.FailNow()
}
registerMsg.IdentitySignature = []byte("invalid")
_, err = registerUser(registerMsg, cert, store)
_, err = registerUser(registerMsg, cert, store, bannedManager)
if err == nil {
t.Errorf("Should not be able to verify identity signature: %v", err)
}
......@@ -230,7 +245,7 @@ func TestRegisterUser_InvalidSignatures(t *testing.T) {
t.FailNow()
}
registerMsg.Frs.FactSig = []byte("invalid")
_, err = registerUser(registerMsg, cert, store)
_, err = registerUser(registerMsg, cert, store, bannedManager)
if err == nil {
t.Errorf("Should not be able to verify fact signature: %v", err)
}
......@@ -241,7 +256,7 @@ func TestRegisterUser_InvalidSignatures(t *testing.T) {
t.FailNow()
}
registerMsg.PermissioningSignature = []byte("invalid")
_, err = registerUser(registerMsg, cert, store)
_, err = registerUser(registerMsg, cert, store, bannedManager)
if err == nil {
t.Errorf("Should not be able to verify permissioning signature: %v", err)
}
......@@ -265,13 +280,20 @@ func TestRegisterUser_InvalidMessage(t *testing.T) {
if err != nil {
t.Fatalf("Could not parse precanned time: %v", err.Error())
}
// Construct dummy ban manager
bannedManager, err := banned.NewManager("", "")
if err != nil {
t.Fatalf("Failed to construct ban manager: %v", err)
}
// Set an invalid message, check that error occurred
registerMsg, err := buildUserRegistrationMessage(clientId, clientKey, testTime, t)
if err != nil {
t.FailNow()
}
registerMsg = nil
_, err = registerUser(registerMsg, cert, store)
_, err = registerUser(registerMsg, cert, store, bannedManager)
if err == nil {
t.Errorf("Should not be able to handle nil message: %v", err)
}
......@@ -282,7 +304,7 @@ func TestRegisterUser_InvalidMessage(t *testing.T) {
t.FailNow()
}
registerMsg.Frs = nil
_, err = registerUser(registerMsg, cert, store)
_, err = registerUser(registerMsg, cert, store, bannedManager)
if err == nil {
t.Errorf("Should not be able to handle nil FactRegistration message: %v", err)
}
......@@ -293,7 +315,7 @@ func TestRegisterUser_InvalidMessage(t *testing.T) {
t.FailNow()
}
registerMsg.Frs.Fact = nil
_, err = registerUser(registerMsg, cert, store)
_, err = registerUser(registerMsg, cert, store, bannedManager)
if err == nil {
t.Errorf("Should not be able to handle nil Fact message: %v", err)
}
......@@ -304,7 +326,7 @@ func TestRegisterUser_InvalidMessage(t *testing.T) {
t.FailNow()
}
registerMsg.IdentityRegistration = nil
_, err = registerUser(registerMsg, cert, store)
_, err = registerUser(registerMsg, cert, store, bannedManager)
if err == nil {
t.Errorf("Should not be able to handle nil IdentityRegistration message: %v", err)
}
......
......@@ -18,41 +18,23 @@ import (
type Storage struct {
// Stored Database interface
database
bannedUserList map[string]struct{}
}
// Create a new Storage object wrapping a database interface
// Returns a Storage object and error
func NewStorage(username, password, dbName, address, port string, bannedUserList map[string]struct{}) (*Storage, error) {
func NewStorage(username, password, dbName, address, port string) (*Storage, error) {
db, err := newDatabase(username, password, dbName, address, port)
storage := &Storage{
database: db,
bannedUserList: bannedUserList,
}
return storage, err
}
// IsBanned checks if the username is in Storage's bannedUserList.
func (s *Storage) IsBanned(username string) bool {
_, exists := s.bannedUserList[username]
return exists
}
// SetBanned is a testing only helper function which sets a username
// in Storage's bannedUserList.
func (s *Storage) SetBanned(username string, t *testing.T) {
if t == nil {
jww.FATAL.Panic("Cannot use this outside of testing")
}
s.bannedUserList[username] = struct{}{}
}
func NewTestDB(t *testing.T) *Storage {
if t == nil {
jww.FATAL.Panic("Cannot use this outside of testing")
}
mockDb, err := NewStorage("", "", "", "", "11", make(map[string]struct{}))
mockDb, err := NewStorage("", "", "", "", "11")
if err != nil {
jww.FATAL.Panicf("Failed to init mock db: %+v", err)
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment