219 lines
6.5 KiB
Go
219 lines
6.5 KiB
Go
package controllers
|
|
|
|
import (
|
|
"apiserver/backend/auth"
|
|
"apiserver/common"
|
|
"apiserver/models"
|
|
"apiserver/store"
|
|
"apiserver/utils"
|
|
|
|
logger "apiserver/utils"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/golang-jwt/jwt/v4"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Define a struct for handling authentication operations
|
|
type AuthController struct{}
|
|
|
|
// generate JWT token
|
|
func generateJWT(username string, IP string) (string, error) {
|
|
//appConfig := utils.AppYamlConfig
|
|
// Create claims with user info
|
|
claims := models.JWTClaims{
|
|
Username: username,
|
|
IP: IP,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(utils.AuthenticationYamlConfig.Token.TokenExpireTime) * time.Second)),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
NotBefore: jwt.NewNumericDate(time.Now()),
|
|
},
|
|
}
|
|
|
|
// Create token with claims
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
// Generate signed token
|
|
secretKey := []byte(utils.AuthenticationYamlConfig.Token.SecretKey)
|
|
// In production, use environment variable
|
|
signedToken, err := token.SignedString(secretKey)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return signedToken, nil
|
|
}
|
|
|
|
// applyQueueRBACFilter appends a queue_name:in:(...) filter exactly once
|
|
// based on the user's accessible queues. If the user already specified a
|
|
// queue_name filter, the RBAC filter is not added.
|
|
func applyQueueRBACFilter(filters []utils.FilterCondition, accessQueues []string) []utils.FilterCondition {
|
|
if len(accessQueues) == 0 {
|
|
return filters
|
|
}
|
|
for _, f := range filters {
|
|
if f.Field == "queue_name" {
|
|
return filters
|
|
}
|
|
}
|
|
return append(filters, utils.FilterCondition{
|
|
Field: "queue_name",
|
|
Operator: "in",
|
|
Value: "(" + strings.Join(accessQueues, ",") + ")",
|
|
})
|
|
}
|
|
func storeToken(token string, username string) error {
|
|
tokenDir := filepath.Join(utils.Global_TopDir, "tokens", username)
|
|
if err := os.MkdirAll(tokenDir, 0700); err != nil {
|
|
logger.Error("Failed to create token directory", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
tokenFile := filepath.Join(tokenDir, "token")
|
|
if err := os.WriteFile(tokenFile, []byte(token), 0600); err != nil {
|
|
logger.Error("Failed to write token file", zap.Error(err))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// removeToken removes the JWT file for a user.
|
|
func removeToken(username string) error {
|
|
tokenDir := filepath.Join(utils.Global_TopDir, "tokens", username)
|
|
tokenFile := filepath.Join(tokenDir, "token")
|
|
if err := os.Remove(tokenFile); err != nil {
|
|
return err
|
|
}
|
|
// Remove user directory if empty
|
|
os.Remove(tokenDir)
|
|
return nil
|
|
}
|
|
|
|
// User logon
|
|
func (a AuthController) Logon(ctx *gin.Context) {
|
|
var req models.LogonRequest
|
|
|
|
if err := utils.BindJSONStrict(ctx, &req); err != nil {
|
|
logger.Error("Invalid request parameters", zap.Error(err))
|
|
ReturnError(ctx, common.ERROR_CODE, err.Error(), nil)
|
|
return
|
|
}
|
|
|
|
if auth.GlobalProvider == nil {
|
|
logger.Error("No auth provider configured")
|
|
ReturnError(ctx, common.ERROR_CODE, "authentication provider not configured", nil)
|
|
return
|
|
}
|
|
|
|
// Authenticate via provider
|
|
if err := auth.GlobalProvider.Authenticate(req.Username, req.Password); err != nil {
|
|
ReturnError(ctx, common.ERROR_CODE, err.Error(), nil)
|
|
return
|
|
}
|
|
|
|
// Auto-assign roles from provider
|
|
if roles := auth.GlobalProvider.GetUserRoles(req.Username); len(roles) > 0 {
|
|
for _, role := range roles {
|
|
store.AddRoleForUser(req.Username, role)
|
|
}
|
|
}
|
|
|
|
// Generate JWT token
|
|
if utils.AuthenticationYamlConfig.Token.Type == "jwt" {
|
|
token, err := generateJWT(req.Username, ctx.ClientIP())
|
|
if err != nil {
|
|
logger.Error("Failed to generate JWT token", zap.Error(err))
|
|
ReturnError(ctx, common.ERROR_CODE, common.ERROR_JWT_GENERATION_MSG+err.Error(), nil)
|
|
return
|
|
}
|
|
|
|
// Store token in server-managed directory
|
|
if err := storeToken(token, req.Username); err != nil {
|
|
logger.Error("Failed to store token", zap.Error(err))
|
|
ReturnError(ctx, common.ERROR_CODE, "Failed to store token", nil)
|
|
return
|
|
}
|
|
|
|
tokenDir := filepath.Join(utils.Global_TopDir, "tokens", req.Username)
|
|
response := models.LogonResponse{
|
|
Token: token,
|
|
Path: tokenDir,
|
|
IP: ctx.ClientIP(),
|
|
TokenExpireTime: utils.AuthenticationYamlConfig.Token.TokenExpireTime,
|
|
}
|
|
logger.Info("Post /logon successfully from ", zap.String("client_ip", ctx.ClientIP()))
|
|
ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, response)
|
|
}
|
|
}
|
|
|
|
// User logout
|
|
func (a AuthController) Logout(ctx *gin.Context) {
|
|
logger.Debug("Enter Logout() function")
|
|
// get token from header
|
|
token := ctx.GetHeader("Authorization")
|
|
|
|
// get username from token
|
|
var err error
|
|
username, err := GetUsernameFromToken(strings.TrimPrefix(token, "Bearer "))
|
|
if err != nil {
|
|
logger.Error(common.ERROR_GET_USERNAME_MSG, zap.Error(err))
|
|
ReturnError(ctx, common.ERROR_CODE, common.ERROR_GET_USERNAME_MSG+": "+err.Error(), nil)
|
|
return
|
|
}
|
|
logger.Debug("username", zap.String("username", username))
|
|
|
|
// Remove token file from server-managed directory
|
|
if err := removeToken(username); err != nil {
|
|
logger.Error(common.ERROR_REMOVE_TOKEN_MSG, zap.Error(err))
|
|
ReturnError(ctx, common.ERROR_CODE, common.ERROR_REMOVE_TOKEN_MSG+": "+err.Error(), nil)
|
|
return
|
|
}
|
|
// Mock successful logout
|
|
response := map[string]interface{}{
|
|
"username": username,
|
|
"message": "Logout successful",
|
|
}
|
|
ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, response)
|
|
}
|
|
|
|
func GetUsernameFromToken(tokenString string) (string, error) {
|
|
token, err := jwt.Parse(strings.TrimPrefix(tokenString, "Bearer "), func(token *jwt.Token) (interface{}, error) {
|
|
// Validate the algorithm
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
}
|
|
return []byte(utils.AuthenticationYamlConfig.Token.SecretKey), nil
|
|
})
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
|
if username, ok := claims["username"].(string); ok {
|
|
return username, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("invalid token")
|
|
}
|
|
|
|
// getAccessQueues returns the list of queues the user can access for the given permission.
|
|
// Returns an error if the user has no such permission.
|
|
func getAccessQueues(username, permID string) ([]string, error) {
|
|
perm, err := GetRequireResourcePermission(username, permID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch perm.DomainType {
|
|
case models.DomainTypeAll:
|
|
return store.GetAllQueueNames(), nil
|
|
case models.DomainTypeQueue:
|
|
return perm.DomainValues, nil
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|