Files

128 lines
3.6 KiB
Go

package middleware
import (
"errors"
"net/http"
"sync/atomic"
"github.com/gravitl/netmaker/db"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/schema"
"github.com/gravitl/netmaker/scope"
)
var (
errMissingTenantID = errors.New("X-Tenant-ID header is required")
errTenantNotFound = errors.New("tenant not found")
errMissingOrgID = errors.New("X-Organization-ID header is required")
errOrgNotFound = errors.New("organization not found")
)
var defaultTenantID atomic.Value
func ResetDefaultTenantID(oldID, newID string) {
defaultTenantID.CompareAndSwap(oldID, newID)
}
// Scope reads the scope header for the given level, validates the tenant/org,
// and stores the level and id in the request context.
func Scope(level scope.Scope, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var id string
switch level {
case scope.TenantScope:
id = r.Header.Get(scope.HeaderTenantID)
if id == "" {
id = r.URL.Query().Get(scope.QueryTenantID)
}
if id == "" {
if defaultTenantID.Load() == nil {
t, err := logic.SoleTenant(r.Context())
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errMissingTenantID, logic.Internal))
return
}
defaultTenantID.Store(t.ID)
}
id = defaultTenantID.Load().(string)
} else {
t := &schema.Tenant{ID: id}
if err := t.Get(db.WithContext(r.Context())); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errTenantNotFound, logic.BadReq))
return
}
}
case scope.OrgScope:
id = r.Header.Get(scope.HeaderOrgID)
if id == "" {
id = r.URL.Query().Get(scope.QueryOrgID)
}
if id == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errMissingOrgID, logic.BadReq))
return
}
o := &schema.Organization{ID: id}
if err := o.Get(db.WithContext(r.Context())); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errOrgNotFound, logic.BadReq))
return
}
case scope.GlobalScope:
// no header required
}
next.ServeHTTP(w, r.WithContext(scope.WithContext(r.Context(), level, id)))
}
}
// InferScope determines whether the request is tenant- or org-scoped based on
// which header is present, and scopes the request accordingly. It is intended for
// endpoints that support both tenant and org scope.
func InferScope(next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orgID := r.Header.Get(scope.HeaderOrgID)
if orgID == "" {
orgID = r.URL.Query().Get(scope.QueryOrgID)
}
tenantID := r.Header.Get(scope.HeaderTenantID)
if tenantID == "" {
tenantID = r.URL.Query().Get(scope.QueryTenantID)
}
if orgID != "" {
o := &schema.Organization{ID: orgID}
if err := o.Get(db.WithContext(r.Context())); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errOrgNotFound, logic.BadReq))
return
}
next.ServeHTTP(w, r.WithContext(scope.WithContext(r.Context(), scope.OrgScope, orgID)))
return
} else if tenantID != "" {
t := &schema.Tenant{ID: tenantID}
if err := t.Get(db.WithContext(r.Context())); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errTenantNotFound, logic.BadReq))
return
}
} else {
if defaultTenantID.Load() == nil {
t, err := logic.SoleTenant(r.Context())
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errMissingTenantID, logic.Internal))
return
}
defaultTenantID.Store(t.ID)
}
tenantID = defaultTenantID.Load().(string)
}
next.ServeHTTP(w, r.WithContext(scope.WithContext(r.Context(), scope.TenantScope, tenantID)))
}
}