forked from baron/baron-sso
worksmobile 연동 & ory stack 26.2.0으로 업그레이드
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -39,6 +40,34 @@ func getEnv(key, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
func getEnvFileOrValue(fileKey string, valueKey string, fallback string) (string, error) {
|
||||
if path := strings.TrimSpace(getEnv(fileKey, "")); path != "" {
|
||||
value, err := readEnvFileValue(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
return getEnv(valueKey, fallback), nil
|
||||
}
|
||||
|
||||
func readEnvFileValue(path string) (string, error) {
|
||||
candidates := []string{path}
|
||||
if !filepath.IsAbs(path) {
|
||||
candidates = append(candidates, filepath.Join("..", path), filepath.Join("..", "..", path))
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, candidate := range candidates {
|
||||
data, err := os.ReadFile(candidate)
|
||||
if err == nil {
|
||||
return string(data), nil
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
return "", fmt.Errorf("read secret file %q: %w", path, lastErr)
|
||||
}
|
||||
|
||||
func normalizeDocsPrefix(prefix string) string {
|
||||
trimmed := strings.TrimSpace(prefix)
|
||||
if trimmed == "" || trimmed == "/" {
|
||||
@@ -268,11 +297,32 @@ func main() {
|
||||
userGroupRepo := repository.NewUserGroupRepository(db)
|
||||
userRepo := repository.NewUserRepository(db)
|
||||
ketoOutboxRepo := repository.NewKetoOutboxRepository(db) // Reuse or re-init
|
||||
worksmobileOutboxRepo := repository.NewWorksmobileOutboxRepository(db)
|
||||
sharedLinkRepo := repository.NewSharedLinkRepository(db)
|
||||
kratosAdminService := service.NewKratosAdminService()
|
||||
oryAdminProvider := service.NewOryProvider()
|
||||
|
||||
tenantService := service.NewTenantService(tenantRepo, userRepo, userGroupRepo, ketoOutboxRepo)
|
||||
worksmobilePrivateKey, err := getEnvFileOrValue("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE", "WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY", "")
|
||||
if err != nil {
|
||||
slog.Error("Worksmobile private key file could not be loaded", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
worksmobileClient := service.NewWorksmobileHTTPClientWithAuth(
|
||||
getEnv("WORKS_ADMIN_ACCESS_TOKEN", getEnv("WORKS_ADMIN_OAUTH_ACCESS_TOKEN", "")),
|
||||
getEnv("SAMAN_SCIM_LONGLIVE_TOKEN", ""),
|
||||
service.WorksmobileOAuthConfig{
|
||||
ClientID: getEnv("WORKS_ADMIN_OAUTH_CLIENT_ID", ""),
|
||||
ClientSecret: getEnv("WORKS_ADMIN_OAUTH_CLIENT_SECRET", ""),
|
||||
ServiceAccount: getEnv("WORKS_ADMIN_OAUTH_CLIENT_SERVICE_ACCOUNT", ""),
|
||||
PrivateKey: worksmobilePrivateKey,
|
||||
Scope: getEnv("WORKS_ADMIN_OAUTH_SCOPE", "directory"),
|
||||
},
|
||||
)
|
||||
worksmobileService := service.NewWorksmobileSyncService(tenantService, userRepo, worksmobileOutboxRepo, worksmobileClient)
|
||||
worksmobileRelayWorker := service.NewWorksmobileRelayWorker(worksmobileOutboxRepo, worksmobileClient)
|
||||
go worksmobileRelayWorker.Start(context.Background())
|
||||
slog.Info("✅ Worksmobile Relay Worker started")
|
||||
sharedLinkService := service.NewSharedLinkService(sharedLinkRepo)
|
||||
userGroupService := service.NewUserGroupService(userGroupRepo, userRepo, tenantRepo, ketoService, ketoOutboxRepo, kratosAdminService)
|
||||
tenantService.SetKetoService(ketoService) // Keto 주입
|
||||
@@ -301,6 +351,9 @@ func main() {
|
||||
userGroupHandler := handler.NewUserGroupHandler(userGroupService)
|
||||
relyingPartyHandler := handler.NewRelyingPartyHandler(relyingPartyService, kratosAdminService)
|
||||
userHandler := handler.NewUserHandler(kratosAdminService, oryAdminProvider, tenantService, ketoService, ketoOutboxRepo, userRepo, userGroupRepo, auditRepo)
|
||||
tenantHandler.SetWorksmobileSyncer(worksmobileService)
|
||||
userHandler.SetWorksmobileSyncer(worksmobileService)
|
||||
worksmobileHandler := handler.NewWorksmobileHandler(worksmobileService)
|
||||
apiKeyHandler := handler.NewApiKeyHandler(db)
|
||||
|
||||
// 3. Initialize Fiber
|
||||
@@ -532,6 +585,7 @@ func main() {
|
||||
|
||||
// Public Tenant Registration
|
||||
api.Post("/tenants/registration", tenantHandler.RegisterTenantPublic)
|
||||
api.Get("/admin/worksmobile/oauth/callback", worksmobileHandler.OAuthCallback)
|
||||
|
||||
// Tenant Context Middleware (identifies tenant from Host header)
|
||||
api.Use(middleware.TenantContextMiddleware(middleware.TenantContextConfig{
|
||||
@@ -643,6 +697,14 @@ func main() {
|
||||
admin.Post("/tenants/:id/owners/:userId", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), tenantHandler.AddOwner)
|
||||
admin.Delete("/tenants/:id/owners/:userId", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), tenantHandler.RemoveOwner)
|
||||
|
||||
admin.Get("/tenants/:tenantId/worksmobile", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.GetOverview)
|
||||
admin.Get("/tenants/:tenantId/worksmobile/comparison", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.GetComparison)
|
||||
admin.Get("/tenants/:tenantId/worksmobile/initial-passwords.csv", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.DownloadInitialPasswordsCSV)
|
||||
admin.Post("/tenants/:tenantId/worksmobile/backfill/dry-run", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.BackfillDryRun)
|
||||
admin.Post("/tenants/:tenantId/worksmobile/orgunits/:orgUnitId/sync", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.SyncOrgUnit)
|
||||
admin.Post("/tenants/:tenantId/worksmobile/users/:userId/sync", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.SyncUser)
|
||||
admin.Post("/tenants/:tenantId/worksmobile/jobs/:jobId/retry", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.RetryJob)
|
||||
|
||||
// Organization & Org-Chart Management (Tenant Admin/Super Admin)
|
||||
org := admin.Group("/tenants/:tenantId/organization")
|
||||
org.Get("/", middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "view"), userGroupHandler.List)
|
||||
|
||||
39
backend/cmd/server/worksmobile_config_test.go
Normal file
39
backend/cmd/server/worksmobile_config_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetEnvFileOrValueReadsSecretFile(t *testing.T) {
|
||||
t.Setenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY", "inline-value")
|
||||
|
||||
secretPath := filepath.Join(t.TempDir(), "worksmobile-private-key.pem")
|
||||
want := "-----BEGIN PRIVATE KEY-----\nsecret\n-----END PRIVATE KEY-----\n"
|
||||
if err := os.WriteFile(secretPath, []byte(want), 0o600); err != nil {
|
||||
t.Fatalf("failed to write secret file: %v", err)
|
||||
}
|
||||
t.Setenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE", secretPath)
|
||||
|
||||
got, err := getEnvFileOrValue("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE", "WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY", "")
|
||||
if err != nil {
|
||||
t.Fatalf("getEnvFileOrValue returned error: %v", err)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("secret value = %q, want file content", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnvFileOrValueFallsBackToRawEnv(t *testing.T) {
|
||||
t.Setenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY", "inline-value")
|
||||
t.Setenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE", "")
|
||||
|
||||
got, err := getEnvFileOrValue("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE", "WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY", "")
|
||||
if err != nil {
|
||||
t.Fatalf("getEnvFileOrValue returned error: %v", err)
|
||||
}
|
||||
if got != "inline-value" {
|
||||
t.Fatalf("secret value = %q, want raw env value", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user