1
0
forked from baron/baron-sso

Implement tenant import and RP auto login policies

This commit is contained in:
2026-04-30 15:45:34 +09:00
parent 24807eab0f
commit f7e4d43b16
76 changed files with 5307 additions and 441 deletions

View File

@@ -14,6 +14,7 @@ import (
"io"
"log/slog"
"net/http"
"net/url"
"os"
"strconv"
"strings"
@@ -24,19 +25,20 @@ import (
)
type DevHandler struct {
Hydra *service.HydraAdminService
Redis domain.RedisRepository
HeadlessJWKS *service.HeadlessJWKSCacheService
SecretRepo domain.ClientSecretRepository
AuditRepo domain.AuditRepository
KratosAdmin service.KratosAdminService
ConsentRepo repository.ClientConsentRepository
Keto service.KetoService
KetoOutbox repository.KetoOutboxRepository
RPSvc service.RelyingPartyService
TenantSvc service.TenantService
DeveloperSvc *service.DeveloperService
Auth interface {
Hydra *service.HydraAdminService
Redis domain.RedisRepository
HeadlessJWKS *service.HeadlessJWKSCacheService
SecretRepo domain.ClientSecretRepository
AuditRepo domain.AuditRepository
KratosAdmin service.KratosAdminService
ConsentRepo repository.ClientConsentRepository
Keto service.KetoService
KetoOutbox repository.KetoOutboxRepository
RPSvc service.RelyingPartyService
TenantSvc service.TenantService
DeveloperSvc *service.DeveloperService
RPUserMetadataRepo repository.RPUserMetadataRepository
Auth interface {
GetEnrichedProfile(c *fiber.Ctx) (*domain.UserProfileResponse, error)
}
}
@@ -1377,6 +1379,86 @@ func (h *DevHandler) publicHeadlessJWKSCacheState(clientID string) (*domain.Head
return h.HeadlessJWKS.PublicState(clientID)
}
func (h *DevHandler) GetRPUserMetadata(c *fiber.Ctx) error {
clientID := strings.TrimSpace(c.Params("id"))
userID := strings.TrimSpace(c.Params("userId"))
if clientID == "" || userID == "" {
return errorJSON(c, fiber.StatusBadRequest, "client id and user id are required")
}
if h.RPUserMetadataRepo == nil {
return errorJSON(c, fiber.StatusServiceUnavailable, "rp user metadata repository unavailable")
}
profile := h.getCurrentProfile(c)
if profile == nil {
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized: authentication required")
}
summary, err := h.loadClientSummary(c.Context(), clientID)
if err != nil {
return errorJSON(c, fiber.StatusNotFound, "client not found")
}
if !h.canViewClientByPermit(c, profile, summary) {
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permission to view client metadata")
}
metadata, err := h.RPUserMetadataRepo.Get(c.Context(), clientID, userID)
if err != nil {
return c.JSON(fiber.Map{
"clientId": clientID,
"userId": userID,
"metadata": domain.JSONMap{},
})
}
return c.JSON(metadata)
}
func (h *DevHandler) UpsertRPUserMetadata(c *fiber.Ctx) error {
clientID := strings.TrimSpace(c.Params("id"))
userID := strings.TrimSpace(c.Params("userId"))
if clientID == "" || userID == "" {
return errorJSON(c, fiber.StatusBadRequest, "client id and user id are required")
}
if h.RPUserMetadataRepo == nil {
return errorJSON(c, fiber.StatusServiceUnavailable, "rp user metadata repository unavailable")
}
profile := h.getCurrentProfile(c)
if profile == nil {
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized: authentication required")
}
summary, err := h.loadClientSummary(c.Context(), clientID)
if err != nil {
return errorJSON(c, fiber.StatusNotFound, "client not found")
}
if !h.canManageClientRelations(c, profile, summary) {
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permission to update client metadata")
}
var req struct {
Metadata map[string]any `json:"metadata"`
}
if err := c.BodyParser(&req); err != nil {
return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
}
if req.Metadata == nil {
req.Metadata = map[string]any{}
}
row := &domain.RPUserMetadata{
ClientID: clientID,
UserID: userID,
Metadata: domain.JSONMap(req.Metadata),
}
if err := h.RPUserMetadataRepo.Upsert(c.Context(), row); err != nil {
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
return c.JSON(row)
}
func (h *DevHandler) syncHeadlessJWKSCache(ctx context.Context, client domain.HydraClient, reason string) {
if h.HeadlessJWKS == nil {
h.HeadlessJWKS = service.NewHeadlessJWKSCacheService(h.Redis, nil)
@@ -1574,6 +1656,10 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
if err != nil {
return errorJSON(c, fiber.StatusBadRequest, err.Error())
}
metadata, err = normalizeClientAutoLoginMetadata(metadata)
if err != nil {
return errorJSON(c, fiber.StatusBadRequest, err.Error())
}
tokenAuthMethod := strings.TrimSpace(valueOr(req.TokenEndpointAuthMethod, ""))
if tokenAuthMethod == "" {
@@ -1766,6 +1852,10 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
if err != nil {
return errorJSON(c, fiber.StatusBadRequest, err.Error())
}
metadata, err = normalizeClientAutoLoginMetadata(metadata)
if err != nil {
return errorJSON(c, fiber.StatusBadRequest, err.Error())
}
resolvedClientType := currentSummary.Type
if clientType != "" {
resolvedClientType = clientType
@@ -2575,6 +2665,30 @@ func readMetadataBoolValue(metadata map[string]interface{}, key string) bool {
return value
}
func normalizeClientAutoLoginMetadata(metadata map[string]interface{}) (map[string]interface{}, error) {
if metadata == nil {
return metadata, nil
}
supported := readMetadataBoolValue(metadata, domain.MetadataAutoLoginSupported)
rawURL := strings.TrimSpace(readMetadataStringValue(metadata, domain.MetadataAutoLoginURL))
metadata[domain.MetadataAutoLoginSupported] = supported
if !supported {
delete(metadata, domain.MetadataAutoLoginURL)
return metadata, nil
}
if rawURL == "" {
return nil, errors.New("auto_login_url is required when auto_login_supported is true")
}
parsed, err := url.Parse(rawURL)
if err != nil || parsed.Scheme == "" || parsed.Host == "" || (parsed.Scheme != "https" && parsed.Scheme != "http") {
return nil, errors.New("auto_login_url must be an http or https URL")
}
metadata[domain.MetadataAutoLoginURL] = rawURL
return metadata, nil
}
func normalizeHeadlessClientConfig(
clientType string,
tokenAuthMethod string,