From b22c67dc0cca44127d80d3cfa50b3b4010747057 Mon Sep 17 00:00:00 2001
From: Shrey Garg <sgarg48@asu.edu>
Date: Mon, 13 Jun 2022 10:10:53 +0000
Subject: [PATCH] "Resolve "Implement filesystem stores"

See merge request danet/gosdn!322
---
 .gitignore                                    |   1 +
 controller/northbound/server/role.go          |   1 -
 controller/nucleus/deviceFilesystemStore.go   |   3 +-
 .../nucleus/deviceFilesystemStore_test.go     |   5 +-
 controller/nucleus/errors/errors.go           |   2 +-
 controller/nucleus/pndFilesystemStore.go      |   5 +-
 controller/nucleus/pndFilesystemStore_test.go |   5 +-
 controller/nucleus/sbiFilesystemStore.go      |   3 +-
 controller/nucleus/sbiFilesystemStore_test.go |   5 +-
 controller/rbac/roleFileSystemStore.go        | 166 +++++++++++++++
 controller/rbac/roleFileSystemStore_test.go   | 185 ++++++++++++++++
 controller/rbac/roleStore.go                  |   7 +-
 controller/rbac/userFileSystemStore.go        | 166 +++++++++++++++
 controller/rbac/userFileSystemStore_test.go   | 201 ++++++++++++++++++
 controller/rbac/userStore.go                  |   7 +-
 .../filesystem-settings.go                    |   6 +-
 16 files changed, 739 insertions(+), 29 deletions(-)
 create mode 100644 controller/rbac/roleFileSystemStore.go
 create mode 100644 controller/rbac/roleFileSystemStore_test.go
 create mode 100644 controller/rbac/userFileSystemStore.go
 create mode 100644 controller/rbac/userFileSystemStore_test.go
 rename controller/{nucleus/filesystem => store}/filesystem-settings.go (61%)

diff --git a/.gitignore b/.gitignore
index 6c56d29b9..5fa2ffa0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,7 @@ controller/api/stores_testing/**
 controller/northbound/server/stores_testing/**
 controller/nucleus/stores_testing/**
 controller/nucleus/**/gostructs.go
+controller/rbac/stores_testing/**
 
 # stores
 stores/*.json
diff --git a/controller/northbound/server/role.go b/controller/northbound/server/role.go
index fd742a96e..4cb05b6e1 100644
--- a/controller/northbound/server/role.go
+++ b/controller/northbound/server/role.go
@@ -117,7 +117,6 @@ func (r Role) UpdateRoles(ctx context.Context, request *apb.UpdateRolesRequest)
 		if err != nil {
 			return nil, handleRPCError(labels, err)
 		}
-
 		_, err = r.roleService.Get(store.Query{ID: rid})
 		if err != nil {
 			return nil, status.Errorf(codes.Canceled, "role not found %v", err)
diff --git a/controller/nucleus/deviceFilesystemStore.go b/controller/nucleus/deviceFilesystemStore.go
index fc57e48cf..0d9db1f89 100644
--- a/controller/nucleus/deviceFilesystemStore.go
+++ b/controller/nucleus/deviceFilesystemStore.go
@@ -8,7 +8,6 @@ import (
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
-	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
 )
@@ -23,7 +22,7 @@ type FilesystemDeviceStore struct {
 
 // NewFilesystemDeviceStore returns a filesystem implementation for a pnd store.
 func NewFilesystemDeviceStore(pndUUID uuid.UUID) device.Store {
-	deviceFilenameForUUID := store.GetStoreFilenameForUUID(pndUUID, filesystem.DeviceFilenameSuffix)
+	deviceFilenameForUUID := store.GetStoreFilenameForUUID(pndUUID, store.DeviceFilenameSuffix)
 
 	store.EnsureFilesystemStorePathExists(deviceFilenameForUUID)
 	return &FilesystemDeviceStore{
diff --git a/controller/nucleus/deviceFilesystemStore_test.go b/controller/nucleus/deviceFilesystemStore_test.go
index e3179ad31..520a30a52 100644
--- a/controller/nucleus/deviceFilesystemStore_test.go
+++ b/controller/nucleus/deviceFilesystemStore_test.go
@@ -9,14 +9,13 @@ import (
 	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
 	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
-	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
 )
 
 func ensureDeviceFilesForTestAreRemoved() {
-	store.EnsureFilesystemStorePathExists(filesystem.DeviceFilenameSuffix)
-	wildcartFilename := "*-" + filesystem.DeviceFilenameSuffix
+	store.EnsureFilesystemStorePathExists(store.DeviceFilenameSuffix)
+	wildcartFilename := "*-" + store.DeviceFilenameSuffix
 	path := store.GetCompletePathToFileStore(wildcartFilename)
 
 	files, err := filepath.Glob(path)
diff --git a/controller/nucleus/errors/errors.go b/controller/nucleus/errors/errors.go
index 8c30a9f42..54be6a617 100644
--- a/controller/nucleus/errors/errors.go
+++ b/controller/nucleus/errors/errors.go
@@ -31,7 +31,7 @@ type ErrNotFound struct {
 }
 
 func (e *ErrNotFound) Error() string {
-	return fmt.Sprintf("ID: %v or Name: %vnot found", e.ID, e.Name)
+	return fmt.Sprintf("ID: %v or Name: %v not found", e.ID, e.Name)
 }
 
 // ErrAlreadyExists implements the Error interface and is called if a specific ID
diff --git a/controller/nucleus/pndFilesystemStore.go b/controller/nucleus/pndFilesystemStore.go
index 61a42808c..beba95994 100644
--- a/controller/nucleus/pndFilesystemStore.go
+++ b/controller/nucleus/pndFilesystemStore.go
@@ -9,7 +9,6 @@ import (
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
-	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
@@ -28,10 +27,10 @@ type FilesystemPndStore struct {
 
 // NewFilesystemPndStore returns a filesystem implementation for a pnd store.
 func NewFilesystemPndStore() FilesystemPndStore {
-	store.EnsureFilesystemStorePathExists(filesystem.PndFilename)
+	store.EnsureFilesystemStorePathExists(store.PndFilename)
 	return FilesystemPndStore{
 		pendingChannels: make(map[uuid.UUID]chan device.Details),
-		pathToPndFile:   store.GetCompletePathToFileStore(filesystem.PndFilename),
+		pathToPndFile:   store.GetCompletePathToFileStore(store.PndFilename),
 		fileMutex:       sync.Mutex{},
 	}
 }
diff --git a/controller/nucleus/pndFilesystemStore_test.go b/controller/nucleus/pndFilesystemStore_test.go
index 3480c6a53..29119f9f3 100644
--- a/controller/nucleus/pndFilesystemStore_test.go
+++ b/controller/nucleus/pndFilesystemStore_test.go
@@ -6,14 +6,13 @@ import (
 	"testing"
 
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
-	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
 )
 
 func ensurePndFileForTestIsRemoved() {
-	store.EnsureFilesystemStorePathExists(filesystem.PndFilename)
-	path := store.GetCompletePathToFileStore(filesystem.PndFilename)
+	store.EnsureFilesystemStorePathExists(store.PndFilename)
+	path := store.GetCompletePathToFileStore(store.PndFilename)
 
 	err := os.Remove(path)
 	if err != nil {
diff --git a/controller/nucleus/sbiFilesystemStore.go b/controller/nucleus/sbiFilesystemStore.go
index 52d0f9894..64fdeb481 100644
--- a/controller/nucleus/sbiFilesystemStore.go
+++ b/controller/nucleus/sbiFilesystemStore.go
@@ -7,7 +7,6 @@ import (
 
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
-	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 
 	"github.com/google/uuid"
@@ -23,7 +22,7 @@ type FilesystemSbiStore struct {
 
 // NewFilesystemSbiStore returns a filesystem implementation for a pnd store.
 func NewFilesystemSbiStore(pndUUID uuid.UUID) southbound.Store {
-	sbiFilenameForUUID := store.GetStoreFilenameForUUID(pndUUID, filesystem.SbiFilenameSuffix)
+	sbiFilenameForUUID := store.GetStoreFilenameForUUID(pndUUID, store.SbiFilenameSuffix)
 
 	store.EnsureFilesystemStorePathExists(sbiFilenameForUUID)
 	return &FilesystemSbiStore{
diff --git a/controller/nucleus/sbiFilesystemStore_test.go b/controller/nucleus/sbiFilesystemStore_test.go
index 38ea67d47..4ec3de489 100644
--- a/controller/nucleus/sbiFilesystemStore_test.go
+++ b/controller/nucleus/sbiFilesystemStore_test.go
@@ -8,14 +8,13 @@ import (
 
 	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
-	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
 )
 
 func ensureSbiFilesForTestAreRemoved() {
-	store.EnsureFilesystemStorePathExists(filesystem.SbiFilenameSuffix)
-	wildcartFilename := "*-" + filesystem.SbiFilenameSuffix
+	store.EnsureFilesystemStorePathExists(store.SbiFilenameSuffix)
+	wildcartFilename := "*-" + store.SbiFilenameSuffix
 	path := store.GetCompletePathToFileStore(wildcartFilename)
 
 	files, err := filepath.Glob(path)
diff --git a/controller/rbac/roleFileSystemStore.go b/controller/rbac/roleFileSystemStore.go
new file mode 100644
index 000000000..02a2baaee
--- /dev/null
+++ b/controller/rbac/roleFileSystemStore.go
@@ -0,0 +1,166 @@
+package rbac
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"sync"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+)
+
+// FileSystemRoleStore is the filesystem implementation of the role store
+type FileSystemRoleStore struct {
+	fileMutex      sync.Mutex
+	pathToRoleFile string
+}
+
+// NewFileSystemRoleStore returns a filesystem implementation for a role store.
+func NewFileSystemRoleStore() rbac.RoleStore {
+	store.EnsureFilesystemStorePathExists(store.RoleFilename)
+	return &FileSystemRoleStore{
+		fileMutex:      sync.Mutex{},
+		pathToRoleFile: store.GetCompletePathToFileStore(store.RoleFilename),
+	}
+}
+
+func (s *FileSystemRoleStore) readAllRolesFromFile() ([]rbac.LoadedRole, error) {
+	var loadedRoles []rbac.LoadedRole
+	content, err := ioutil.ReadFile(s.pathToRoleFile)
+	if err != nil {
+		return nil, err
+	}
+
+	err = json.Unmarshal(content, &loadedRoles)
+	if err != nil {
+		return nil, err
+	}
+
+	return loadedRoles, nil
+}
+
+func (s *FileSystemRoleStore) writeAllRolesToFile(Roles []rbac.LoadedRole) error {
+	serializedData, err := json.Marshal(Roles)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(s.pathToRoleFile, serializedData, 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Add adds a Role to the Role store
+func (s *FileSystemRoleStore) Add(RoleToAdd rbac.Role) error {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	roles, err := s.readAllRolesFromFile()
+	if err != nil {
+		return err
+	}
+
+	var loadedRole rbac.LoadedRole
+	loadedRole, err = store.TransformObjectToLoadedObject[rbac.Role, rbac.LoadedRole](RoleToAdd)
+	if err != nil {
+		return err
+	}
+
+	roles = append(roles, loadedRole)
+
+	err = s.writeAllRolesToFile(roles)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+//Delete deletes a Role from the Role store
+func (s *FileSystemRoleStore) Delete(RoleToDelete rbac.Role) error {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	roles, err := s.readAllRolesFromFile()
+	if err != nil {
+		return err
+	}
+
+	for i, role := range roles {
+		if role.ID == RoleToDelete.ID().String() {
+			//remove item from slice
+			roles[i] = roles[len(roles)-1]
+			roles = roles[:len(roles)-1]
+
+			err = s.writeAllRolesToFile(roles)
+			if err != nil {
+				return err
+			}
+
+			return nil
+		}
+	}
+
+	return &errors.ErrNotFound{ID: RoleToDelete.ID}
+}
+
+//Get takes a Roles ID and return the Role if found
+func (s *FileSystemRoleStore) Get(query store.Query) (rbac.LoadedRole, error) {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	var role rbac.LoadedRole
+	roles, err := s.readAllRolesFromFile()
+	if err != nil {
+		return role, err
+	}
+
+	for _, role := range roles {
+		if role.ID == query.ID.String() || role.RoleName == query.Name {
+			return role, nil
+		}
+	}
+
+	return role, &errors.ErrNotFound{ID: query.ID, Name: query.Name}
+}
+
+// GetAll returns all the Roles
+func (s *FileSystemRoleStore) GetAll() ([]rbac.LoadedRole, error) {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	Roles, err := s.readAllRolesFromFile()
+	return Roles, err
+}
+
+//Update updates an exsisting Role
+func (s *FileSystemRoleStore) Update(roleToUpdate rbac.Role) error {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	loadedRole, err := store.TransformObjectToLoadedObject[rbac.Role, rbac.LoadedRole](roleToUpdate)
+	if err != nil {
+		return err
+	}
+	Roles, err := s.readAllRolesFromFile()
+	if err != nil {
+		return err
+	}
+
+	for i, Role := range Roles {
+		if Role.ID == roleToUpdate.ID().String() {
+			Roles[i] = loadedRole
+			err = s.writeAllRolesToFile(Roles)
+			if err != nil {
+				return err
+			}
+			return nil
+		}
+	}
+
+	return &errors.ErrNotFound{ID: roleToUpdate.ID().String()}
+}
diff --git a/controller/rbac/roleFileSystemStore_test.go b/controller/rbac/roleFileSystemStore_test.go
new file mode 100644
index 000000000..cfc033ee8
--- /dev/null
+++ b/controller/rbac/roleFileSystemStore_test.go
@@ -0,0 +1,185 @@
+package rbac
+
+import (
+	"log"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+)
+
+func ensureRoleFilesForTestAreRemoved() {
+	store.EnsureFilesystemStorePathExists(store.RoleFilename)
+	path := store.GetCompletePathToFileStore(store.RoleFilename)
+
+	files, err := filepath.Glob(path)
+
+	if err != nil {
+		log.Println(err)
+	}
+	for _, f := range files {
+		if err := os.Remove(f); err != nil {
+			log.Println(err)
+		}
+	}
+}
+
+func TestFileSystemRoleStore_Add(t *testing.T) {
+	ensureRoleFilesForTestAreRemoved()
+
+	type args struct {
+		RoleToAdd rbac.Role
+	}
+	var AddRole rbac.Role
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{"AddRole1", args{AddRole}, false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewRoleStore()
+			if err := s.Add(tt.args.RoleToAdd); (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemRoleStore.Add() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestFileSystemRoleStore_Delete(t *testing.T) {
+	ensureRoleFilesForTestAreRemoved()
+
+	type args struct {
+		RoleToDelete rbac.Role
+	}
+	var idtest uuid.UUID
+	addRole := NewRole(idtest, "testRole", "role", []string{})
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{"AddRole1", args{
+			addRole,
+		}, false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewRoleStore()
+			s.Add(addRole)
+			if err := s.Delete(tt.args.RoleToDelete); (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemRoleStore.Delete() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestFileSystemRoleStore_Get(t *testing.T) {
+	ensureRoleFilesForTestAreRemoved()
+
+	type args struct {
+		query store.Query
+	}
+	var idtest uuid.UUID
+	var arrTest []string
+	addRole := NewRole(idtest, "testRole", "role", arrTest)
+	tests := []struct {
+		name    string
+		args    args
+		want    rbac.LoadedRole
+		wantErr bool
+	}{
+		{"AddRole1", args{
+			store.Query{
+				ID: idtest, Name: "test",
+			},
+		},
+			rbac.LoadedRole{ID: idtest.String(), RoleName: "testRole", Description: "role", Permissions: arrTest},
+			false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewRoleStore()
+			s.Add(addRole)
+			got, err := s.Get(tt.args.query)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemRoleStore.Get() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("FileSystemRoleStore.Get() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestFileSystemRoleStore_GetAll(t *testing.T) {
+	ensureRoleFilesForTestAreRemoved()
+	var idtest uuid.UUID
+	var arrTest []string
+	addRole1 := NewRole(idtest, "testRole1", "role1", arrTest)
+	addRole2 := NewRole(idtest, "testRole2", "role2", arrTest)
+	addRole3 := NewRole(idtest, "testRole3", "role3", arrTest)
+	tests := []struct {
+		name    string
+		want    []rbac.LoadedRole
+		wantErr bool
+	}{
+		{
+			"testRole",
+			[]rbac.LoadedRole{{ID: idtest.String(), RoleName: "testRole1", Description: "role1", Permissions: arrTest}, {ID: idtest.String(), RoleName: "testRole2", Description: "role2", Permissions: arrTest}, {ID: idtest.String(), RoleName: "testRole3", Description: "role3", Permissions: arrTest}},
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewRoleStore()
+			s.Add(addRole1)
+			s.Add(addRole2)
+			s.Add(addRole3)
+			got, err := s.GetAll()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemRoleStore.GetAll() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("FileSystemRoleStore.GetAll() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestFileSystemRoleStore_Update(t *testing.T) {
+	ensureRoleFilesForTestAreRemoved()
+
+	var idtest uuid.UUID
+	var arrTest []string
+	addRole1 := NewRole(idtest, "testRole1", "role1", arrTest)
+	type args struct {
+		roleToUpdate rbac.Role
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{"AddRole1", args{
+			addRole1,
+		}, false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewRoleStore()
+			s.Add(addRole1)
+			if err := s.Update(tt.args.roleToUpdate); (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemRoleStore.Update() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
diff --git a/controller/rbac/roleStore.go b/controller/rbac/roleStore.go
index 4482168fa..35b9a5127 100644
--- a/controller/rbac/roleStore.go
+++ b/controller/rbac/roleStore.go
@@ -19,13 +19,10 @@ func NewRoleStore() rbac.RoleStore {
 	storeMode := store.GetStoreMode()
 
 	switch storeMode {
-	case store.Filesystem:
-		return NewMemoryRoleStore()
 	case store.Database:
 		return &DatabaseRoleStore{"role.json"}
-	case store.Memory:
-		return NewMemoryRoleStore()
 	default:
-		return nil
+		store := NewFileSystemRoleStore()
+		return store
 	}
 }
diff --git a/controller/rbac/userFileSystemStore.go b/controller/rbac/userFileSystemStore.go
new file mode 100644
index 000000000..11f2184c7
--- /dev/null
+++ b/controller/rbac/userFileSystemStore.go
@@ -0,0 +1,166 @@
+package rbac
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"sync"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+)
+
+// FileSystemUserStore is the filesystem implementation of the user store
+type FileSystemUserStore struct {
+	fileMutex      sync.Mutex
+	pathToUserFile string
+}
+
+// NewFileSystemUserStore returns a filesystem implementation for a user store.
+func NewFileSystemUserStore() rbac.UserStore {
+	store.EnsureFilesystemStorePathExists(store.UserFilename)
+	return &FileSystemUserStore{
+		fileMutex:      sync.Mutex{},
+		pathToUserFile: store.GetCompletePathToFileStore(store.UserFilename),
+	}
+}
+
+func (s *FileSystemUserStore) readAllUsersFromFile() ([]rbac.LoadedUser, error) {
+	var loadedUsers []rbac.LoadedUser
+	content, err := ioutil.ReadFile(s.pathToUserFile)
+	if err != nil {
+		return nil, err
+	}
+
+	err = json.Unmarshal(content, &loadedUsers)
+	if err != nil {
+		return nil, err
+	}
+
+	return loadedUsers, nil
+}
+
+func (s *FileSystemUserStore) writeAllUsersToFile(users []rbac.LoadedUser) error {
+	serializedData, err := json.Marshal(users)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(s.pathToUserFile, serializedData, 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Add adds a User to the User store
+func (s *FileSystemUserStore) Add(UserToAdd rbac.User) error {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	users, err := s.readAllUsersFromFile()
+	if err != nil {
+		return err
+	}
+
+	var loadedUser rbac.LoadedUser
+	loadedUser, err = store.TransformObjectToLoadedObject[rbac.User, rbac.LoadedUser](UserToAdd)
+	if err != nil {
+		return err
+	}
+
+	users = append(users, loadedUser)
+
+	err = s.writeAllUsersToFile(users)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+//Delete deletes a User from the User store
+func (s *FileSystemUserStore) Delete(userToDelete rbac.User) error {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	users, err := s.readAllUsersFromFile()
+	if err != nil {
+		return err
+	}
+
+	for i, user := range users {
+		if user.ID == userToDelete.ID().String() {
+			//remove item from slice
+			users[i] = users[len(users)-1]
+			users = users[:len(users)-1]
+
+			err = s.writeAllUsersToFile(users)
+			if err != nil {
+				return err
+			}
+
+			return nil
+		}
+	}
+
+	return &errors.ErrNotFound{ID: userToDelete.ID}
+}
+
+//Get takes a Users ID and return the User if found
+func (s *FileSystemUserStore) Get(query store.Query) (rbac.LoadedUser, error) {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	var user rbac.LoadedUser
+
+	users, err := s.readAllUsersFromFile()
+	if err != nil {
+		return user, err
+	}
+
+	for _, user := range users {
+		if user.ID == query.ID.String() || user.UserName == query.Name {
+			return user, nil
+		}
+	}
+	return user, &errors.ErrNotFound{ID: query.ID, Name: query.Name}
+}
+
+// GetAll returns all the Users
+func (s *FileSystemUserStore) GetAll() ([]rbac.LoadedUser, error) {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	Users, err := s.readAllUsersFromFile()
+	return Users, err
+}
+
+//Update updates an exsisting user
+func (s *FileSystemUserStore) Update(userToUpdate rbac.User) error {
+	s.fileMutex.Lock()
+	defer s.fileMutex.Unlock()
+
+	loadedUser, err := store.TransformObjectToLoadedObject[rbac.User, rbac.LoadedUser](userToUpdate)
+	if err != nil {
+		return err
+	}
+	users, err := s.readAllUsersFromFile()
+	if err != nil {
+		return err
+	}
+
+	for i, user := range users {
+		if user.ID == userToUpdate.ID().String() {
+			users[i] = loadedUser
+			err = s.writeAllUsersToFile(users)
+			if err != nil {
+				return err
+			}
+			return nil
+		}
+	}
+
+	return &errors.ErrNotFound{ID: userToUpdate.ID().String()}
+}
diff --git a/controller/rbac/userFileSystemStore_test.go b/controller/rbac/userFileSystemStore_test.go
new file mode 100644
index 000000000..4261e982e
--- /dev/null
+++ b/controller/rbac/userFileSystemStore_test.go
@@ -0,0 +1,201 @@
+package rbac
+
+import (
+	"log"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+)
+
+func ensureUserFilesForTestAreRemoved() {
+	store.EnsureFilesystemStorePathExists(store.UserFilename)
+	path := store.GetCompletePathToFileStore(store.UserFilename)
+
+	files, err := filepath.Glob(path)
+
+	if err != nil {
+		log.Println(err)
+	}
+	for _, f := range files {
+		if err := os.Remove(f); err != nil {
+			log.Println(err)
+		}
+	}
+}
+
+func TestFileSystemUserStore_Add(t *testing.T) {
+	ensureUserFilesForTestAreRemoved()
+
+	type args struct {
+		UserToAdd rbac.User
+	}
+	var idtest uuid.UUID
+	var role map[string]string
+	testingUser := NewUser(idtest, "testUser", role, "xyz", "svsvsfbdwbwbev", "svswvasfbw")
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			"testUser",
+			args{testingUser},
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewUserStore()
+
+			if err := s.Add(tt.args.UserToAdd); (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemUserStore.Add() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestFileSystemUserStore_Delete(t *testing.T) {
+	ensureUserFilesForTestAreRemoved()
+
+	type args struct {
+		UserToDelete rbac.User
+	}
+	var idtest uuid.UUID
+	var role map[string]string
+	testingUser := NewUser(idtest, "", role, "xyz", "svsvsfbdwbwbev", "svswvasfbw")
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			"testUser",
+			args{testingUser},
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewUserStore()
+			s.Add(testingUser)
+			if err := s.Delete(tt.args.UserToDelete); (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemUserStore.Delete() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestFileSystemUserStore_Get(t *testing.T) {
+	ensureUserFilesForTestAreRemoved()
+
+	type args struct {
+		query store.Query
+	}
+	var idtest uuid.UUID
+	var role map[string]string
+	testingUser := NewUser(idtest, "", role, "xyz", "svsvsfbdwbwbev", "svswvasfbw")
+	tests := []struct {
+		name    string
+		args    args
+		want    rbac.LoadedUser
+		wantErr bool
+	}{
+		{
+			"testUser",
+			args{
+				store.Query{ID: idtest, Name: "test"},
+			},
+			rbac.LoadedUser{ID: idtest.String(), UserName: "", Roles: role, Password: "xyz", Token: "svsvsfbdwbwbev", Salt: "svswvasfbw"},
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewUserStore()
+			s.Add(testingUser)
+			got, err := s.Get(tt.args.query)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemUserStore.Get() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("FileSystemUserStore.Get() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestFileSystemUserStore_GetAll(t *testing.T) {
+	ensureUserFilesForTestAreRemoved()
+
+	var idtest uuid.UUID
+	var role map[string]string
+	testingUser1 := NewUser(idtest, "", role, "xyz", "svsvsfbdwbwbevasf", "svswvasfbwasv")
+	testingUser2 := NewUser(idtest, "", role, "abc", "svsvsfbdwbwbevsav", "svswvasfbwadf")
+	testingUser3 := NewUser(idtest, "", role, "lmn", "svsvsfbdwbwbevscv", "svswvasfbwasd")
+	tests := []struct {
+		name    string
+		want    []rbac.LoadedUser
+		wantErr bool
+	}{
+		{
+			"testUser",
+			[]rbac.LoadedUser{{ID: idtest.String(), UserName: "", Roles: role, Password: "xyz", Token: "svsvsfbdwbwbevasf", Salt: "svswvasfbwasv"}, {ID: idtest.String(), UserName: "", Roles: role, Password: "abc", Token: "svsvsfbdwbwbevsav", Salt: "svswvasfbwadf"}, {ID: idtest.String(), UserName: "", Roles: role, Password: "lmn", Token: "svsvsfbdwbwbevscv", Salt: "svswvasfbwasd"}},
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewUserStore()
+			s.Add(testingUser1)
+			s.Add(testingUser2)
+			s.Add(testingUser3)
+			got, err := s.GetAll()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemUserStore.GetAll() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("FileSystemUserStore.GetAll() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestFileSystemUserStore_Update(t *testing.T) {
+	ensureUserFilesForTestAreRemoved()
+
+	type args struct {
+		userToUpdate rbac.User
+	}
+	var idtest uuid.UUID
+	var role map[string]string
+	testingUser := NewUser(idtest, "", role, "xyz", "svsvsfbdwbwbev", "svswvasfbw")
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			"testUser",
+			args{
+				testingUser,
+			},
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewUserStore()
+			s.Add(testingUser)
+			if err := s.Update(tt.args.userToUpdate); (err != nil) != tt.wantErr {
+				t.Errorf("FileSystemUserStore.Update() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
diff --git a/controller/rbac/userStore.go b/controller/rbac/userStore.go
index 1524abe9c..e157d8bf0 100644
--- a/controller/rbac/userStore.go
+++ b/controller/rbac/userStore.go
@@ -19,13 +19,10 @@ func NewUserStore() rbac.UserStore {
 	storeMode := store.GetStoreMode()
 
 	switch storeMode {
-	case store.Filesystem:
-		return NewMemoryUserStore()
 	case store.Database:
 		return &DatabaseUserStore{"user.json"}
-	case store.Memory:
-		return NewMemoryUserStore()
 	default:
-		return nil
+		store := NewFileSystemUserStore()
+		return store
 	}
 }
diff --git a/controller/nucleus/filesystem/filesystem-settings.go b/controller/store/filesystem-settings.go
similarity index 61%
rename from controller/nucleus/filesystem/filesystem-settings.go
rename to controller/store/filesystem-settings.go
index 8df6e31e7..5a23dd0b9 100644
--- a/controller/nucleus/filesystem/filesystem-settings.go
+++ b/controller/store/filesystem-settings.go
@@ -1,4 +1,4 @@
-package filesystem
+package store
 
 const (
 	// PndFilename is the name of the file where the pnds are stored
@@ -7,4 +7,8 @@ const (
 	DeviceFilenameSuffix string = "deviceStore.json"
 	// SbiFilenameSuffix is the suffix of the file where the sbis are stored
 	SbiFilenameSuffix string = "sbiStore.json"
+	// UserFilename is the name of the file where the users are stored
+	UserFilename string = "userStore.json"
+	// RoleFilename is the name of the file where the roles are stored
+	RoleFilename string = "roleStore.json"
 )
-- 
GitLab