첫 커밋: 로컬 프로젝트 업로드
This commit is contained in:
62
baron-sso/backend/internal/validator/schema_validator.go
Normal file
62
baron-sso/backend/internal/validator/schema_validator.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValidateIDPCompatibility checks if the provided IDP supports all required fields defined in the BrokerUser model.
|
||||
func ValidateIDPCompatibility(brokerModel any, idp domain.IdentityProvider) error {
|
||||
metadata, err := idp.GetMetadata()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch metadata from IDP %s: %w", idp.Name(), err)
|
||||
}
|
||||
|
||||
supportedMap := make(map[string]bool)
|
||||
for _, f := range metadata.SupportedFields {
|
||||
supportedMap[f] = true
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(brokerModel)
|
||||
if t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
for field := range t.Fields() {
|
||||
field := field
|
||||
|
||||
// Check "required" tag
|
||||
isRequired := field.Tag.Get("required") == "true"
|
||||
jsonTag := field.Tag.Get("json")
|
||||
fieldName := strings.Split(jsonTag, ",")[0]
|
||||
|
||||
// Skip if fieldName is empty or if it's the Attributes map (handled separately)
|
||||
if fieldName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if fieldName != "attributes" {
|
||||
if isRequired && !supportedMap[fieldName] {
|
||||
return fmt.Errorf("IDP %s does not support required field: %s", idp.Name(), fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
// Check "required_keys" tag for map types (Custom Attributes)
|
||||
if fieldName == "attributes" {
|
||||
reqKeys := field.Tag.Get("required_keys")
|
||||
if reqKeys != "" {
|
||||
keys := strings.SplitSeq(reqKeys, ",")
|
||||
for key := range keys {
|
||||
key = strings.TrimSpace(key)
|
||||
if !supportedMap[key] {
|
||||
return fmt.Errorf("IDP %s does not support required custom attribute: %s", idp.Name(), key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
145
baron-sso/backend/internal/validator/schema_validator_test.go
Normal file
145
baron-sso/backend/internal/validator/schema_validator_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// MockProvider는 IdentityProvider 인터페이스를 구현하는 테스트용 구조체입니다.
|
||||
type MockProvider struct {
|
||||
Supported []string
|
||||
MetadataErr error
|
||||
}
|
||||
|
||||
func (m *MockProvider) Name() string {
|
||||
return "MockIDP"
|
||||
}
|
||||
|
||||
func (m *MockProvider) GetMetadata() (*domain.IDPMetadata, error) {
|
||||
if m.MetadataErr != nil {
|
||||
return nil, m.MetadataErr
|
||||
}
|
||||
return &domain.IDPMetadata{
|
||||
SupportedFields: m.Supported,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockProvider) CreateUser(user *domain.BrokerUser, password string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *MockProvider) SignIn(loginID, password string) (*domain.AuthInfo, error) {
|
||||
return &domain.AuthInfo{}, nil
|
||||
}
|
||||
|
||||
func (m *MockProvider) UserExists(loginID string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *MockProvider) IssueSession(loginID string) (*domain.AuthInfo, error) {
|
||||
return nil, domain.ErrNotSupported
|
||||
}
|
||||
|
||||
func (m *MockProvider) InitiateLinkLogin(loginID, returnTo string) (*domain.LinkLoginInit, error) {
|
||||
return nil, domain.ErrNotSupported
|
||||
}
|
||||
|
||||
func (m *MockProvider) VerifyLoginCode(loginID, flowID, code string) (*domain.AuthInfo, error) {
|
||||
return nil, domain.ErrNotSupported
|
||||
}
|
||||
|
||||
func (m *MockProvider) GetPasswordPolicy() (*domain.PasswordPolicy, error) {
|
||||
return nil, domain.ErrNotSupported
|
||||
}
|
||||
|
||||
// Stub implementations to satisfy the IdentityProvider interface for this unit test.
|
||||
func (m *MockProvider) InitiatePasswordReset(loginID, redirectUrl string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockProvider) VerifyPasswordResetToken(token string) (*domain.AuthInfo, error) {
|
||||
return &domain.AuthInfo{}, nil
|
||||
}
|
||||
|
||||
func (m *MockProvider) UpdateUserPassword(loginID, newPassword string, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestValidateIDPCompatibility(t *testing.T) {
|
||||
// BrokerUser 모델은 다음과 같이 정의되어 있다고 가정합니다 (idp_models.go 참조):
|
||||
// ID (required), Email (required), Name, PhoneNumber
|
||||
// Attributes (required_keys: "grade", "department")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
supported []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "성공: 모든 필수 필드 지원",
|
||||
supported: []string{"id", "email", "name", "grade", "department"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "성공: 선택 필드(name) 누락이어도 성공",
|
||||
supported: []string{"id", "email", "grade", "department"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "실패: 필수 필드(id) 누락",
|
||||
supported: []string{"email", "grade", "department"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "실패: 필수 필드(email) 누락",
|
||||
supported: []string{"id", "grade", "department"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "실패: 커스텀 속성(grade) 누락",
|
||||
supported: []string{"id", "email", "department"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "실패: 커스텀 속성(department) 누락",
|
||||
supported: []string{"id", "email", "grade"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 테스트용 IDP Provider 생성
|
||||
mockIDP := &MockProvider{Supported: tt.supported}
|
||||
|
||||
// 검증 수행
|
||||
err := ValidateIDPCompatibility(domain.BrokerUser{}, mockIDP)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateIDPCompatibility() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateIDPCompatibilityMetadataError(t *testing.T) {
|
||||
mockIDP := &MockProvider{MetadataErr: errors.New("metadata unavailable")}
|
||||
|
||||
err := ValidateIDPCompatibility(domain.BrokerUser{}, mockIDP)
|
||||
if err == nil {
|
||||
t.Fatalf("expected metadata error")
|
||||
}
|
||||
if got := err.Error(); got != "failed to fetch metadata from IDP MockIDP: metadata unavailable" {
|
||||
t.Fatalf("unexpected error: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateIDPCompatibilityPointerModel(t *testing.T) {
|
||||
mockIDP := &MockProvider{Supported: []string{"id", "email", "grade", "department"}}
|
||||
|
||||
if err := ValidateIDPCompatibility(&domain.BrokerUser{}, mockIDP); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user