feat: add business line permissions and Wayne namespace mapping

This commit is contained in:
mac
2026-07-17 15:02:18 +08:00
parent e1d755b2df
commit d264924d1e
23 changed files with 1343 additions and 81 deletions
+2 -1
View File
@@ -11,4 +11,5 @@ web/dist/
certs/
scripts/
data/
docs/
docs/*
!docs/mysql-init.sql
+47
View File
@@ -0,0 +1,47 @@
CREATE DATABASE IF NOT EXISTS authserver
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON authserver.* TO 'auth'@'localhost' IDENTIFIED BY 'auth';
GRANT ALL PRIVILEGES ON authserver.* TO 'auth'@'127.0.0.1' IDENTIFIED BY 'auth';
GRANT ALL PRIVILEGES ON authserver.* TO 'auth'@'192.168.65.%' IDENTIFIED BY 'auth';
GRANT ALL PRIVILEGES ON authserver.* TO 'auth'@'%' IDENTIFIED BY 'auth';
FLUSH PRIVILEGES;
USE authserver;
CREATE TABLE IF NOT EXISTS `business_lines` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` datetime(3) DEFAULT NULL,
`updated_at` datetime(3) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_business_lines_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `business_line_users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`business_line_id` bigint unsigned NOT NULL,
`user_id` bigint unsigned NOT NULL,
`permission` bigint NOT NULL DEFAULT '1',
`created_at` datetime(3) DEFAULT NULL,
`updated_at` datetime(3) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_business_line_users_unique` (`business_line_id`,`user_id`),
KEY `idx_business_line_users_business_line_id` (`business_line_id`),
KEY `idx_business_line_users_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `business_line_wayne_namespaces` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`business_line_id` bigint unsigned NOT NULL,
`wayne_namespace_id` bigint unsigned NOT NULL,
`wayne_namespace_name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`kube_namespace` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`created_at` datetime(3) DEFAULT NULL,
`updated_at` datetime(3) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_business_line_wayne_namespaces_unique` (`business_line_id`,`wayne_namespace_id`),
KEY `idx_business_line_wayne_namespaces_business_line_id` (`business_line_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+3
View File
@@ -15,6 +15,9 @@ func AutoMigrate(db *gorm.DB) error {
return db.AutoMigrate(
&model.User{},
&model.WayenCredential{},
&model.BusinessLine{},
&model.BusinessLineUser{},
&model.BusinessLineWayneNamespace{},
&model.AccessToken{},
&model.AuditLog{},
)
+431
View File
@@ -0,0 +1,431 @@
package handler
import (
"errors"
"net/http"
"strconv"
"time"
"github.com/1024XEngineer/xinfra/server/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type BusinessLineHandler struct {
db *gorm.DB
}
type BusinessLineWithPermission struct {
ID uint64 `json:"id"`
Name string `json:"name"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
Permission int `json:"permission"`
}
type GrantBusinessLinePermissionRequest struct {
BusinessLineID uint64 `json:"business_line_id" binding:"required"`
TargetUserID uint64 `json:"target_user_id" binding:"required"`
TargetBusinessLineID uint64 `json:"target_business_line_id" binding:"required"`
Permission int `json:"permission"`
}
type BusinessLinePayload struct {
Name string `json:"name" binding:"required"`
}
type WayneNamespaceBindingPayload struct {
Namespaces []WayneNamespaceBindingItem `json:"namespaces"`
}
type WayneNamespaceBindingItem struct {
ID uint64 `json:"id" binding:"required"`
Name string `json:"name"`
KubeNamespace string `json:"kubeNamespace"`
}
func NewBusinessLineHandler(db *gorm.DB) *BusinessLineHandler {
return &BusinessLineHandler{db: db}
}
func (h *BusinessLineHandler) ListCurrentUserBusinessLines(c *gin.Context) {
claims, ok := CurrentClaims(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
return
}
if claims.IsAdmin {
var rows []model.BusinessLine
if err := h.db.Order("id ASC").Find(&rows).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
items := make([]BusinessLineWithPermission, 0, len(rows))
for _, row := range rows {
items = append(items, BusinessLineWithPermission{
ID: row.ID,
Name: row.Name,
CreatedAt: row.CreatedAt.Format(time.RFC3339),
UpdatedAt: row.UpdatedAt.Format(time.RFC3339),
Permission: 0,
})
}
c.JSON(http.StatusOK, gin.H{"items": items})
return
}
var rows []struct {
ID uint64
Name string
CreatedAt time.Time
UpdatedAt time.Time
Permission int
}
if err := h.db.
Table("business_line_users").
Select("business_lines.id, business_lines.name, business_lines.created_at, business_lines.updated_at, business_line_users.permission").
Joins("JOIN business_lines ON business_lines.id = business_line_users.business_line_id").
Where("business_line_users.user_id = ?", claims.UserID).
Order("business_lines.id ASC").
Scan(&rows).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
items := make([]BusinessLineWithPermission, 0, len(rows))
for _, row := range rows {
items = append(items, BusinessLineWithPermission{
ID: row.ID,
Name: row.Name,
CreatedAt: row.CreatedAt.Format(time.RFC3339),
UpdatedAt: row.UpdatedAt.Format(time.RFC3339),
Permission: row.Permission,
})
}
c.JSON(http.StatusOK, gin.H{"items": items})
}
func (h *BusinessLineHandler) ListAll(c *gin.Context) {
if !requirePlatformAdmin(c) {
return
}
var rows []model.BusinessLine
if err := h.db.Order("id ASC").Find(&rows).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
items := make([]gin.H, 0, len(rows))
for _, row := range rows {
items = append(items, gin.H{
"id": row.ID,
"name": row.Name,
"created_at": row.CreatedAt.Format(time.RFC3339),
"updated_at": row.UpdatedAt.Format(time.RFC3339),
})
}
c.JSON(http.StatusOK, gin.H{"items": items})
}
func (h *BusinessLineHandler) Create(c *gin.Context) {
if !requirePlatformAdmin(c) {
return
}
var req BusinessLinePayload
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
item := model.BusinessLine{Name: req.Name}
if err := h.db.Create(&item).Error; err != nil {
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
return
}
writeBusinessLine(c, item)
}
func (h *BusinessLineHandler) Update(c *gin.Context) {
if !requirePlatformAdmin(c) {
return
}
var req BusinessLinePayload
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var item model.BusinessLine
if err := h.db.First(&item, "id = ?", c.Param("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "business line not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if err := h.db.Model(&item).Update("name", req.Name).Error; err != nil {
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
return
}
item.Name = req.Name
writeBusinessLine(c, item)
}
func (h *BusinessLineHandler) Delete(c *gin.Context) {
if !requirePlatformAdmin(c) {
return
}
var item model.BusinessLine
if err := h.db.First(&item, "id = ?", c.Param("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "business line not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if err := h.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("business_line_id = ?", item.ID).Delete(&model.BusinessLineUser{}).Error; err != nil {
return err
}
if err := tx.Where("business_line_id = ?", item.ID).Delete(&model.BusinessLineWayneNamespace{}).Error; err != nil {
return err
}
return tx.Delete(&item).Error
}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func (h *BusinessLineHandler) GrantPermission(c *gin.Context) {
claims, ok := CurrentClaims(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
return
}
var req GrantBusinessLinePermissionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Permission != 0 && req.Permission != 1 {
c.JSON(http.StatusBadRequest, gin.H{"error": "permission must be 0 or 1"})
return
}
if !claims.IsAdmin {
var currentBinding model.BusinessLineUser
if err := h.db.Where("business_line_id = ? AND user_id = ? AND permission = ?", req.BusinessLineID, claims.UserID, 0).
First(&currentBinding).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusForbidden, gin.H{"error": "current user is not platform admin or business line admin"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
var businessLine model.BusinessLine
if err := h.db.First(&businessLine, "id = ?", req.BusinessLineID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "business line not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var targetUser model.User
if err := h.db.Where("id = ? AND deleted_at IS NULL", req.TargetUserID).First(&targetUser).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "target user not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var targetBusinessLine model.BusinessLine
if err := h.db.First(&targetBusinessLine, "id = ?", req.TargetBusinessLineID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "target business line not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var binding model.BusinessLineUser
err := h.db.Where("business_line_id = ? AND user_id = ?", req.TargetBusinessLineID, req.TargetUserID).First(&binding).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
binding = model.BusinessLineUser{
BusinessLineID: req.TargetBusinessLineID,
UserID: req.TargetUserID,
Permission: req.Permission,
}
if err := h.db.Create(&binding).Error; err != nil {
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
return
}
} else if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
} else if binding.Permission != req.Permission {
if err := h.db.Model(&binding).Update("permission", req.Permission).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
binding.Permission = req.Permission
}
c.JSON(http.StatusOK, gin.H{
"id": binding.ID,
"business_line_id": binding.BusinessLineID,
"user_id": binding.UserID,
"permission": binding.Permission,
"created_at": binding.CreatedAt.Format(time.RFC3339),
"updated_at": binding.UpdatedAt.Format(time.RFC3339),
})
}
func (h *BusinessLineHandler) ListWayneNamespaces(c *gin.Context) {
businessLineID, ok := parseBusinessLineID(c)
if !ok {
return
}
if !h.canManageBusinessLine(c, businessLineID) {
return
}
var rows []model.BusinessLineWayneNamespace
if err := h.db.Where("business_line_id = ?", businessLineID).Order("wayne_namespace_id ASC").Find(&rows).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
items := make([]gin.H, 0, len(rows))
for _, row := range rows {
items = append(items, gin.H{
"id": row.WayneNamespaceID,
"name": row.WayneNamespaceName,
"kubeNamespace": row.KubeNamespace,
})
}
c.JSON(http.StatusOK, gin.H{"items": items})
}
func (h *BusinessLineHandler) ReplaceWayneNamespaces(c *gin.Context) {
businessLineID, ok := parseBusinessLineID(c)
if !ok {
return
}
if !h.canManageBusinessLine(c, businessLineID) {
return
}
var req WayneNamespaceBindingPayload
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("business_line_id = ?", businessLineID).Delete(&model.BusinessLineWayneNamespace{}).Error; err != nil {
return err
}
for _, item := range req.Namespaces {
row := model.BusinessLineWayneNamespace{
BusinessLineID: businessLineID,
WayneNamespaceID: item.ID,
WayneNamespaceName: item.Name,
KubeNamespace: item.KubeNamespace,
}
if err := tx.Create(&row).Error; err != nil {
return err
}
}
return nil
}); err != nil {
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func (h *BusinessLineHandler) canManageBusinessLine(c *gin.Context, businessLineID uint64) bool {
claims, ok := CurrentClaims(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
return false
}
var businessLine model.BusinessLine
if err := h.db.First(&businessLine, "id = ?", businessLineID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "business line not found"})
return false
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return false
}
if claims.IsAdmin {
return true
}
var binding model.BusinessLineUser
if err := h.db.Where("business_line_id = ? AND user_id = ? AND permission = ?", businessLineID, claims.UserID, 0).
First(&binding).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusForbidden, gin.H{"error": "current user is not platform admin or business line admin"})
return false
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return false
}
return true
}
func parseBusinessLineID(c *gin.Context) (uint64, bool) {
value, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil || value == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid business line id"})
return 0, false
}
return value, true
}
func requirePlatformAdmin(c *gin.Context) bool {
claims, ok := CurrentClaims(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
return false
}
if !claims.IsAdmin {
c.JSON(http.StatusForbidden, gin.H{"error": "platform admin required"})
return false
}
return true
}
func writeBusinessLine(c *gin.Context, item model.BusinessLine) {
c.JSON(http.StatusOK, gin.H{
"id": item.ID,
"name": item.Name,
"created_at": item.CreatedAt.Format(time.RFC3339),
"updated_at": item.UpdatedAt.Format(time.RFC3339),
})
}
+25 -3
View File
@@ -3,13 +3,18 @@ package handler
import (
"net/http"
"github.com/1024XEngineer/xinfra/server/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type UserHandler struct{}
type UserHandler struct {
db *gorm.DB
}
func NewUserHandler() *UserHandler {
return &UserHandler{}
func NewUserHandler(db *gorm.DB) *UserHandler {
return &UserHandler{db: db}
}
func (h *UserHandler) Me(c *gin.Context) {
@@ -25,3 +30,20 @@ func (h *UserHandler) Me(c *gin.Context) {
"is_admin": claims.IsAdmin,
})
}
func (h *UserHandler) List(c *gin.Context) {
var users []model.User
if err := h.db.Where("deleted_at IS NULL").Order("id ASC").Find(&users).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
items := make([]gin.H, 0, len(users))
for _, user := range users {
items = append(items, gin.H{
"uid": user.ID,
"username": user.Username,
})
}
c.JSON(http.StatusOK, gin.H{"items": items})
}
+56 -1
View File
@@ -2,7 +2,9 @@ package handler
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/1024XEngineer/xinfra/server/internal/auth"
@@ -40,7 +42,12 @@ func (h *WayenHandler) Login(c *gin.Context) {
return
}
result, err := h.wayen.Login(email, claims.Username)
refOverride, ok := h.resolveWayneRef(c, claims)
if !ok {
return
}
result, err := h.wayen.Login(email, claims.Username, refOverride)
if err != nil {
h.writeAudit(c, claims.UserID, claims.Username, "deny", err.Error())
switch {
@@ -73,6 +80,54 @@ func (h *WayenHandler) Login(c *gin.Context) {
c.Redirect(http.StatusFound, result.TargetURL)
}
func (h *WayenHandler) resolveWayneRef(c *gin.Context, claims *auth.Claims) (string, bool) {
rawBusinessLineID := strings.TrimSpace(c.Query("business_line_id"))
if rawBusinessLineID == "" {
return "", true
}
businessLineID, err := strconv.ParseUint(rawBusinessLineID, 10, 64)
if err != nil || businessLineID == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid business_line_id"})
return "", false
}
var businessLine model.BusinessLine
if err := h.db.First(&businessLine, "id = ?", businessLineID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "business line not found"})
return "", false
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return "", false
}
if !claims.IsAdmin {
var binding model.BusinessLineUser
if err := h.db.Where("business_line_id = ? AND user_id = ?", businessLineID, claims.UserID).First(&binding).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusForbidden, gin.H{"error": "current user does not have business line permission"})
return "", false
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return "", false
}
}
var mapping model.BusinessLineWayneNamespace
if err := h.db.Where("business_line_id = ?", businessLineID).Order("wayne_namespace_id ASC").First(&mapping).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return "", true
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return "", false
}
if mapping.WayneNamespaceID == 0 {
return "", true
}
return fmt.Sprintf("/portal/namespace/%d/app", mapping.WayneNamespaceID), true
}
func (h *WayenHandler) GetCredential(c *gin.Context) {
claims, ok := CurrentClaims(c)
if !ok {
+26
View File
@@ -32,6 +32,32 @@ type WayenCredential struct {
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
type BusinessLine struct {
ID uint64 `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:128;not null;uniqueIndex" json:"name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type BusinessLineUser struct {
ID uint64 `gorm:"primaryKey" json:"id"`
BusinessLineID uint64 `gorm:"not null;uniqueIndex:idx_business_line_users_unique,priority:1;index" json:"business_line_id"`
UserID uint64 `gorm:"not null;uniqueIndex:idx_business_line_users_unique,priority:2;index" json:"user_id"`
Permission int `gorm:"not null;default:1" json:"permission"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type BusinessLineWayneNamespace struct {
ID uint64 `gorm:"primaryKey" json:"id"`
BusinessLineID uint64 `gorm:"not null;uniqueIndex:idx_business_line_wayne_namespaces_unique,priority:1;index" json:"business_line_id"`
WayneNamespaceID uint64 `gorm:"not null;uniqueIndex:idx_business_line_wayne_namespaces_unique,priority:2" json:"wayne_namespace_id"`
WayneNamespaceName string `gorm:"size:128;not null;default:''" json:"wayne_namespace_name"`
KubeNamespace string `gorm:"size:128;not null;default:''" json:"kube_namespace"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type AccessToken struct {
ID uint64 `gorm:"primaryKey" json:"id"`
UserID uint64 `gorm:"not null;index" json:"user_id"`
+11 -1
View File
@@ -73,7 +73,8 @@ func registerAuthServerRoutes(r *gin.Engine, deps Dependencies) {
healthHandler := handler.NewHealthHandler(deps.DB)
authHandler := handler.NewAuthHandler(deps.Config, authService)
userHandler := handler.NewUserHandler()
userHandler := handler.NewUserHandler(deps.DB)
businessLineHandler := handler.NewBusinessLineHandler(deps.DB)
wayenHandler := handler.NewWayenHandler(deps.DB, wayenService, auditService)
wayneRoleBindingHandler := handler.NewWayneRoleBindingHandler(wayneRoleBindingService, auditService)
clouddmHandler := handler.NewCloudDMHandler(deps.Config, auditService)
@@ -100,6 +101,15 @@ func registerAuthServerRoutes(r *gin.Engine, deps Dependencies) {
protected := v1.Group("")
protected.Use(handler.AuthMiddleware(deps.Config))
protected.GET("/users/me", userHandler.Me)
protected.GET("/users", userHandler.List)
protected.GET("/business-lines", businessLineHandler.ListCurrentUserBusinessLines)
protected.GET("/business-lines/all", businessLineHandler.ListAll)
protected.POST("/business-lines", businessLineHandler.Create)
protected.PUT("/business-lines/:id", businessLineHandler.Update)
protected.DELETE("/business-lines/:id", businessLineHandler.Delete)
protected.POST("/business-lines/authorizations", businessLineHandler.GrantPermission)
protected.GET("/business-lines/:id/wayne-namespaces", businessLineHandler.ListWayneNamespaces)
protected.PUT("/business-lines/:id/wayne-namespaces", businessLineHandler.ReplaceWayneNamespaces)
protected.GET("/wayen/login", wayenHandler.Login)
protected.GET("/wayen/credential", wayenHandler.GetCredential)
protected.PUT("/wayen/credential", wayenHandler.SaveCredential)
+4 -4
View File
@@ -48,7 +48,7 @@ func NewWayenService(cfg config.Config, db *gorm.DB) *WayenService {
}
}
func (s *WayenService) Login(email, username string) (*WayenLoginResult, error) {
func (s *WayenService) Login(email, username, refOverride string) (*WayenLoginResult, error) {
email = strings.TrimSpace(email)
if email == "" {
return nil, ErrWayenEmailMissing
@@ -58,7 +58,7 @@ func (s *WayenService) Login(email, username string) (*WayenLoginResult, error)
oauthLoginURL = strings.TrimSpace(s.cfg.OAuthRedirectURI)
}
if oauthLoginURL != "" && strings.TrimSpace(s.cfg.WayenTargetURL) != "" {
target, err := s.oauthLoginURL(oauthLoginURL, s.cfg.WayenTargetURL)
target, err := s.oauthLoginURL(oauthLoginURL, s.cfg.WayenTargetURL, refOverride)
if err != nil {
return nil, err
}
@@ -111,7 +111,7 @@ func (s *WayenService) Login(email, username string) (*WayenLoginResult, error)
}, nil
}
func (s *WayenService) oauthLoginURL(redirectURI, targetURL string) (string, error) {
func (s *WayenService) oauthLoginURL(redirectURI, targetURL, refOverride string) (string, error) {
parsed, err := url.Parse(strings.TrimSpace(redirectURI))
if err != nil {
return "", err
@@ -124,7 +124,7 @@ func (s *WayenService) oauthLoginURL(redirectURI, targetURL string) (string, err
next.Path = "/sign-in"
}
values := next.Query()
values.Set("ref", defaultConfigValue(s.cfg.WayenOAuthRef, "/portal/namespace/1/app"))
values.Set("ref", defaultConfigValue(refOverride, defaultConfigValue(s.cfg.WayenOAuthRef, "/portal/namespace/1/app")))
next.RawQuery = values.Encode()
query := parsed.Query()
+26 -2
View File
@@ -15,7 +15,7 @@ func TestWayenLoginUsesDedicatedOAuthLoginURL(t *testing.T) {
WayenOAuthRef: "/portal/namespace/1/app",
}, nil)
result, err := service.Login("eastsales@qiniu.com", "eastsales@qiniu.com")
result, err := service.Login("eastsales@qiniu.com", "eastsales@qiniu.com", "")
if err != nil {
t.Fatalf("Login() error = %v", err)
}
@@ -55,7 +55,7 @@ func TestWayenLoginFallsBackToOAuthRedirectURI(t *testing.T) {
WayenTargetURL: "http://218.11.5.223:32000/",
}, nil)
result, err := service.Login("eastsales@qiniu.com", "eastsales@qiniu.com")
result, err := service.Login("eastsales@qiniu.com", "eastsales@qiniu.com", "")
if err != nil {
t.Fatalf("Login() error = %v", err)
}
@@ -67,3 +67,27 @@ func TestWayenLoginFallsBackToOAuthRedirectURI(t *testing.T) {
t.Fatalf("target host = %q, want fallback redirect host", parsed.Host)
}
}
func TestWayenLoginUsesRefOverride(t *testing.T) {
service := NewWayenService(config.Config{
WayenOAuthLoginURL: "http://218.11.5.223:32000/login/oauth2/oauth2",
WayenTargetURL: "http://218.11.5.223:32000/",
WayenOAuthRef: "/portal/namespace/1/app",
}, nil)
result, err := service.Login("eastsales@qiniu.com", "eastsales@qiniu.com", "/portal/namespace/3/app")
if err != nil {
t.Fatalf("Login() error = %v", err)
}
parsed, err := url.Parse(result.TargetURL)
if err != nil {
t.Fatalf("invalid target url: %v", err)
}
parsedNext, err := url.Parse(parsed.Query().Get("next"))
if err != nil {
t.Fatalf("invalid next url: %v", err)
}
if parsedNext.Query().Get("ref") != "/portal/namespace/3/app" {
t.Fatalf("next ref = %q", parsedNext.Query().Get("ref"))
}
}