mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-23 08:00:55 +00:00
206 lines
6.8 KiB
Go
206 lines
6.8 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/core/app/model"
|
|
"github.com/1Panel-dev/1Panel/core/app/repo"
|
|
"github.com/1Panel-dev/1Panel/core/buserr"
|
|
"github.com/1Panel-dev/1Panel/core/constant"
|
|
"github.com/1Panel-dev/1Panel/core/global"
|
|
"github.com/1Panel-dev/1Panel/core/init/session/psession"
|
|
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const APIKeyOwnerPanelAdmin = "panel_admin"
|
|
const APIKeyOwnerEnterpriseUser = "enterprise_user"
|
|
|
|
func IsAPICredentialSetting(key string) bool {
|
|
switch strings.ToLower(strings.TrimSpace(key)) {
|
|
case "apikey", "apiinterfacestatus", "ipwhitelist", "apitrustedproxies", "apikeyvaliditytime", "encryptkey":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
type APIKeyOwner struct {
|
|
Type string
|
|
ID string
|
|
Name string
|
|
IsSuperAdmin bool
|
|
}
|
|
|
|
type APIKeyOwnerProvider interface {
|
|
CurrentAPIKeyOwner(c *gin.Context) (APIKeyOwner, error)
|
|
ResolveAPIKeyOwner(c *gin.Context, ownerType, ownerID string) (APIKeyOwner, error)
|
|
LoadLegacyAPIKey(owner APIKeyOwner) (APIAuthConfig, error)
|
|
SaveLegacyAPIKey(owner APIKeyOwner, config APIAuthConfig) error
|
|
}
|
|
|
|
type APIKeyBinding struct {
|
|
KeyID string
|
|
KeyName string
|
|
Owner APIKeyOwner
|
|
Revision uint64
|
|
Fingerprint string
|
|
}
|
|
|
|
func RequireAPIKeySession(c *gin.Context) (psession.SessionUser, error) {
|
|
if c == nil || c.GetBool("API_AUTH") || c.GetBool("LOCAL_REQUEST") || HasAPICredentials(c) || global.SESSION == nil {
|
|
return psession.SessionUser{}, buserr.New("ErrNotLogin")
|
|
}
|
|
u, err := global.SESSION.Get(c)
|
|
if err != nil || u.ID == "" || u.Name == "" {
|
|
return psession.SessionUser{}, buserr.New("ErrNotLogin")
|
|
}
|
|
c.Set(psession.GinContextSessionUserKey, u)
|
|
return u, nil
|
|
}
|
|
|
|
func HasAPICredentials(c *gin.Context) bool {
|
|
return c.GetHeader("1Panel-Token") != "" || c.GetHeader("1Panel-Timestamp") != "" || c.GetHeader("1Panel-Key-ID") != "" || c.GetHeader("1Panel-Signature-Version") != ""
|
|
}
|
|
|
|
func LoadLegacyAPIKeyPolicy(owner APIKeyOwner) (model.LegacyAPIKeyPolicy, error) {
|
|
policy := model.LegacyAPIKeyPolicy{OwnerType: owner.Type, OwnerID: owner.ID, AllowAppBinding: true, Revision: 1}
|
|
var saved model.LegacyAPIKeyPolicy
|
|
err := repo.APIKeyOwnerQuery(global.DB, owner.Type, owner.ID).First(&saved).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return policy, nil
|
|
}
|
|
return saved, err
|
|
}
|
|
|
|
func LegacyAPIKeyFingerprint(owner APIKeyOwner, config APIAuthConfig, policy model.LegacyAPIKeyPolicy) (string, uint64) {
|
|
data, _ := json.Marshal([]interface{}{owner.Type, owner.ID, config.ApiInterfaceStatus, config.ApiKey, config.IpWhiteList, config.ApiTrustedProxies, config.ApiKeyValidityTime, policy.AllowAppBinding, policy.Revision})
|
|
hash := sha256.Sum256(data)
|
|
return hex.EncodeToString(hash[:]), (binary.BigEndian.Uint64(hash[:8]) & ((1 << 53) - 1)) + 1
|
|
}
|
|
|
|
func APIKeySecret(key model.APIKey) (string, error) {
|
|
if key.SecretVersion != 1 || key.SecretCiphertext == "" {
|
|
return "", buserr.New("ErrApiConfigKeyInvalid")
|
|
}
|
|
master, err := encrypt.APIKeyEncryptionKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return encrypt.DecryptAPIKey(key.SecretCiphertext, key.ID, master)
|
|
}
|
|
|
|
func ValidateAPIKeyState(key model.APIKey, now time.Time) error {
|
|
if key.Status != constant.StatusEnable {
|
|
return buserr.New("ErrApiConfigStatusInvalid")
|
|
}
|
|
if key.ExpiresAt != nil && !now.Before(*key.ExpiresAt) {
|
|
return buserr.New("ErrApiConfigKeyExpired")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func configForAPIKey(key model.APIKey, secret string, owner APIKeyOwner) APIAuthConfig {
|
|
return APIAuthConfig{ApiInterfaceStatus: key.Status, ApiKey: secret, IpWhiteList: key.IPWhiteList, ApiTrustedProxies: key.APITrustedProxies, ApiKeyValidityTime: key.APIKeyValidityTime, KeyID: key.ID, KeyName: key.Name, KeyRevision: key.Revision, KeyExpiresAt: key.ExpiresAt, Owner: owner}
|
|
}
|
|
|
|
func loadMultiAPIKeyConfig(c *gin.Context, legacy APIAuthConfigLoader, provider APIKeyOwnerProvider) (APIAuthConfig, error) {
|
|
keyID := c.GetHeader("1Panel-Key-ID")
|
|
q := global.DB.Model(&model.APIKey{}).Select("id", "secret_ciphertext", "secret_version", "revision")
|
|
if keyID != "" {
|
|
q = q.Where("id = ?", keyID)
|
|
} else {
|
|
q = q.Where("status <> ?", "Revoked")
|
|
}
|
|
master, err := encrypt.APIKeyEncryptionKey()
|
|
if err != nil {
|
|
if keyID == "" {
|
|
return legacy(c)
|
|
}
|
|
return APIAuthConfig{}, err
|
|
}
|
|
var candidates []model.APIKey
|
|
var matched *model.APIKey
|
|
err = q.Order("id").FindInBatches(&candidates, 128, func(_ *gorm.DB, _ int) error {
|
|
for _, key := range candidates {
|
|
if key.SecretVersion != 1 {
|
|
continue
|
|
}
|
|
secret, err := encrypt.DecryptAPIKey(key.SecretCiphertext, key.ID, master)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if IsValid1PanelTokenWithVersion(c.GetHeader("1Panel-Token"), c.GetHeader("1Panel-Timestamp"), secret, c.GetHeader("1Panel-Signature-Version")) {
|
|
copy := key
|
|
matched = ©
|
|
return errAPIKeyMatched
|
|
}
|
|
}
|
|
return nil
|
|
}).Error
|
|
if err != nil && !errors.Is(err, errAPIKeyMatched) {
|
|
return APIAuthConfig{}, err
|
|
}
|
|
if matched == nil {
|
|
if keyID != "" {
|
|
return APIAuthConfig{}, buserr.New("ErrApiConfigKeyInvalid")
|
|
}
|
|
return legacy(c)
|
|
}
|
|
key, err := repo.NewAPIKeyRepo().Get(matched.ID)
|
|
if err != nil || key.Revision != matched.Revision {
|
|
return APIAuthConfig{}, buserr.New("ErrApiConfigStatusInvalid")
|
|
}
|
|
if err = ValidateAPIKeyState(key, time.Now()); err != nil {
|
|
return APIAuthConfig{}, err
|
|
}
|
|
owner, err := provider.ResolveAPIKeyOwner(c, key.OwnerType, key.OwnerID)
|
|
if err != nil {
|
|
return APIAuthConfig{}, buserr.New("ErrApiConfigStatusInvalid")
|
|
}
|
|
secret, err := APIKeySecret(key)
|
|
if err != nil {
|
|
return APIAuthConfig{}, buserr.New("ErrApiConfigKeyInvalid")
|
|
}
|
|
return configForAPIKey(key, secret, owner), nil
|
|
}
|
|
|
|
var errAPIKeyMatched = errors.New("API credential matched")
|
|
|
|
func SetAPIKeyContext(c *gin.Context, config APIAuthConfig, provider APIKeyOwnerProvider) {
|
|
if config.KeyID != "" {
|
|
c.Set("API_AUTH_KEY_KIND", "apiKey")
|
|
c.Set("API_AUTH_KEY_ID", config.KeyID)
|
|
c.Set("API_AUTH_KEY_NAME", config.KeyName)
|
|
c.Set("API_AUTH_KEY_REVISION", config.KeyRevision)
|
|
c.Set("API_AUTH_OWNER_TYPE", config.Owner.Type)
|
|
c.Set("API_AUTH_OWNER_ID", config.Owner.ID)
|
|
if config.KeyExpiresAt != nil {
|
|
c.Set("API_AUTH_KEY_EXPIRES_AT", *config.KeyExpiresAt)
|
|
}
|
|
role := "COMMON_USER"
|
|
if config.Owner.IsSuperAdmin {
|
|
role = "ADMIN"
|
|
}
|
|
c.Set(psession.GinContextSessionUserKey, psession.SessionUser{ID: config.Owner.ID, Name: config.Owner.Name, Role: role})
|
|
c.Set("API_AUTH_USERNAME", config.Owner.Name)
|
|
return
|
|
}
|
|
c.Set("API_AUTH_KEY_KIND", "legacy")
|
|
c.Set("API_AUTH_KEY_ID", "legacy")
|
|
c.Set("API_AUTH_KEY_NAME", "Legacy API Key")
|
|
if provider != nil {
|
|
if owner, err := provider.CurrentAPIKeyOwner(c); err == nil {
|
|
c.Set("API_AUTH_OWNER_TYPE", owner.Type)
|
|
c.Set("API_AUTH_OWNER_ID", owner.ID)
|
|
}
|
|
}
|
|
}
|