75 lines
2.3 KiB
Go
75 lines
2.3 KiB
Go
|
|
// Package tenantengine calls tenant-engine's cache-read endpoint at
|
||
|
|
// token-issuance time to source the optional tenant_roles claim
|
||
|
|
// (net-kingdom/canon/standards/iam-profile_v0.3.md, "Tenant Roles").
|
||
|
|
//
|
||
|
|
// This is the cache-read direction only, and it fails OPEN -- the opposite
|
||
|
|
// of flex-auth's live-lookup adapter (flex-auth/internal/adapters/tenantengine),
|
||
|
|
// which must fail closed. tenant_roles is documented as a cache callers
|
||
|
|
// must never trust for privileged decisions (flex-auth re-validates live
|
||
|
|
// before authorizing aal2-class actions); losing this claim at issuance
|
||
|
|
// time is a performance regression, not a security one. Blocking login
|
||
|
|
// because a cache source is briefly down would be the wrong trade.
|
||
|
|
package tenantengine
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"net/http"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Client fetches cached capability roles for a tenant.
|
||
|
|
type Client struct {
|
||
|
|
BaseURL string
|
||
|
|
HTTP *http.Client
|
||
|
|
}
|
||
|
|
|
||
|
|
// New returns a tenant-engine cache-read client. A nil httpClient gets a
|
||
|
|
// short default timeout -- this call sits on the synchronous token-issuance
|
||
|
|
// path and must not turn a cache miss into a slow login.
|
||
|
|
func New(baseURL string, httpClient *http.Client) *Client {
|
||
|
|
if httpClient == nil {
|
||
|
|
httpClient = &http.Client{Timeout: 2 * time.Second}
|
||
|
|
}
|
||
|
|
return &Client{BaseURL: strings.TrimRight(baseURL, "/"), HTTP: httpClient}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Roles fetches GET /tenants/{tenantID}/roles.
|
||
|
|
//
|
||
|
|
// Returns (nil, false) -- not an error -- on any failure: unreachable
|
||
|
|
// tenant-engine, non-200 response, or a malformed body. Callers must treat
|
||
|
|
// false as "omit the tenant_roles claim entirely", never as "emit an empty
|
||
|
|
// or stale role list".
|
||
|
|
func (c *Client) Roles(ctx context.Context, tenantID string) ([]string, bool) {
|
||
|
|
if c == nil || c.BaseURL == "" {
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
|
||
|
|
url := fmt.Sprintf("%s/tenants/%s/roles", c.BaseURL, tenantID)
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||
|
|
if err != nil {
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
|
||
|
|
resp, err := c.HTTP.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode != http.StatusOK {
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
|
||
|
|
var body struct {
|
||
|
|
Roles []string `json:"roles"`
|
||
|
|
}
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
|
||
|
|
return body.Roles, true
|
||
|
|
}
|