Files
authz_client/client.go
T

213 lines
5.8 KiB
Go
Raw Normal View History

2019-11-05 21:24:54 +01:00
package client
import (
"context"
2019-11-05 21:24:54 +01:00
"encoding/json"
"fmt"
2022-11-10 10:54:45 +01:00
"io"
2019-11-05 21:24:54 +01:00
"net/http"
2019-11-22 08:12:48 +01:00
"reflect"
2023-12-26 18:21:27 +01:00
"sync"
2021-05-15 14:42:19 +02:00
goamqp "codeberg.org/messaging/go-messaging-amqp"
spec "codeberg.org/messaging/messaging"
2019-11-05 21:24:54 +01:00
)
// 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"`
2019-11-05 21:24:54 +01:00
}
// PrivilegeHandler processes PrivilegeAdded-events and fetches the initial set of privileges from an authz-service
type PrivilegeHandler struct {
2023-12-26 18:21:27 +01:00
*sync.RWMutex
2019-11-05 21:24:54 +01:00
client *http.Client
baseURL string
apiKey string
2020-04-12 20:33:35 +02:00
privileges map[string]map[string]*CompanyPrivileges
2019-11-05 21:24:54 +01:00
}
// 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
}
}
// WithAPIKey sets an API key used as a Bearer token when fetching privileges
func WithAPIKey(key string) OptsFunc {
return func(handler *PrivilegeHandler) {
handler.apiKey = key
}
}
2019-11-05 21:24:54 +01:00
// New creates a new PrivilegeHandler. Pass OptsFuncs to configure.
func New(opts ...OptsFunc) *PrivilegeHandler {
handler := &PrivilegeHandler{
2023-12-26 18:21:27 +01:00
RWMutex: &sync.RWMutex{},
2019-11-05 21:24:54 +01:00
client: &http.Client{},
baseURL: "http://authz-service",
2020-04-12 20:33:35 +02:00
privileges: map[string]map[string]*CompanyPrivileges{},
2019-11-05 21:24:54 +01:00
}
for _, opt := range opts {
opt(handler)
}
return handler
}
// Fetch the initial set of privileges from an authz-service
func (h *PrivilegeHandler) Fetch() error {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/authz", h.baseURL), nil)
if err != nil {
return err
}
if h.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+h.apiKey)
}
resp, err := h.client.Do(req)
2019-11-05 21:24:54 +01:00
if err != nil {
return err
}
2022-11-10 10:54:45 +01:00
buff, err := io.ReadAll(resp.Body)
2019-11-05 21:24:54 +01:00
if err != nil {
return err
}
h.Lock()
defer h.Unlock()
2019-11-05 21:24:54 +01:00
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", process[UserAdded](h)),
goamqp.TransientEventStreamConsumer("User.Removed", process[UserRemoved](h)),
goamqp.TransientEventStreamConsumer("Privilege.Added", process[PrivilegeAdded](h)),
goamqp.TransientEventStreamConsumer("Privilege.Removed", process[PrivilegeRemoved](h)),
}
}
// privilegeEvent is the set of events Process handles.
type privilegeEvent interface {
UserAdded | UserRemoved | PrivilegeAdded | PrivilegeRemoved
}
// process adapts Process to a typed go-messaging-amqp handler.
func process[T privilegeEvent](h *PrivilegeHandler) spec.EventHandler[T] {
return func(_ context.Context, event spec.ConsumableEvent[T]) error {
return h.Process(&event.Payload)
}
}
2019-11-05 21:24:54 +01:00
// Process privilege-related events and update the internal state
func (h *PrivilegeHandler) Process(msg any) error {
h.Lock()
defer h.Unlock()
2019-12-31 11:53:49 +01:00
switch ev := msg.(type) {
2020-04-12 20:33:35 +02:00
case *UserAdded:
// Keep the privileges already recorded for the company. Each routing key has
// its own transient queue, so a Privilege.Added published after this event can
// be processed before it; overwriting the entry here would drop that privilege
// until the next Fetch, which only runs at start.
priv, exists := h.privileges[ev.Email]
if !exists {
priv = map[string]*CompanyPrivileges{}
h.privileges[ev.Email] = priv
}
if _, exists := priv[ev.CompanyID]; !exists {
2020-04-12 20:33:35 +02:00
priv[ev.CompanyID] = &CompanyPrivileges{}
}
return nil
2020-04-12 20:33:35 +02:00
case *UserRemoved:
if priv, exists := h.privileges[ev.Email]; exists {
delete(priv, ev.CompanyID)
}
return nil
2019-12-31 11:53:49 +01:00
case *PrivilegeAdded:
2020-04-12 20:33:35 +02:00
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, true)
return nil
2019-12-31 11:53:49 +01:00
case *PrivilegeRemoved:
2020-04-12 20:33:35 +02:00
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, false)
return nil
2019-12-31 11:53:49 +01:00
default:
2019-11-22 08:12:48 +01:00
fmt.Printf("Got unexpected message type (%s): '%+v'\n", reflect.TypeOf(msg).String(), msg)
return fmt.Errorf("unexpected event type: '%s'", reflect.TypeOf(msg))
2019-11-05 21:24:54 +01:00
}
}
2020-04-12 20:33:35 +02:00
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
2020-04-12 20:33:35 +02:00
}
} else {
priv[companyId] = &CompanyPrivileges{}
h.setPrivileges(email, companyId, privilege, set)
2019-11-05 21:24:54 +01:00
}
} else {
2020-04-12 20:33:35 +02:00
h.privileges[email] = map[string]*CompanyPrivileges{}
h.setPrivileges(email, companyId, privilege, set)
2019-12-31 11:53:49 +01:00
}
}
2019-11-05 21:24:54 +01:00
// 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 {
2023-12-26 18:21:27 +01:00
h.RLock()
defer h.RUnlock()
2019-11-05 21:24:54 +01:00
var result []string
if p, exists := h.privileges[email]; exists {
for k, v := range p {
2020-04-12 20:33:35 +02:00
if predicate(*v) {
2019-11-05 21:24:54 +01:00
result = append(result, k)
}
}
}
return result
}
2019-12-31 11:53:49 +01:00
// 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 {
2023-12-26 18:21:27 +01:00
h.RLock()
defer h.RUnlock()
2019-12-31 11:53:49 +01:00
if p, exists := h.privileges[email]; exists {
if v, exists := p[companyID]; exists {
2020-04-12 20:33:35 +02:00
return predicate(*v)
2019-12-31 11:53:49 +01:00
}
}
return false
}