forked from baron/baron-sso
Implement tenant import and RP auto login policies
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user