Files
ttstd-infinite-canvas/backend/internal/app/settings.go

763 lines
25 KiB
Go

package app
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"infinite-canvas/backend/internal/model"
"gorm.io/gorm"
)
const ossSettingKey = "oss"
const encryptedSettingPrefix = "enc:v1:"
const defaultOSSPathPrefix = "open-ai-canvas"
const (
aliyunOSSProvider = "aliyun"
tencentCOSProvider = "tencent"
qiniuKodoProvider = "qiniu"
s3Provider = "s3"
)
type OSSSettingRequest struct {
Enabled bool `json:"enabled"`
Provider string `json:"provider"`
Region string `json:"region"`
Endpoint string `json:"endpoint"`
CDNBaseURL string `json:"cdnBaseUrl"`
Bucket string `json:"bucket"`
AccessKeyID string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
PublicBaseURL string `json:"publicBaseUrl"`
PathPrefix string `json:"pathPrefix"`
S3Preset string `json:"s3Preset"`
PathStyle bool `json:"pathStyle"`
SessionToken string `json:"sessionToken"`
AllowUserS3 bool `json:"allowUserS3"`
}
type PublicOSSSetting struct {
Enabled bool `json:"enabled"`
Provider string `json:"provider"`
Region string `json:"region"`
Endpoint string `json:"endpoint"`
CDNBaseURL string `json:"cdnBaseUrl"`
Bucket string `json:"bucket"`
AccessKeyID string `json:"accessKeyId"`
HasAccessKeySecret bool `json:"hasAccessKeySecret"`
PublicBaseURL string `json:"publicBaseUrl"`
PathPrefix string `json:"pathPrefix"`
S3Preset string `json:"s3Preset"`
PathStyle bool `json:"pathStyle"`
HasSessionToken bool `json:"hasSessionToken"`
StorageLocationID string `json:"storageLocationId"`
TestedAt *time.Time `json:"testedAt,omitempty"`
TestedDigest string `json:"testedDigest,omitempty"`
HistoryCount int64 `json:"historyCount"`
ReferencedResourceCount int64 `json:"referencedResourceCount"`
AllowUserS3 bool `json:"allowUserS3"`
UpdatedBy string `json:"updatedBy"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type ossSettingValue struct {
Enabled bool `json:"enabled"`
Provider string `json:"provider"`
Region string `json:"region"`
Endpoint string `json:"endpoint"`
CDNBaseURL string `json:"cdnBaseUrl"`
Bucket string `json:"bucket"`
AccessKeyID string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
PublicBaseURL string `json:"publicBaseUrl"`
PathPrefix string `json:"pathPrefix"`
S3Preset string `json:"s3Preset"`
PathStyle bool `json:"pathStyle"`
SessionToken string `json:"sessionToken"`
StorageLocationID string `json:"storageLocationId"`
AllowUserS3 bool `json:"allowUserS3"`
// 平台切换云厂商后仍需读取历史资源,因此仅归档非当前厂商的访问密钥。
ArchivedCredentials map[string]ossProviderCredentials `json:"archivedCredentials,omitempty"`
}
type ossProviderCredentials struct {
AccessKeyID string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
}
func (s *Service) AdminOSSSetting(actor *model.User) (*PublicOSSSetting, error) {
if err := s.RequireAdmin(actor); err != nil {
return nil, err
}
setting, value, err := s.readOSSSetting()
if err != nil {
return nil, err
}
public, err := s.publicOSSSetting(setting, value, "platform", "")
if err != nil {
return nil, err
}
return &public, nil
}
func (s *Service) UpdateOSSSetting(actor *model.User, req OSSSettingRequest) (*PublicOSSSetting, error) {
if err := s.RequireAdmin(actor); err != nil {
return nil, err
}
currentSetting, currentValue, err := s.readOSSSetting()
if err != nil {
return nil, err
}
next, err := ossSettingFromRequest(req, currentValue)
if err != nil {
return nil, err
}
if !next.Enabled {
if next.PublicBaseURL == "" {
return nil, BadAuthRequest("服务器本地存储需要填写服务器访问地址")
}
if _, err := validatePublicResourceBaseURL(next.PublicBaseURL); err != nil {
return nil, fmt.Errorf("服务器访问地址无效:%w", err)
}
}
next = archiveOSSProviderCredentials(next, currentValue)
if next.Provider == s3Provider && next.Enabled {
location, err := s.requireTestedS3Location("platform", "", next)
if err != nil {
return nil, err
}
next.StorageLocationID = location.ID
} else if next.Provider == s3Provider && storageLocationDigest(next) == storageLocationDigest(currentValue) {
next.StorageLocationID = currentValue.StorageLocationID
}
stored, err := s.encryptOSSSettingSecrets(next)
if err != nil {
return nil, err
}
valueJSON, err := json.Marshal(stored)
if err != nil {
return nil, err
}
setting := model.SystemSetting{
Key: ossSettingKey,
ValueJSON: string(valueJSON),
UpdatedBy: actor.ID,
}
if currentSetting != nil {
setting.CreatedAt = currentSetting.CreatedAt
}
if err := s.repo.SaveSystemSetting(&setting); err != nil {
return nil, err
}
if next.Provider == s3Provider && next.StorageLocationID != "" {
if err := s.repo.ActivateStorageLocation("platform", "", next.StorageLocationID, next.Enabled); err != nil {
return nil, err
}
} else if currentValue.Provider == s3Provider && currentValue.StorageLocationID != "" {
if err := s.repo.ActivateStorageLocation("platform", "", "", false); err != nil {
return nil, err
}
}
public, err := s.publicOSSSetting(&setting, next, "platform", "")
if err != nil {
return nil, err
}
return &public, nil
}
func (s *Service) UserOSSSetting(actor *model.User) (*PublicOSSSetting, error) {
if actor == nil {
return nil, Unauthorized("请先登录")
}
setting, value, err := s.readUserOSSSetting(actor.ID)
if err != nil {
return nil, err
}
_, platform, err := s.readOSSSetting()
if err != nil {
return nil, err
}
public, err := s.publicUserOSSSetting(setting, value, actor.ID, platform.AllowUserS3)
if err != nil {
return nil, err
}
return &public, nil
}
func (s *Service) UpdateUserOSSSetting(actor *model.User, req OSSSettingRequest) (*PublicOSSSetting, error) {
if actor == nil {
return nil, Unauthorized("请先登录")
}
_, currentValue, err := s.readUserOSSSetting(actor.ID)
if err != nil {
return nil, err
}
next, err := ossSettingFromRequest(req, currentValue)
if err != nil {
return nil, err
}
_, platform, err := s.readOSSSetting()
if err != nil {
return nil, err
}
if next.Enabled && next.Provider == s3Provider && !platform.AllowUserS3 {
return nil, Forbidden("平台管理员尚未允许个人 S3 兼容存储")
}
if next.Provider == s3Provider && next.Enabled {
location, err := s.requireTestedS3Location("user", actor.ID, next)
if err != nil {
return nil, err
}
next.StorageLocationID = location.ID
} else if next.Provider == s3Provider && storageLocationDigest(next) == storageLocationDigest(currentValue) {
next.StorageLocationID = currentValue.StorageLocationID
}
stored, err := s.encryptOSSSettingSecrets(next)
if err != nil {
return nil, err
}
valueJSON, err := json.Marshal(stored)
if err != nil {
return nil, err
}
// 配置按版本追加而不是覆盖,资源会固定引用创建时的版本。
setting := model.UserOSSSetting{ID: newID(), UserID: actor.ID, Enabled: next.Enabled, ValueJSON: string(valueJSON)}
if err := s.repo.CreateUserOSSSetting(&setting); err != nil {
return nil, err
}
if next.Provider == s3Provider && next.StorageLocationID != "" {
if err := s.repo.ActivateStorageLocation("user", actor.ID, next.StorageLocationID, next.Enabled); err != nil {
return nil, err
}
} else if currentValue.Provider == s3Provider && currentValue.StorageLocationID != "" {
if err := s.repo.ActivateStorageLocation("user", actor.ID, "", false); err != nil {
return nil, err
}
}
public, err := s.publicUserOSSSetting(&setting, next, actor.ID, platform.AllowUserS3)
if err != nil {
return nil, err
}
return &public, nil
}
func (s *Service) readOSSSetting() (*model.SystemSetting, ossSettingValue, error) {
setting, err := s.repo.SystemSetting(ossSettingKey)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, normalizeOSSSetting(defaultOSSSetting()), nil
}
if err != nil {
return nil, ossSettingValue{}, err
}
value := defaultOSSSetting()
if strings.TrimSpace(setting.ValueJSON) != "" {
if err := json.Unmarshal([]byte(setting.ValueJSON), &value); err != nil {
return nil, ossSettingValue{}, errors.New("平台 OSS 配置格式无效")
}
}
needsMigration, err := s.decryptOSSSettingSecrets(&value)
if err != nil {
return nil, ossSettingValue{}, err
}
if needsMigration {
migrated, err := s.encryptOSSSettingSecrets(value)
if err != nil {
return nil, ossSettingValue{}, err
}
encoded, err := json.Marshal(migrated)
if err != nil {
return nil, ossSettingValue{}, err
}
setting.ValueJSON = string(encoded)
if err := s.repo.SaveSystemSetting(setting); err != nil {
return nil, ossSettingValue{}, err
}
}
return setting, normalizeOSSSetting(value), nil
}
func (s *Service) readUserOSSSetting(userID string) (*model.UserOSSSetting, ossSettingValue, error) {
setting, err := s.repo.LatestUserOSSSetting(userID)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, normalizeOSSSetting(defaultOSSSetting()), nil
}
if err != nil {
return nil, ossSettingValue{}, err
}
value, err := s.userOSSSettingValue(setting)
return setting, value, err
}
func (s *Service) readUserOSSSettingByID(userID string, id string) (*model.UserOSSSetting, ossSettingValue, error) {
setting, err := s.repo.UserOSSSettingForUser(userID, id)
if err != nil {
return nil, ossSettingValue{}, err
}
value, err := s.userOSSSettingValue(setting)
return setting, value, err
}
func (s *Service) userOSSSettingValue(setting *model.UserOSSSetting) (ossSettingValue, error) {
value := defaultOSSSetting()
if strings.TrimSpace(setting.ValueJSON) != "" {
if err := json.Unmarshal([]byte(setting.ValueJSON), &value); err != nil {
return ossSettingValue{}, errors.New("用户 OSS 配置格式无效")
}
}
if _, err := s.decryptOSSSettingSecrets(&value); err != nil {
return ossSettingValue{}, err
}
value.Enabled = setting.Enabled
return normalizeOSSSetting(value), nil
}
func (s *Service) encryptOSSSettingSecrets(value ossSettingValue) (ossSettingValue, error) {
var err error
value.AccessKeySecret, err = s.encryptSettingSecret(value.AccessKeySecret)
if err != nil {
return ossSettingValue{}, err
}
value.SessionToken, err = s.encryptSettingSecret(value.SessionToken)
if err != nil {
return ossSettingValue{}, err
}
value.ArchivedCredentials = cloneOSSProviderCredentials(value.ArchivedCredentials)
for provider, credentials := range value.ArchivedCredentials {
credentials.AccessKeySecret, err = s.encryptSettingSecret(credentials.AccessKeySecret)
if err != nil {
return ossSettingValue{}, err
}
value.ArchivedCredentials[provider] = credentials
}
return value, nil
}
func (s *Service) decryptOSSSettingSecrets(value *ossSettingValue) (bool, error) {
needsMigration := value.AccessKeySecret != "" && !strings.HasPrefix(value.AccessKeySecret, encryptedSettingPrefix)
secret, err := s.decryptSettingSecret(value.AccessKeySecret)
if err != nil {
return false, err
}
value.AccessKeySecret = secret
if value.SessionToken != "" && !strings.HasPrefix(value.SessionToken, encryptedSettingPrefix) {
needsMigration = true
}
value.SessionToken, err = s.decryptSettingSecret(value.SessionToken)
if err != nil {
return false, err
}
value.ArchivedCredentials = cloneOSSProviderCredentials(value.ArchivedCredentials)
for provider, credentials := range value.ArchivedCredentials {
if credentials.AccessKeySecret != "" && !strings.HasPrefix(credentials.AccessKeySecret, encryptedSettingPrefix) {
needsMigration = true
}
credentials.AccessKeySecret, err = s.decryptSettingSecret(credentials.AccessKeySecret)
if err != nil {
return false, err
}
value.ArchivedCredentials[provider] = credentials
}
return needsMigration, nil
}
func (s *Service) encryptSettingSecret(value string) (string, error) {
if value == "" {
return "", nil
}
key, err := s.settingsEncryptionKey()
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
ciphertext := gcm.Seal(nil, nonce, []byte(value), nil)
return encryptedSettingPrefix + base64.RawStdEncoding.EncodeToString(append(nonce, ciphertext...)), nil
}
func (s *Service) decryptSettingSecret(value string) (string, error) {
if !strings.HasPrefix(value, encryptedSettingPrefix) {
return value, nil
}
payload, err := base64.RawStdEncoding.DecodeString(strings.TrimPrefix(value, encryptedSettingPrefix))
if err != nil {
return "", errors.New("OSS 密钥密文格式无效")
}
key, err := s.settingsEncryptionKey()
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
if len(payload) < gcm.NonceSize() {
return "", errors.New("OSS 密钥密文长度无效")
}
plaintext, err := gcm.Open(nil, payload[:gcm.NonceSize()], payload[gcm.NonceSize():], nil)
if err != nil {
return "", errors.New("OSS 密钥解密失败,请检查存储加密密钥")
}
return string(plaintext), nil
}
func (s *Service) settingsEncryptionKey() ([]byte, error) {
path := filepath.Join(s.dataDir, ".settings-key")
if data, err := os.ReadFile(path); err == nil && len(data) == 32 {
return data, nil
}
if err := os.MkdirAll(s.dataDir, 0o750); err != nil {
return nil, err
}
key := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return nil, err
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
if errors.Is(err, os.ErrExist) {
data, readErr := os.ReadFile(path)
if readErr != nil {
return nil, fmt.Errorf("读取存储加密密钥失败:%w", readErr)
}
if len(data) != 32 {
return nil, errors.New("存储加密密钥长度无效")
}
return data, nil
}
if err != nil {
return nil, err
}
if _, err := file.Write(key); err != nil {
_ = file.Close()
return nil, err
}
if err := file.Close(); err != nil {
return nil, err
}
return key, nil
}
func (s *Service) protectTaskSecrets(value interface{}) error {
switch item := value.(type) {
case map[string]interface{}:
for key, child := range item {
if isTaskSecretField(key) {
secret, _ := child.(string)
if secret != "" && secret != "system" && !strings.HasPrefix(secret, encryptedSettingPrefix) {
encrypted, err := s.encryptSettingSecret(secret)
if err != nil {
return err
}
item[key] = encrypted
}
continue
}
if err := s.protectTaskSecrets(child); err != nil {
return err
}
}
case []interface{}:
for _, child := range item {
if err := s.protectTaskSecrets(child); err != nil {
return err
}
}
}
return nil
}
func (s *Service) decryptTaskInputJSON(raw string) (string, error) {
if strings.TrimSpace(raw) == "" || !strings.Contains(raw, encryptedSettingPrefix) {
return raw, nil
}
var input interface{}
if err := json.Unmarshal([]byte(raw), &input); err != nil {
return "", err
}
if err := s.decryptTaskSecrets(input); err != nil {
return "", err
}
encoded, err := json.Marshal(input)
return string(encoded), err
}
func (s *Service) decryptTaskSecrets(value interface{}) error {
switch item := value.(type) {
case map[string]interface{}:
for key, child := range item {
if isTaskSecretField(key) {
secret, _ := child.(string)
if strings.HasPrefix(secret, encryptedSettingPrefix) {
plain, err := s.decryptSettingSecret(secret)
if err != nil {
return err
}
item[key] = plain
}
continue
}
if err := s.decryptTaskSecrets(child); err != nil {
return err
}
}
case []interface{}:
for _, child := range item {
if err := s.decryptTaskSecrets(child); err != nil {
return err
}
}
}
return nil
}
func isTaskSecretField(key string) bool {
switch key {
case "apiKey", "secretKey", "runningHubWalletApiKey", "runningHubUploadApiKey":
return true
default:
return false
}
}
func ossSettingFromRequest(req OSSSettingRequest, current ossSettingValue) (ossSettingValue, error) {
next := normalizeOSSSetting(ossSettingValue{
Enabled: req.Enabled,
Provider: strings.TrimSpace(req.Provider),
Region: strings.TrimSpace(req.Region),
Endpoint: strings.TrimRight(strings.TrimSpace(req.Endpoint), "/"),
CDNBaseURL: strings.TrimRight(strings.TrimSpace(req.CDNBaseURL), "/"),
Bucket: strings.TrimSpace(req.Bucket),
AccessKeyID: strings.TrimSpace(req.AccessKeyID),
AccessKeySecret: strings.TrimSpace(req.AccessKeySecret),
PublicBaseURL: strings.TrimRight(strings.TrimSpace(req.PublicBaseURL), "/"),
PathPrefix: strings.Trim(strings.TrimSpace(req.PathPrefix), "/"),
S3Preset: strings.TrimSpace(req.S3Preset),
PathStyle: req.PathStyle,
SessionToken: strings.TrimSpace(req.SessionToken),
AllowUserS3: req.AllowUserS3,
})
if next.Provider != aliyunOSSProvider && next.Provider != tencentCOSProvider && next.Provider != qiniuKodoProvider && next.Provider != s3Provider {
return next, BadAuthRequest("仅支持阿里云 OSS、腾讯云 COS、七牛云 Kodo 和通用 S3")
}
if next.Provider == s3Provider {
switch next.S3Preset {
case "aws", "r2", "b2", "rustfs", "custom":
default:
return next, BadAuthRequest("S3 预设无效")
}
}
current = normalizeOSSSetting(current)
// 同一云厂商修改存储位置时,留空仍表示保留现有密钥;切换厂商时不能复用。
if next.AccessKeySecret == "" && next.Provider == current.Provider {
next.AccessKeySecret = current.AccessKeySecret
}
if next.SessionToken == "" && next.Provider == current.Provider {
next.SessionToken = current.SessionToken
}
if next.Enabled {
if next.Bucket == "" {
return next, BadAuthRequest("请填写对象存储 Bucket")
}
if next.Endpoint == "" {
if next.Provider == tencentCOSProvider {
return next, BadAuthRequest("请填写腾讯云 COS Region 或 Endpoint")
}
if next.Provider == qiniuKodoProvider {
return next, BadAuthRequest("请填写七牛云 Kodo 上传 Endpoint")
}
if next.Provider == s3Provider {
return next, BadAuthRequest("请填写 S3 Endpoint 服务根 URL")
}
return next, BadAuthRequest("请填写阿里云 OSS Endpoint")
}
if next.Provider == s3Provider && next.Region == "" {
return next, BadAuthRequest("请填写 S3 Region")
}
var endpointErr error
if next.Provider == s3Provider {
_, endpointErr = validateStorageEndpoint(next.Endpoint)
} else {
_, endpointErr = ValidateOutboundURL(next.Endpoint)
}
if endpointErr != nil {
return next, endpointErr
}
if next.CDNBaseURL != "" {
if _, err := ossCDNBaseURL(next.CDNBaseURL); err != nil {
return next, BadAuthRequest(err.Error())
}
if _, err := ValidateOutboundURL(next.CDNBaseURL); err != nil {
return next, err
}
}
if next.AccessKeyID == "" {
return next, BadAuthRequest("请填写访问密钥 AccessKey")
}
if next.AccessKeySecret == "" {
return next, BadAuthRequest("请填写访问密钥 SecretKey")
}
}
return next, nil
}
func archiveOSSProviderCredentials(next ossSettingValue, current ossSettingValue) ossSettingValue {
next.ArchivedCredentials = cloneOSSProviderCredentials(current.ArchivedCredentials)
if current.Provider != next.Provider && (current.AccessKeyID != "" || current.AccessKeySecret != "") {
if next.ArchivedCredentials == nil {
next.ArchivedCredentials = make(map[string]ossProviderCredentials)
}
next.ArchivedCredentials[current.Provider] = ossProviderCredentials{AccessKeyID: current.AccessKeyID, AccessKeySecret: current.AccessKeySecret}
}
delete(next.ArchivedCredentials, next.Provider)
return next
}
func cloneOSSProviderCredentials(source map[string]ossProviderCredentials) map[string]ossProviderCredentials {
if len(source) == 0 {
return nil
}
cloned := make(map[string]ossProviderCredentials, len(source))
for provider, credentials := range source {
cloned[strings.ToLower(strings.TrimSpace(provider))] = ossProviderCredentials{
AccessKeyID: strings.TrimSpace(credentials.AccessKeyID),
AccessKeySecret: strings.TrimSpace(credentials.AccessKeySecret),
}
}
return cloned
}
func normalizeOSSSetting(value ossSettingValue) ossSettingValue {
value.Provider = strings.ToLower(strings.TrimSpace(value.Provider))
if value.Provider == "" {
value.Provider = aliyunOSSProvider
}
value.Region = strings.TrimSpace(value.Region)
value.Endpoint = strings.TrimRight(strings.TrimSpace(value.Endpoint), "/")
if value.Provider == tencentCOSProvider && value.Endpoint == "" && value.Region != "" {
value.Endpoint = "https://cos." + value.Region + ".myqcloud.com"
}
value.CDNBaseURL = strings.TrimRight(strings.TrimSpace(value.CDNBaseURL), "/")
value.Bucket = strings.TrimSpace(value.Bucket)
value.AccessKeyID = strings.TrimSpace(value.AccessKeyID)
value.AccessKeySecret = strings.TrimSpace(value.AccessKeySecret)
value.PublicBaseURL = strings.TrimRight(strings.TrimSpace(value.PublicBaseURL), "/")
value.PathPrefix = strings.Trim(strings.TrimSpace(value.PathPrefix), "/")
if value.PathPrefix == "" {
value.PathPrefix = defaultOSSPathPrefix
}
value.S3Preset = strings.ToLower(strings.TrimSpace(value.S3Preset))
if value.S3Preset == "" {
value.S3Preset = "custom"
}
value.SessionToken = strings.TrimSpace(value.SessionToken)
value.StorageLocationID = strings.TrimSpace(value.StorageLocationID)
value.ArchivedCredentials = cloneOSSProviderCredentials(value.ArchivedCredentials)
return value
}
func defaultOSSSetting() ossSettingValue {
return ossSettingValue{Provider: aliyunOSSProvider, PathPrefix: defaultOSSPathPrefix}
}
func (s *Service) publicOSSSetting(setting *model.SystemSetting, value ossSettingValue, scope string, ownerID string) (PublicOSSSetting, error) {
result := PublicOSSSetting{
Enabled: value.Enabled,
Provider: value.Provider,
Region: value.Region,
Endpoint: value.Endpoint,
CDNBaseURL: value.CDNBaseURL,
Bucket: value.Bucket,
AccessKeyID: value.AccessKeyID,
HasAccessKeySecret: strings.TrimSpace(value.AccessKeySecret) != "",
PublicBaseURL: value.PublicBaseURL,
PathPrefix: value.PathPrefix,
S3Preset: value.S3Preset,
PathStyle: value.PathStyle,
HasSessionToken: value.SessionToken != "",
StorageLocationID: value.StorageLocationID,
AllowUserS3: value.AllowUserS3,
}
if setting != nil {
result.UpdatedBy = setting.UpdatedBy
result.CreatedAt = setting.CreatedAt
result.UpdatedAt = setting.UpdatedAt
}
if err := s.populateStorageLocationPublic(&result, scope, ownerID, value.StorageLocationID); err != nil {
return PublicOSSSetting{}, err
}
return result, nil
}
func (s *Service) publicUserOSSSetting(setting *model.UserOSSSetting, value ossSettingValue, ownerID string, allowUserS3 bool) (PublicOSSSetting, error) {
result := PublicOSSSetting{
Enabled: value.Enabled,
Provider: value.Provider,
Region: value.Region,
Endpoint: value.Endpoint,
CDNBaseURL: value.CDNBaseURL,
Bucket: value.Bucket,
AccessKeyID: value.AccessKeyID,
HasAccessKeySecret: strings.TrimSpace(value.AccessKeySecret) != "",
PublicBaseURL: value.PublicBaseURL,
PathPrefix: value.PathPrefix,
S3Preset: value.S3Preset,
PathStyle: value.PathStyle,
HasSessionToken: value.SessionToken != "",
StorageLocationID: value.StorageLocationID,
AllowUserS3: allowUserS3,
}
if value.Provider == s3Provider && !allowUserS3 {
result.Enabled = false
}
if setting != nil {
result.UpdatedBy = setting.UserID
result.CreatedAt = setting.CreatedAt
result.UpdatedAt = setting.UpdatedAt
}
if err := s.populateStorageLocationPublic(&result, "user", ownerID, value.StorageLocationID); err != nil {
return PublicOSSSetting{}, err
}
return result, nil
}
func storageLocationDigest(value ossSettingValue) string {
value = normalizeOSSSetting(value)
payload := strings.Join([]string{value.Provider, value.Endpoint, value.Bucket, value.Region, strconv.FormatBool(value.PathStyle), value.PathPrefix}, "\x00")
sum := sha256.Sum256([]byte(payload))
return hex.EncodeToString(sum[:])
}
func storageTestDigest(value ossSettingValue) string {
payload := strings.Join([]string{storageLocationDigest(value), value.AccessKeyID, value.AccessKeySecret, value.SessionToken}, "\x00")
sum := sha256.Sum256([]byte(payload))
return hex.EncodeToString(sum[:])
}