- Introduced new endpoints for managing identity providers, including retrieval of redirect URIs and testing/syncing providers. - Enhanced organization settings to include identity provider configurations, allowing for self-enrollment and domain restrictions. - Implemented caching for access policies and added validation for identity provider secrets. - Added integration tests to ensure proper functionality of identity provider management and policy enforcement.
145 lines
3.2 KiB
Go
145 lines
3.2 KiB
Go
package orgpolicy
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/ultisuite/ulti-backend/internal/auth"
|
|
)
|
|
|
|
type IdentityProviderPolicy struct {
|
|
ID string
|
|
Slug string
|
|
Type string
|
|
Enabled bool
|
|
AllowedEmailDomains []string
|
|
AllowedIdentities []string
|
|
AllowedOrganizations []string
|
|
}
|
|
|
|
type AuthAccessPolicy struct {
|
|
AllowSelfEnrollment bool
|
|
Providers []IdentityProviderPolicy
|
|
}
|
|
|
|
func (p AuthAccessPolicy) AllowsIdentity(email string, claims *auth.Claims) bool {
|
|
enabled := make([]IdentityProviderPolicy, 0)
|
|
for _, provider := range p.Providers {
|
|
if provider.Enabled {
|
|
enabled = append(enabled, provider)
|
|
}
|
|
}
|
|
if len(enabled) == 0 {
|
|
return true
|
|
}
|
|
|
|
if claims != nil && strings.TrimSpace(claims.Source) != "" {
|
|
for _, provider := range enabled {
|
|
if provider.Slug == claims.Source && providerAllows(provider, email, claims) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
hasRestrictions := false
|
|
for _, provider := range enabled {
|
|
if providerHasRestrictions(provider) {
|
|
hasRestrictions = true
|
|
if providerAllows(provider, email, claims) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
if !hasRestrictions {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func providerHasRestrictions(provider IdentityProviderPolicy) bool {
|
|
return len(provider.AllowedEmailDomains) > 0 ||
|
|
len(provider.AllowedIdentities) > 0 ||
|
|
len(provider.AllowedOrganizations) > 0
|
|
}
|
|
|
|
func providerAllows(provider IdentityProviderPolicy, email string, claims *auth.Claims) bool {
|
|
if len(provider.AllowedIdentities) > 0 {
|
|
if !containsFold(provider.AllowedIdentities, email) {
|
|
return false
|
|
}
|
|
}
|
|
if len(provider.AllowedEmailDomains) > 0 {
|
|
if !emailDomainAllowed(email, provider.AllowedEmailDomains) {
|
|
return false
|
|
}
|
|
}
|
|
if len(provider.AllowedOrganizations) > 0 {
|
|
if claims == nil || !organizationAllowed(claims, provider.AllowedOrganizations) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func emailDomainAllowed(email string, domains []string) bool {
|
|
email = strings.ToLower(strings.TrimSpace(email))
|
|
at := strings.LastIndex(email, "@")
|
|
if at < 0 {
|
|
return false
|
|
}
|
|
domain := email[at+1:]
|
|
for _, allowed := range domains {
|
|
allowed = strings.ToLower(strings.TrimSpace(strings.TrimPrefix(allowed, "@")))
|
|
if allowed == "" {
|
|
continue
|
|
}
|
|
if domain == allowed || strings.HasSuffix(domain, "."+allowed) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func organizationAllowed(claims *auth.Claims, orgs []string) bool {
|
|
candidates := []string{
|
|
claims.HD,
|
|
claims.TID,
|
|
claims.Org,
|
|
}
|
|
for _, org := range orgs {
|
|
org = strings.TrimSpace(org)
|
|
if org == "" {
|
|
continue
|
|
}
|
|
for _, candidate := range candidates {
|
|
if strings.EqualFold(strings.TrimSpace(candidate), org) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func containsFold(list []string, value string) bool {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
for _, item := range list {
|
|
if strings.ToLower(strings.TrimSpace(item)) == value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func stringSlice(v any) []string {
|
|
raw, ok := v.([]any)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
out := make([]string, 0, len(raw))
|
|
for _, item := range raw {
|
|
if s, ok := item.(string); ok && strings.TrimSpace(s) != "" {
|
|
out = append(out, strings.TrimSpace(s))
|
|
}
|
|
}
|
|
return out
|
|
}
|