mirror of https://github.com/vernonkeenan/lib
feat: add native service principal contract
parent
edd36034e6
commit
5ad4f6103e
|
|
@ -44,13 +44,13 @@ pins the same module, a `lib` bump is a constellation-wide rebuild (KV-C6a).
|
|||
| Path | What it is |
|
||||
|---|---|
|
||||
| `app/root.go` | `InitConfig(systemName, level)` — loads `/etc/vernonkeenan/<systemName>.yaml` via viper, one-time init guard |
|
||||
| `app/config.go` | `Configuration` struct + `Get*`/`Is*` accessors (DBMS, DSN, log level, metrics, service accounts, workers, email, chunk/cache sizing) |
|
||||
| `app/config.go` | `Configuration` struct + `Get*`/`Is*` accessors (DBMS, DSN, authentication mode, log level, metrics, service accounts, workers, email, chunk/cache sizing) |
|
||||
| `app/mysql.go` | `InitDB()` — opens `MyDB *sql.DB` against the configured MySQL DSN |
|
||||
| `app/force.go` | `InitForce(serviceAccountName)` — opens a Salesforce `go-force` API connection from a configured service account |
|
||||
| `app/auth.go` | `CheckAPIUser(token)` — validates an API key against `auth`'s generated client |
|
||||
| `app/auth.go` | `CheckAPIUser(token)` — validates a credential against `auth`; errors never log the raw value |
|
||||
| `app/prometheus.go` | `SetupPrometheusHandler` — CORS + request-count/duration middleware |
|
||||
| `app/service-helpers.go` | Pointer helpers (`String`/`Bool`/`Int64`/…), `GetFieldsAndValues` reflection helper (unit-tested) |
|
||||
| `app/user.go`, `user-helpers.go`, `tenantuser.go`, `userrole.go`, `address.go` | First-class domain structs + swagger marshal/unmarshal helpers shared by consumers |
|
||||
| `app/user.go`, `user-helpers.go`, `tenantuser.go`, `userrole.go`, `address.go` | Compatibility principal/domain structs. ADR-KV-024 adds `PrincipalKey`, `PrincipalType`, exact `Scopes`, `HasScope`, and `IsServicePrincipal`; no native response carries its bearer secret. |
|
||||
| `app/logger/logger.go` | Thin `zap.SugaredLogger` wrapper (`New(level)`, level constants) |
|
||||
| `api/{auth,crm,members,plex,research,sfgate,stash}/` | go-swagger–generated typed clients + models, one directory per sibling service, regenerated from that service's own spec |
|
||||
| `swagger/*.yaml`, `swagger/defs/`, `swagger/external/` | Copies of each sibling service's spec (the KV-C6b spec-copy problem — no sync mechanism besides `make regen`), shared component defs, and host/path-rewritten copies for external doc publishing; also carries legacy vendored Kazoo/Clerk specs from `lib`'s pre-mesh history |
|
||||
|
|
|
|||
|
|
@ -148,6 +148,12 @@ type User struct {
|
|||
// Portal Role Level
|
||||
PortalRole string `json:"PortalRole,omitempty"`
|
||||
|
||||
// Stable machine principal key; empty for legacy human principals
|
||||
PrincipalKey string `json:"PrincipalKey,omitempty"`
|
||||
|
||||
// Principal type, currently service or legacy_user
|
||||
PrincipalType string `json:"PrincipalType,omitempty"`
|
||||
|
||||
// Profile
|
||||
ProfileID string `json:"ProfileID,omitempty"`
|
||||
|
||||
|
|
@ -157,6 +163,9 @@ type User struct {
|
|||
// Admin Info Emails
|
||||
ReceivesAdminInfoEmails bool `json:"ReceivesAdminInfoEmails,omitempty"`
|
||||
|
||||
// Explicit mesh authorization scopes; never contains credentials
|
||||
Scopes []string `json:"Scopes"`
|
||||
|
||||
// Email Sender Address
|
||||
SenderEmail string `json:"SenderEmail,omitempty"`
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
//
|
||||
// When called, ChechAPIUser assumes there is an "auth" service account in the
|
||||
// app configuration file.
|
||||
//
|
||||
func CheckAPIUser(token *string) (*User, error) {
|
||||
sugar.Debug("app.CheckAPIUser: 📥")
|
||||
if authUser == nil {
|
||||
|
|
@ -27,7 +26,7 @@ func CheckAPIUser(token *string) (*User, error) {
|
|||
params.Apikey = token
|
||||
response, err := authClient.User.GetUsers(params, authUser.Auth)
|
||||
if err != nil {
|
||||
sugar.Warnf("app.CheckAPIUser: ❗ Access attempt with invalid API key: %s", *token)
|
||||
sugar.Warn("app.CheckAPIUser: ❗ access attempt with invalid API key")
|
||||
return nil, err
|
||||
}
|
||||
var obj *auth_models.User
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ type ServiceAccount struct {
|
|||
// Configuration defines the config struct
|
||||
type Configuration struct {
|
||||
AppName string `mapstructure:"app_name,omitempty"`
|
||||
AuthMethod string `mapstructure:"auth_method,omitempty"`
|
||||
BackendID string `mapstructure:"backend_id,omitempty"`
|
||||
BuildEnv string `mapstructure:"build_env,omitempty"`
|
||||
CacheSizes map[string]CacheSize `mapstructure:"cache_sizes,omitempty"`
|
||||
|
|
@ -98,6 +99,11 @@ type Configuration struct {
|
|||
Workers map[string]Worker `mapstructure:"workers,omitempty"`
|
||||
}
|
||||
|
||||
// GetAuthMethod returns the configured authentication-authority mode.
|
||||
func GetAuthMethod() string {
|
||||
return config.AuthMethod
|
||||
}
|
||||
|
||||
// GetCacheSize returns the named cache size
|
||||
func GetCacheSize(name string) int64 {
|
||||
obj, ok := config.CacheSizes[name]
|
||||
|
|
|
|||
|
|
@ -76,12 +76,15 @@ func MarshalAuthUserToSwagger(obj *auth_models.User) *User {
|
|||
OutOfOfficeMessage: obj.OutOfOfficeMessage,
|
||||
Phone: obj.Phone,
|
||||
PortalRole: obj.PortalRole,
|
||||
PrincipalKey: obj.PrincipalKey,
|
||||
PrincipalType: obj.PrincipalType,
|
||||
ProfileID: obj.ProfileID,
|
||||
ReceivesAdminEmails: obj.ReceivesAdminEmails,
|
||||
ReceivesAdminInfoEmails: obj.ReceivesAdminInfoEmails,
|
||||
SenderEmail: obj.SenderEmail,
|
||||
SenderName: obj.SenderName,
|
||||
Signature: obj.Signature,
|
||||
Scopes: append([]string(nil), obj.Scopes...),
|
||||
SmallPhotoURL: obj.SmallPhotoURL,
|
||||
StartOfDay: obj.StartOfDay,
|
||||
ExternalAccount: obj.ExternalAccount,
|
||||
|
|
|
|||
22
app/user.go
22
app/user.go
|
|
@ -57,6 +57,8 @@ type User struct {
|
|||
OutOfOfficeMessage string
|
||||
Phone string
|
||||
PortalRole string
|
||||
PrincipalKey string
|
||||
PrincipalType string
|
||||
ProfileID string
|
||||
ReceivesAdminEmails bool
|
||||
ReceivesAdminInfoEmails bool
|
||||
|
|
@ -64,6 +66,7 @@ type User struct {
|
|||
SenderEmail string
|
||||
SenderName string
|
||||
Signature string
|
||||
Scopes []string
|
||||
SmallPhotoURL string
|
||||
StartOfDay string
|
||||
ExternalAccount string
|
||||
|
|
@ -76,3 +79,22 @@ type User struct {
|
|||
UserRoleID string
|
||||
UserType string
|
||||
}
|
||||
|
||||
// HasScope reports whether a native service principal has an exact mesh
|
||||
// authorization scope. Legacy user principals have no scopes.
|
||||
func (u *User) HasScope(scope string) bool {
|
||||
if u == nil || u.PrincipalType != "service" {
|
||||
return false
|
||||
}
|
||||
for _, granted := range u.Scopes {
|
||||
if granted == scope {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsServicePrincipal matches stable machine identity, never a bearer secret.
|
||||
func (u *User) IsServicePrincipal(principalKey string) bool {
|
||||
return u != nil && u.PrincipalType == "service" && u.PrincipalKey == principalKey
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package app
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNativeServicePrincipalScope(t *testing.T) {
|
||||
principal := &User{
|
||||
PrincipalType: "service",
|
||||
PrincipalKey: "cache",
|
||||
Scopes: []string{"auth:introspect", "crm:account-contact:load"},
|
||||
}
|
||||
if !principal.IsServicePrincipal("cache") {
|
||||
t.Fatal("expected stable service-principal match")
|
||||
}
|
||||
if !principal.HasScope("crm:account-contact:load") {
|
||||
t.Fatal("expected exact scope grant")
|
||||
}
|
||||
if principal.HasScope("crm:account:write") {
|
||||
t.Fatal("unexpected scope grant")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyUserCannotClaimServiceScope(t *testing.T) {
|
||||
legacy := &User{APIKey: "legacy", Scopes: []string{"crm:account-contact:load"}}
|
||||
if legacy.IsServicePrincipal("cache") || legacy.HasScope("crm:account-contact:load") {
|
||||
t.Fatal("legacy user must not satisfy native service authorization")
|
||||
}
|
||||
}
|
||||
|
|
@ -115,8 +115,9 @@ cmd_install_swagger() {
|
|||
bin="$(swagger_bin)"
|
||||
[[ -x "$bin" ]] || die "go install reported success but ${bin} is not executable"
|
||||
|
||||
local got
|
||||
got="$("$bin" version 2>&1 | head -1)"
|
||||
local got version_output
|
||||
version_output="$("$bin" version 2>&1)"
|
||||
got="${version_output%%$'\n'*}"
|
||||
log "swagger version: ${got}"
|
||||
case "$got" in
|
||||
*"${SWAGGER_VERSION#v}"*) ;;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
swagger: "2.0"
|
||||
info:
|
||||
version: 0.3.0
|
||||
version: 0.4.0
|
||||
title: "auth"
|
||||
description: "Authentication Microservice"
|
||||
termsOfService: "https://salesforcedevops.net/terms/"
|
||||
|
|
@ -272,6 +272,12 @@ definitions:
|
|||
PortalRole:
|
||||
description: Portal Role Level
|
||||
type: string
|
||||
PrincipalKey:
|
||||
description: Stable machine principal key; empty for legacy human principals
|
||||
type: string
|
||||
PrincipalType:
|
||||
description: Principal type, currently service or legacy_user
|
||||
type: string
|
||||
ProfileID:
|
||||
description: Profile
|
||||
type: string
|
||||
|
|
@ -290,6 +296,11 @@ definitions:
|
|||
Signature:
|
||||
description: Email Signature
|
||||
type: string
|
||||
Scopes:
|
||||
description: Explicit mesh authorization scopes; never contains credentials
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
SmallPhotoURL:
|
||||
description: Small Photo URL
|
||||
type: string
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
swagger: "2.0"
|
||||
info:
|
||||
version: 0.3.0
|
||||
version: 0.4.0
|
||||
title: "auth"
|
||||
description: "Authentication Microservice"
|
||||
termsOfService: "https://salesforcedevops.net/terms/"
|
||||
|
|
@ -272,6 +272,12 @@ definitions:
|
|||
PortalRole:
|
||||
description: Portal Role Level
|
||||
type: string
|
||||
PrincipalKey:
|
||||
description: Stable machine principal key; empty for legacy human principals
|
||||
type: string
|
||||
PrincipalType:
|
||||
description: Principal type, currently service or legacy_user
|
||||
type: string
|
||||
ProfileID:
|
||||
description: Profile
|
||||
type: string
|
||||
|
|
@ -290,6 +296,11 @@ definitions:
|
|||
Signature:
|
||||
description: Email Signature
|
||||
type: string
|
||||
Scopes:
|
||||
description: Explicit mesh authorization scopes; never contains credentials
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
SmallPhotoURL:
|
||||
description: Small Photo URL
|
||||
type: string
|
||||
|
|
|
|||
Loading…
Reference in New Issue