From 376278e2be580e235d30da5458a23ee1359f0467 Mon Sep 17 00:00:00 2001 From: Joakim Olsson Date: Sun, 12 Apr 2020 20:33:35 +0200 Subject: [PATCH] chore: modify event structure --- client.go | 93 +++++++++++++-------------- client_test.go | 168 +++++++++++++++++++++++++++++++++---------------- events.go | 62 ++++++++++++++++++ events_test.go | 110 ++++++++++++++++++++++++++++++++ 4 files changed, 330 insertions(+), 103 deletions(-) create mode 100644 events.go create mode 100644 events_test.go diff --git a/client.go b/client.go index 93c5dad..e3968d7 100644 --- a/client.go +++ b/client.go @@ -19,34 +19,11 @@ type CompanyPrivileges struct { Supplier bool `json:"supplier"` } -// PrivilegeAdded is the event sent when a new privilege is added -type PrivilegeAdded struct { - Email string `json:"email"` - CompanyID string `json:"companyId"` - Name string `json:"name"` - RegistrationNumber string `json:"registrationNumber"` - Admin bool `json:"admin"` - Company bool `json:"company"` - Consumer bool `json:"consumer"` - Time bool `json:"time"` - Invoicing bool `json:"invoicing"` - Accounting bool `json:"accounting"` - Supplier bool `json:"supplier"` -} - -// PrivilegeRemoved is the event sent when a privilege is removed -type PrivilegeRemoved struct { - Email string `json:"email"` - CompanyID string `json:"companyId"` - Name string `json:"name"` - RegistrationNumber string `json:"registrationNumber"` -} - // PrivilegeHandler processes PrivilegeAdded-events and fetches the initial set of privileges from an authz-service type PrivilegeHandler struct { client *http.Client baseURL string - privileges map[string]map[string]CompanyPrivileges + privileges map[string]map[string]*CompanyPrivileges } // OptsFunc is used to configure the PrivilegeHandler @@ -64,7 +41,7 @@ func New(opts ...OptsFunc) *PrivilegeHandler { handler := &PrivilegeHandler{ client: &http.Client{}, baseURL: "http://authz-service", - privileges: map[string]map[string]CompanyPrivileges{}, + privileges: map[string]map[string]*CompanyPrivileges{}, } for _, opt := range opts { opt(handler) @@ -94,11 +71,25 @@ func (h *PrivilegeHandler) Fetch() error { // Process privilege-related events and update the internal state func (h *PrivilegeHandler) Process(msg interface{}) bool { switch ev := msg.(type) { + case *UserAdded: + if priv, exists := h.privileges[ev.Email]; exists { + priv[ev.CompanyID] = &CompanyPrivileges{} + } else { + h.privileges[ev.Email] = map[string]*CompanyPrivileges{ + ev.CompanyID: {}, + } + } + return true + case *UserRemoved: + if priv, exists := h.privileges[ev.Email]; exists { + delete(priv, ev.CompanyID) + } + return true case *PrivilegeAdded: - h.setPrivileges(ev) + h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, true) return true case *PrivilegeRemoved: - h.removePrivileges(ev) + h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, false) return true default: fmt.Printf("Got unexpected message type (%s): '%+v'\n", reflect.TypeOf(msg).String(), msg) @@ -106,28 +97,32 @@ func (h *PrivilegeHandler) Process(msg interface{}) bool { } } -func (h *PrivilegeHandler) setPrivileges(ev *PrivilegeAdded) { - if priv, exists := h.privileges[ev.Email]; exists { - priv[ev.CompanyID] = CompanyPrivileges{ - Admin: ev.Admin, - Company: ev.Company, - Consumer: ev.Consumer, - Time: ev.Time, - Invoicing: ev.Invoicing, - Accounting: ev.Accounting, - Supplier: ev.Supplier, +func (h *PrivilegeHandler) setPrivileges(email, companyId string, privilege Privilege, set bool) { + if priv, exists := h.privileges[email]; exists { + if c, exists := priv[companyId]; exists { + switch privilege { + case PrivilegeAdmin: + c.Admin = set + case PrivilegeCompany: + c.Company = set + case PrivilegeConsumer: + c.Consumer = set + case PrivilegeTime: + c.Time = set + case PrivilegeInvoicing: + c.Invoicing = set + case PrivilegeAccounting: + c.Accounting = set + case PrivilegeSupplier: + c.Supplier = set + } + } else { + priv[companyId] = &CompanyPrivileges{} + h.setPrivileges(email, companyId, privilege, set) } } else { - h.privileges[ev.Email] = map[string]CompanyPrivileges{ - ev.CompanyID: {}, - } - h.setPrivileges(ev) - } -} - -func (h *PrivilegeHandler) removePrivileges(ev *PrivilegeRemoved) { - if priv, exists := h.privileges[ev.Email]; exists { - delete(priv, ev.CompanyID) + h.privileges[email] = map[string]*CompanyPrivileges{} + h.setPrivileges(email, companyId, privilege, set) } } @@ -136,7 +131,7 @@ func (h *PrivilegeHandler) CompaniesByUser(email string, predicate func(privileg var result []string if p, exists := h.privileges[email]; exists { for k, v := range p { - if predicate(v) { + if predicate(*v) { result = append(result, k) } } @@ -148,7 +143,7 @@ func (h *PrivilegeHandler) CompaniesByUser(email string, predicate func(privileg func (h *PrivilegeHandler) IsAllowed(email, companyID string, predicate func(privileges CompanyPrivileges) bool) bool { if p, exists := h.privileges[email]; exists { if v, exists := p[companyID]; exists { - return predicate(v) + return predicate(*v) } } diff --git a/client_test.go b/client_test.go index e2648ee..0c9bb5e 100644 --- a/client_test.go +++ b/client_test.go @@ -20,15 +20,9 @@ func TestPrivilegeHandler_Process_PrivilegeRemoved(t *testing.T) { handler := New(WithBaseURL("base")) result := handler.Process(&PrivilegeAdded{ - Email: "jim@example.org", - CompanyID: "abc-123", - Admin: true, - Company: true, - Consumer: true, - Time: true, - Invoicing: true, - Accounting: true, - Supplier: true, + Email: "jim@example.org", + CompanyID: "abc-123", + Privilege: PrivilegeAdmin, }) assert.True(t, result) @@ -39,10 +33,9 @@ func TestPrivilegeHandler_Process_PrivilegeRemoved(t *testing.T) { assert.Equal(t, []string{"abc-123"}, companies) result = handler.Process(&PrivilegeRemoved{ - Email: "jim@example.org", - CompanyID: "abc-123", - Name: "name", - RegistrationNumber: "reg-no", + Email: "jim@example.org", + CompanyID: "abc-123", + Privilege: PrivilegeAdmin, }) assert.True(t, result) @@ -53,6 +46,45 @@ func TestPrivilegeHandler_Process_PrivilegeRemoved(t *testing.T) { assert.Empty(t, companies) } +func TestPrivilegeHandler_Process_UserAdded_And_UserRemoved(t *testing.T) { + handler := New(WithBaseURL("base")) + + result := handler.Process(&UserAdded{ + Email: "jim@example.org", + CompanyID: "abc-123", + }) + assert.True(t, result) + + result = handler.Process(&UserAdded{ + Email: "jim@example.org", + CompanyID: "abc-456", + }) + assert.True(t, result) + + companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { + return true + }) + + assert.Equal(t, []string{"abc-123", "abc-456"}, companies) + + result = handler.Process(&UserRemoved{ + Email: "jim@example.org", + CompanyID: "abc-123", + }) + assert.True(t, result) + + result = handler.Process(&UserRemoved{ + Email: "jim@example.org", + CompanyID: "abc-456", + }) + assert.True(t, result) + + companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { + return true + }) + assert.Empty(t, companies) +} + func TestPrivilegeHandler_GetCompanies_Email_Not_Found(t *testing.T) { handler := New(WithBaseURL("base")) @@ -66,16 +98,9 @@ func TestPrivilegeHandler_GetCompanies_Email_Not_Found(t *testing.T) { func TestPrivilegeHandler_GetCompanies_No_Companies_Found(t *testing.T) { handler := New(WithBaseURL("base")) - result := handler.Process(&PrivilegeAdded{ - Email: "jim@example.org", - CompanyID: "abc-123", - Admin: false, - Company: false, - Consumer: false, - Time: false, - Invoicing: false, - Accounting: false, - Supplier: false, + result := handler.Process(&UserAdded{ + Email: "jim@example.org", + CompanyID: "abc-123", }) assert.True(t, result) @@ -84,21 +109,32 @@ func TestPrivilegeHandler_GetCompanies_No_Companies_Found(t *testing.T) { }) assert.Empty(t, companies) + + companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { + return true + }) + + assert.Equal(t, []string{"abc-123"}, companies) + + result = handler.Process(&UserRemoved{ + Email: "jim@example.org", + CompanyID: "abc-123", + }) + assert.True(t, result) + + companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { + return true + }) + assert.Empty(t, companies) } func TestPrivilegeHandler_GetCompanies_Company_With_Company_Access_Found(t *testing.T) { handler := New(WithBaseURL("base")) result := handler.Process(&PrivilegeAdded{ - Email: "jim@example.org", - CompanyID: "abc-123", - Admin: false, - Company: true, - Consumer: false, - Time: false, - Invoicing: false, - Accounting: false, - Supplier: false, + Email: "jim@example.org", + CompanyID: "abc-123", + Privilege: PrivilegeCompany, }) assert.True(t, result) @@ -113,20 +149,14 @@ func TestPrivilegeHandler_GetCompanies_Company_With_Admin_Access_Found(t *testin handler := New(WithBaseURL("base")) result := handler.Process(&PrivilegeAdded{ - Email: "jim@example.org", - CompanyID: "abc-123", - Admin: true, - Company: false, - Consumer: false, - Time: false, - Invoicing: false, - Accounting: false, - Supplier: false, + Email: "jim@example.org", + CompanyID: "abc-123", + Privilege: PrivilegeConsumer, }) assert.True(t, result) companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { - return privileges.Admin + return privileges.Consumer }) assert.Equal(t, []string{"abc-123"}, companies) @@ -146,19 +176,49 @@ func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T handler := New(WithBaseURL("base")) handler.Process(&PrivilegeAdded{ - Email: "jim@example.org", - CompanyID: "abc-123", - Admin: false, - Company: true, - Consumer: false, - Time: false, - Invoicing: false, - Accounting: false, - Supplier: false, + Email: "jim@example.org", + CompanyID: "abc-123", + Privilege: PrivilegeTime, }) result := handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { - return privileges.Company + return privileges.Time + }) + + assert.True(t, result) + + handler.Process(&PrivilegeAdded{ + Email: "jim@example.org", + CompanyID: "abc-123", + Privilege: PrivilegeInvoicing, + }) + + result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { + return privileges.Invoicing + }) + + assert.True(t, result) + + handler.Process(&PrivilegeAdded{ + Email: "jim@example.org", + CompanyID: "abc-123", + Privilege: PrivilegeAccounting, + }) + + result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { + return privileges.Accounting + }) + + assert.True(t, result) + + handler.Process(&PrivilegeAdded{ + Email: "jim@example.org", + CompanyID: "abc-123", + Privilege: PrivilegeSupplier, + }) + + result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { + return privileges.Supplier }) assert.True(t, result) @@ -175,7 +235,7 @@ func TestPrivilegeHandler_Fetch_Error_Response(t *testing.T) { server.Close() err := handler.Fetch() - assert.EqualError(t, err, fmt.Sprintf("Get http://%s/authz: dial tcp %s: connect: connection refused", baseURL, baseURL)) + assert.EqualError(t, err, fmt.Sprintf("Get \"http://%s/authz\": dial tcp %s: connect: connection refused", baseURL, baseURL)) } func TestPrivilegeHandler_Fetch_Error_Unreadable_Body(t *testing.T) { @@ -229,7 +289,7 @@ func TestPrivilegeHandler_Fetch_Valid(t *testing.T) { err := handler.Fetch() assert.NoError(t, err) - expectedPrivileges := map[string]map[string]CompanyPrivileges{ + expectedPrivileges := map[string]map[string]*CompanyPrivileges{ "jim@example.org": { "00010203-0405-4607-8809-0a0b0c0d0e0f": { Admin: false, diff --git a/events.go b/events.go new file mode 100644 index 0000000..be2f331 --- /dev/null +++ b/events.go @@ -0,0 +1,62 @@ +package client + +// UserAdded is the event sent when a new user is added to a company +type UserAdded struct { + Email string `json:"email"` + CompanyID string `json:"companyId"` +} + +// UserRemoved is the event sent when a user is removed from a company +type UserRemoved struct { + Email string `json:"email"` + CompanyID string `json:"companyId"` +} + +// Privilege is an enumeration of all available privileges +type Privilege string + +const ( + PrivilegeAdmin = "ADMIN" + PrivilegeCompany = "COMPANY" + PrivilegeConsumer = "CONSUMER" + PrivilegeTime = "TIME" + PrivilegeInvoicing = "INVOICING" + PrivilegeAccounting = "ACCOUNTING" + PrivilegeSupplier = "SUPPLIER" +) + +var AllPrivilege = []Privilege{ + PrivilegeAdmin, + PrivilegeCompany, + PrivilegeConsumer, + PrivilegeTime, + PrivilegeInvoicing, + PrivilegeAccounting, + PrivilegeSupplier, +} + +func (e Privilege) IsValid() bool { + switch e { + case PrivilegeAdmin, PrivilegeCompany, PrivilegeConsumer, PrivilegeTime, PrivilegeInvoicing, PrivilegeAccounting, PrivilegeSupplier: + return true + } + return false +} + +func (e Privilege) String() string { + return string(e) +} + +// PrivilegeAdded is the event sent when a new privilege is added +type PrivilegeAdded struct { + Email string `json:"email"` + CompanyID string `json:"companyId"` + Privilege Privilege `json:"privilege"` +} + +// PrivilegeRemoved is the event sent when a privilege is removed +type PrivilegeRemoved struct { + Email string `json:"email"` + CompanyID string `json:"companyId"` + Privilege Privilege `json:"privilege"` +} diff --git a/events_test.go b/events_test.go new file mode 100644 index 0000000..bb4a2aa --- /dev/null +++ b/events_test.go @@ -0,0 +1,110 @@ +package client + +import "testing" + +func TestPrivilege_IsValid(t *testing.T) { + tests := []struct { + name string + e Privilege + want bool + }{ + { + name: "Admin", + e: "ADMIN", + want: true, + }, + { + name: "Company", + e: "COMPANY", + want: true, + }, + { + name: "Consumer", + e: "CONSUMER", + want: true, + }, + { + name: "Time", + e: "TIME", + want: true, + }, + { + name: "Invoicing", + e: "INVOICING", + want: true, + }, + { + name: "Accounting", + e: "ACCOUNTING", + want: true, + }, + { + name: "Supplier", + e: "SUPPLIER", + want: true, + }, + { + name: "Invalid", + e: "BLUTTI", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.IsValid(); got != tt.want { + t.Errorf("IsValid() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPrivilege_String(t *testing.T) { + tests := []struct { + name string + e Privilege + want string + }{ + { + name: "Admin", + e: "ADMIN", + want: "ADMIN", + }, + { + name: "Company", + e: "COMPANY", + want: "COMPANY", + }, + { + name: "Consumer", + e: "CONSUMER", + want: "CONSUMER", + }, + { + name: "Time", + e: "TIME", + want: "TIME", + }, + { + name: "Invoicing", + e: "INVOICING", + want: "INVOICING", + }, + { + name: "Accounting", + e: "ACCOUNTING", + want: "ACCOUNTING", + }, + { + name: "Supplier", + e: "SUPPLIER", + want: "SUPPLIER", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +}