af93e418f4
Replace read lock with write lock in client.go to ensure thread safety during the unmarshalling of JSON data. This prevents concurrent read access and potential data races, improving the integrity of the privileges data structure.
183 lines
4.9 KiB
Go
183 lines
4.9 KiB
Go
package client
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"reflect"
|
|
"sync"
|
|
|
|
"github.com/sparetimecoders/goamqp"
|
|
)
|
|
|
|
// CompanyPrivileges contains the privileges for a combination of email address and company id
|
|
type CompanyPrivileges struct {
|
|
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"`
|
|
Salary bool `json:"salary"`
|
|
}
|
|
|
|
// PrivilegeHandler processes PrivilegeAdded-events and fetches the initial set of privileges from an authz-service
|
|
type PrivilegeHandler struct {
|
|
*sync.RWMutex
|
|
client *http.Client
|
|
baseURL string
|
|
privileges map[string]map[string]*CompanyPrivileges
|
|
}
|
|
|
|
// OptsFunc is used to configure the PrivilegeHandler
|
|
type OptsFunc func(handler *PrivilegeHandler)
|
|
|
|
// WithBaseURL sets the base URL to the authz-service
|
|
func WithBaseURL(url string) OptsFunc {
|
|
return func(handler *PrivilegeHandler) {
|
|
handler.baseURL = url
|
|
}
|
|
}
|
|
|
|
// New creates a new PrivilegeHandler. Pass OptsFuncs to configure.
|
|
func New(opts ...OptsFunc) *PrivilegeHandler {
|
|
handler := &PrivilegeHandler{
|
|
RWMutex: &sync.RWMutex{},
|
|
client: &http.Client{},
|
|
baseURL: "http://authz-service",
|
|
privileges: map[string]map[string]*CompanyPrivileges{},
|
|
}
|
|
for _, opt := range opts {
|
|
opt(handler)
|
|
}
|
|
return handler
|
|
}
|
|
|
|
// Fetch the initial set of privileges from an authz-service
|
|
func (h *PrivilegeHandler) Fetch() error {
|
|
resp, err := h.client.Get(fmt.Sprintf("%s/authz", h.baseURL))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
buff, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
err = json.Unmarshal(buff, &h.privileges)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *PrivilegeHandler) Setup() []goamqp.Setup {
|
|
return []goamqp.Setup{
|
|
goamqp.TransientEventStreamConsumer("User.Added", h.Process, UserAdded{}),
|
|
goamqp.TransientEventStreamConsumer("User.Removed", h.Process, UserRemoved{}),
|
|
goamqp.TransientEventStreamConsumer("Privilege.Added", h.Process, PrivilegeAdded{}),
|
|
goamqp.TransientEventStreamConsumer("Privilege.Removed", h.Process, PrivilegeRemoved{}),
|
|
}
|
|
}
|
|
|
|
// Process privilege-related events and update the internal state
|
|
func (h *PrivilegeHandler) Process(msg interface{}, _ goamqp.Headers) (interface{}, error) {
|
|
switch ev := msg.(type) {
|
|
case *UserAdded:
|
|
if priv, exists := h.privileges[ev.Email]; exists {
|
|
priv[ev.CompanyID] = &CompanyPrivileges{}
|
|
} else {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
h.privileges[ev.Email] = map[string]*CompanyPrivileges{
|
|
ev.CompanyID: {},
|
|
}
|
|
}
|
|
return nil, nil
|
|
case *UserRemoved:
|
|
if priv, exists := h.privileges[ev.Email]; exists {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
delete(priv, ev.CompanyID)
|
|
}
|
|
return nil, nil
|
|
case *PrivilegeAdded:
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, true)
|
|
return nil, nil
|
|
case *PrivilegeRemoved:
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, false)
|
|
return nil, nil
|
|
default:
|
|
fmt.Printf("Got unexpected message type (%s): '%+v'\n", reflect.TypeOf(msg).String(), msg)
|
|
return nil, fmt.Errorf("unexpected event type: '%s'", reflect.TypeOf(msg))
|
|
}
|
|
}
|
|
|
|
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
|
|
case PrivilegeSalary:
|
|
c.Salary = set
|
|
}
|
|
} else {
|
|
priv[companyId] = &CompanyPrivileges{}
|
|
h.setPrivileges(email, companyId, privilege, set)
|
|
}
|
|
} else {
|
|
h.privileges[email] = map[string]*CompanyPrivileges{}
|
|
h.setPrivileges(email, companyId, privilege, set)
|
|
}
|
|
}
|
|
|
|
// CompaniesByUser return a slice of company ids matching the provided email and predicate func
|
|
func (h *PrivilegeHandler) CompaniesByUser(email string, predicate func(privileges CompanyPrivileges) bool) []string {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
var result []string
|
|
if p, exists := h.privileges[email]; exists {
|
|
for k, v := range p {
|
|
if predicate(*v) {
|
|
result = append(result, k)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// IsAllowed return true if the provided predicate return true for the privileges matching the provided email and companyID, return false otherwise
|
|
func (h *PrivilegeHandler) IsAllowed(email, companyID string, predicate func(privileges CompanyPrivileges) bool) bool {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if p, exists := h.privileges[email]; exists {
|
|
if v, exists := p[companyID]; exists {
|
|
return predicate(*v)
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|