// Package rbacclient ist ein schlanker HTTP-Client für Core RBAC-06 // (internal/policyapi, POST /authorize). Archive ist ein physisch // getrenntes Go-Modul und kann Core RBAC-02 (internal/policy) daher // nicht direkt importieren — nur über die HTTP-Schnittstelle von // RBAC-06 aufrufen. package rbacclient import ( "bytes" "context" "encoding/json" "errors" "fmt" "net/http" ) // ErrDenied wird zurückgegeben, wenn RBAC-06 die Anfrage ausdrücklich // ablehnt (kein Transport-/Serverfehler, sondern eine bewusste // Policy-Entscheidung). var ErrDenied = errors.New("rbac: zugriff verweigert") // Client ruft den RBAC-06-Endpunkt auf. type Client struct { BaseURL string ServiceToken string HTTPClient *http.Client } func New(baseURL, serviceToken string) *Client { return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: http.DefaultClient} } type authorizeRequest struct { Role string `json:"role"` Permission string `json:"permission"` } type authorizeResponse struct { Allowed bool `json:"allowed"` } // Authorize fragt RBAC-06, ob role die permission besitzt. Jeder // Fehler (Transport, Timeout, unerwarteter Status) wird als Fehler // zurückgegeben, NICHT stillschweigend als "erlaubt" behandelt — der // Aufrufer muss bei einem Fehler fail-closed (verweigern) reagieren. func (c *Client) Authorize(ctx context.Context, role, permission string) (bool, error) { body, err := json.Marshal(authorizeRequest{Role: role, Permission: permission}) if err != nil { return false, fmt.Errorf("rbac: request kodieren: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/authorize", bytes.NewReader(body)) if err != nil { return false, fmt.Errorf("rbac: request bauen: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Service-Token", c.ServiceToken) resp, err := c.httpClient().Do(req) if err != nil { return false, fmt.Errorf("rbac: aufruf fehlgeschlagen: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return false, fmt.Errorf("rbac: unerwarteter status %d", resp.StatusCode) } var out authorizeResponse if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return false, fmt.Errorf("rbac: antwort dekodieren: %w", err) } if !out.Allowed { return false, ErrDenied } return true, nil } func (c *Client) httpClient() *http.Client { if c.HTTPClient != nil { return c.HTTPClient } return http.DefaultClient }