forked from baron/baron-sso
외부 IdP 연동 관리 API 기능 구현
This commit is contained in:
@@ -36,6 +36,7 @@ func migrateSchemas(db *gorm.DB) error {
|
|||||||
&domain.User{},
|
&domain.User{},
|
||||||
&domain.Tenant{},
|
&domain.Tenant{},
|
||||||
&domain.ApiKey{},
|
&domain.ApiKey{},
|
||||||
|
&domain.IdentityProviderConfig{},
|
||||||
// &domain.RelyingParty{}, // TODO: Uncomment when model is ready
|
// &domain.RelyingParty{}, // TODO: Uncomment when model is ready
|
||||||
// &domain.UserConsent{}, // TODO: Uncomment when model is ready
|
// &domain.UserConsent{}, // TODO: Uncomment when model is ready
|
||||||
)
|
)
|
||||||
|
|||||||
51
backend/internal/domain/federation_models.go
Normal file
51
backend/internal/domain/federation_models.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProviderType defines the type of the identity provider.
|
||||||
|
type ProviderType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProviderTypeOIDC ProviderType = "oidc"
|
||||||
|
ProviderTypeSAML ProviderType = "saml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IdentityProviderConfig stores the configuration for an external Identity Provider.
|
||||||
|
type IdentityProviderConfig struct {
|
||||||
|
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()" json:"id"`
|
||||||
|
TenantID string `gorm:"type:uuid;not null;index" json:"tenant_id"`
|
||||||
|
Tenant Tenant `gorm:"foreignKey:TenantID" json:"-"` // Belongs to Tenant
|
||||||
|
ProviderType ProviderType `gorm:"type:varchar(10);not null" json:"provider_type"`
|
||||||
|
DisplayName string `gorm:"not null" json:"display_name"`
|
||||||
|
Status string `gorm:"default:'active'" json:"status"`
|
||||||
|
|
||||||
|
// OIDC Specific Fields
|
||||||
|
IssuerURL *string `gorm:"null" json:"issuer_url,omitempty"`
|
||||||
|
ClientID *string `gorm:"null" json:"client_id,omitempty"`
|
||||||
|
ClientSecret *string `gorm:"null" json:"client_secret,omitempty"`
|
||||||
|
// Scopes are space-separated
|
||||||
|
Scopes *string `gorm:"null" json:"scopes,omitempty"`
|
||||||
|
|
||||||
|
// SAML Specific Fields
|
||||||
|
MetadataURL *string `gorm:"null" json:"metadata_url,omitempty"`
|
||||||
|
MetadataXML *string `gorm:"type:text;null" json:"metadata_xml,omitempty"`
|
||||||
|
EntityID *string `gorm:"null" json:"entity_id,omitempty"`
|
||||||
|
AcsURL *string `gorm:"null" json:"acs_url,omitempty"`
|
||||||
|
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeCreate hook to generate UUID if not present.
|
||||||
|
func (idc *IdentityProviderConfig) BeforeCreate(tx *gorm.DB) (err error) {
|
||||||
|
if idc.ID == "" {
|
||||||
|
idc.ID = uuid.NewString()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
126
backend/internal/handler/federation_handler.go
Normal file
126
backend/internal/handler/federation_handler.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"baron-sso-backend/internal/domain"
|
||||||
|
"baron-sso-backend/internal/repository"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FederationHandler handles API requests for IdP federation configurations.
|
||||||
|
type FederationHandler struct {
|
||||||
|
Repo *repository.FederationRepository
|
||||||
|
DB *gorm.DB // For tenant existence checks
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFederationHandler creates a new FederationHandler.
|
||||||
|
func NewFederationHandler(repo *repository.FederationRepository, db *gorm.DB) *FederationHandler {
|
||||||
|
return &FederationHandler{Repo: repo, DB: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateIdpConfig handles the creation of a new IdP configuration.
|
||||||
|
func (h *FederationHandler) CreateIdpConfig(c *fiber.Ctx) error {
|
||||||
|
var req domain.IdentityProviderConfig
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if req.TenantID == "" || req.DisplayName == "" || req.ProviderType == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tenant_id, display_name, and provider_type are required"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if tenant exists
|
||||||
|
var tenant domain.Tenant
|
||||||
|
if err := h.DB.First(&tenant, "id = ?", req.TenantID).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tenant not found"})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Repo.Create(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIdpConfigByID handles retrieving a single IdP configuration.
|
||||||
|
func (h *FederationHandler) GetIdpConfigByID(c *fiber.Ctx) error {
|
||||||
|
id := c.Params("id")
|
||||||
|
config, err := h.Repo.GetByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "identity provider configuration not found"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListIdpConfigsForTenant handles listing all IdP configurations for a tenant.
|
||||||
|
func (h *FederationHandler) ListIdpConfigsForTenant(c *fiber.Ctx) error {
|
||||||
|
tenantID := c.Params("tenantId")
|
||||||
|
if tenantID == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tenantId is required"})
|
||||||
|
}
|
||||||
|
|
||||||
|
configs, err := h.Repo.ListByTenantID(tenantID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(configs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateIdpConfig handles updating an IdP configuration.
|
||||||
|
func (h *FederationHandler) UpdateIdpConfig(c *fiber.Ctx) error {
|
||||||
|
id := c.Params("id")
|
||||||
|
|
||||||
|
existingConfig, err := h.Repo.GetByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
if existingConfig == nil {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "identity provider configuration not found"})
|
||||||
|
}
|
||||||
|
|
||||||
|
var req domain.IdentityProviderConfig
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite fields
|
||||||
|
existingConfig.DisplayName = req.DisplayName
|
||||||
|
existingConfig.Status = req.Status
|
||||||
|
existingConfig.IssuerURL = req.IssuerURL
|
||||||
|
existingConfig.ClientID = req.ClientID
|
||||||
|
existingConfig.ClientSecret = req.ClientSecret
|
||||||
|
existingConfig.Scopes = req.Scopes
|
||||||
|
existingConfig.MetadataURL = req.MetadataURL
|
||||||
|
existingConfig.MetadataXML = req.MetadataXML
|
||||||
|
existingConfig.EntityID = req.EntityID
|
||||||
|
existingConfig.AcsURL = req.AcsURL
|
||||||
|
|
||||||
|
|
||||||
|
if err := h.Repo.Update(existingConfig); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(existingConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteIdpConfig handles deleting an IdP configuration.
|
||||||
|
func (h *FederationHandler) DeleteIdpConfig(c *fiber.Ctx) error {
|
||||||
|
id := c.Params("id")
|
||||||
|
|
||||||
|
if err := h.Repo.Delete(id); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendStatus(fiber.StatusNoContent)
|
||||||
|
}
|
||||||
54
backend/internal/repository/federation_repository.go
Normal file
54
backend/internal/repository/federation_repository.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"baron-sso-backend/internal/domain"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FederationRepository handles database operations for IdentityProviderConfig.
|
||||||
|
type FederationRepository struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFederationRepository creates a new FederationRepository.
|
||||||
|
func NewFederationRepository(db *gorm.DB) *FederationRepository {
|
||||||
|
return &FederationRepository{DB: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new identity provider configuration.
|
||||||
|
func (r *FederationRepository) Create(config *domain.IdentityProviderConfig) error {
|
||||||
|
return r.DB.Create(config).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID retrieves an identity provider configuration by its ID.
|
||||||
|
func (r *FederationRepository) GetByID(id string) (*domain.IdentityProviderConfig, error) {
|
||||||
|
var config domain.IdentityProviderConfig
|
||||||
|
if err := r.DB.First(&config, "id = ?", id).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil // Return nil, nil for not found
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByTenantID retrieves all identity provider configurations for a given tenant.
|
||||||
|
func (r *FederationRepository) ListByTenantID(tenantID string) ([]domain.IdentityProviderConfig, error) {
|
||||||
|
var configs []domain.IdentityProviderConfig
|
||||||
|
if err := r.DB.Where("tenant_id = ?", tenantID).Find(&configs).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return configs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates an existing identity provider configuration.
|
||||||
|
func (r *FederationRepository) Update(config *domain.IdentityProviderConfig) error {
|
||||||
|
return r.DB.Save(config).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes an identity provider configuration by its ID.
|
||||||
|
func (r *FederationRepository) Delete(id string) error {
|
||||||
|
return r.DB.Delete(&domain.IdentityProviderConfig{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user