mirror of
https://github.com/cloudreve/Cloudreve.git
synced 2025-12-25 15:42:47 +00:00
feat(encryption): add UI and settings for file encryption
This commit is contained in:
parent
16b02b1fb3
commit
e3580d9351
|
|
@ -131,9 +131,9 @@ type Dep interface {
|
||||||
// UAParser Get a singleton uaparser.Parser instance for user agent parsing.
|
// UAParser Get a singleton uaparser.Parser instance for user agent parsing.
|
||||||
UAParser() *uaparser.Parser
|
UAParser() *uaparser.Parser
|
||||||
// MasterEncryptKeyVault Get a singleton encrypt.MasterEncryptKeyVault instance for master encrypt key vault.
|
// MasterEncryptKeyVault Get a singleton encrypt.MasterEncryptKeyVault instance for master encrypt key vault.
|
||||||
MasterEncryptKeyVault() encrypt.MasterEncryptKeyVault
|
MasterEncryptKeyVault(ctx context.Context) encrypt.MasterEncryptKeyVault
|
||||||
// EncryptorFactory Get a new encrypt.CryptorFactory instance.
|
// EncryptorFactory Get a new encrypt.CryptorFactory instance.
|
||||||
EncryptorFactory() encrypt.CryptorFactory
|
EncryptorFactory(ctx context.Context) encrypt.CryptorFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
type dependency struct {
|
type dependency struct {
|
||||||
|
|
@ -183,7 +183,6 @@ type dependency struct {
|
||||||
configPath string
|
configPath string
|
||||||
isPro bool
|
isPro bool
|
||||||
requiredDbVersion string
|
requiredDbVersion string
|
||||||
licenseKey string
|
|
||||||
|
|
||||||
// Protects inner deps that can be reloaded at runtime.
|
// Protects inner deps that can be reloaded at runtime.
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
@ -212,17 +211,17 @@ func (d *dependency) RequestClient(opts ...request.Option) request.Client {
|
||||||
return request.NewClient(d.ConfigProvider(), opts...)
|
return request.NewClient(d.ConfigProvider(), opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dependency) MasterEncryptKeyVault() encrypt.MasterEncryptKeyVault {
|
func (d *dependency) MasterEncryptKeyVault(ctx context.Context) encrypt.MasterEncryptKeyVault {
|
||||||
if d.masterEncryptKeyVault != nil {
|
if d.masterEncryptKeyVault != nil {
|
||||||
return d.masterEncryptKeyVault
|
return d.masterEncryptKeyVault
|
||||||
}
|
}
|
||||||
|
|
||||||
d.masterEncryptKeyVault = encrypt.NewMasterEncryptKeyVault(d.SettingProvider())
|
d.masterEncryptKeyVault = encrypt.NewMasterEncryptKeyVault(ctx, d.SettingProvider())
|
||||||
return d.masterEncryptKeyVault
|
return d.masterEncryptKeyVault
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dependency) EncryptorFactory() encrypt.CryptorFactory {
|
func (d *dependency) EncryptorFactory(ctx context.Context) encrypt.CryptorFactory {
|
||||||
return encrypt.NewCryptorFactory(d.MasterEncryptKeyVault())
|
return encrypt.NewCryptorFactory(d.MasterEncryptKeyVault(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dependency) WebAuthn(ctx context.Context) (*webauthn.WebAuthn, error) {
|
func (d *dependency) WebAuthn(ctx context.Context) (*webauthn.WebAuthn, error) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package dependency
|
package dependency
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||||
"github.com/cloudreve/Cloudreve/v4/inventory"
|
"github.com/cloudreve/Cloudreve/v4/inventory"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/auth"
|
"github.com/cloudreve/Cloudreve/v4/pkg/auth"
|
||||||
|
|
@ -11,7 +13,6 @@ import (
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||||
"github.com/gin-contrib/static"
|
"github.com/gin-contrib/static"
|
||||||
"io/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Option 发送请求的额外设置
|
// Option 发送请求的额外设置
|
||||||
|
|
@ -67,12 +68,6 @@ func WithProFlag(c bool) Option {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithLicenseKey(c string) Option {
|
|
||||||
return optionFunc(func(o *dependency) {
|
|
||||||
o.licenseKey = c
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRawEntClient Set the default raw ent client.
|
// WithRawEntClient Set the default raw ent client.
|
||||||
func WithRawEntClient(c *ent.Client) Option {
|
func WithRawEntClient(c *ent.Client) Option {
|
||||||
return optionFunc(func(o *dependency) {
|
return optionFunc(func(o *dependency) {
|
||||||
|
|
|
||||||
2
assets
2
assets
|
|
@ -1 +1 @@
|
||||||
Subproject commit 1c9dd8d9adbb6842b404ecd908a625ce519b754f
|
Subproject commit 8b91fca9291b58edd100949954039fc71524f97d
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/ent/entity"
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/encrypt"
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
outputToFile string
|
||||||
|
newMasterKeyFile string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(masterKeyCmd)
|
||||||
|
masterKeyCmd.AddCommand(masterKeyGenerateCmd)
|
||||||
|
masterKeyCmd.AddCommand(masterKeyGetCmd)
|
||||||
|
masterKeyCmd.AddCommand(masterKeyRotateCmd)
|
||||||
|
|
||||||
|
masterKeyGenerateCmd.Flags().StringVarP(&outputToFile, "output", "o", "", "Output master key to file instead of stdout")
|
||||||
|
masterKeyRotateCmd.Flags().StringVarP(&newMasterKeyFile, "new-key", "n", "", "Path to file containing the new master key (base64 encoded).")
|
||||||
|
}
|
||||||
|
|
||||||
|
var masterKeyCmd = &cobra.Command{
|
||||||
|
Use: "master-key",
|
||||||
|
Short: "Master encryption key management",
|
||||||
|
Long: "Manage master encryption keys for file encryption. Use subcommands to generate, get, or rotate keys.",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
_ = cmd.Help()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var masterKeyGenerateCmd = &cobra.Command{
|
||||||
|
Use: "generate",
|
||||||
|
Short: "Generate a new master encryption key",
|
||||||
|
Long: "Generate a new random 32-byte (256-bit) master encryption key and output it in base64 format.",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// Generate 32-byte random key
|
||||||
|
key := make([]byte, 32)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: Failed to generate random key: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode to base64
|
||||||
|
encodedKey := base64.StdEncoding.EncodeToString(key)
|
||||||
|
|
||||||
|
if outputToFile != "" {
|
||||||
|
// Write to file
|
||||||
|
if err := os.WriteFile(outputToFile, []byte(encodedKey), 0600); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: Failed to write key to file: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("Master key generated and saved to: %s\n", outputToFile)
|
||||||
|
} else {
|
||||||
|
// Output to stdout
|
||||||
|
fmt.Println(encodedKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var masterKeyGetCmd = &cobra.Command{
|
||||||
|
Use: "get",
|
||||||
|
Short: "Get the current master encryption key",
|
||||||
|
Long: "Retrieve and display the current master encryption key from the configured vault (setting, env, or file).",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
ctx := context.Background()
|
||||||
|
dep := dependency.NewDependency(
|
||||||
|
dependency.WithConfigPath(confPath),
|
||||||
|
)
|
||||||
|
logger := dep.Logger()
|
||||||
|
|
||||||
|
// Get the master key vault
|
||||||
|
vault := encrypt.NewMasterEncryptKeyVault(ctx, dep.SettingProvider())
|
||||||
|
|
||||||
|
// Retrieve the master key
|
||||||
|
key, err := vault.GetMasterKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to get master key: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode to base64 and display
|
||||||
|
encodedKey := base64.StdEncoding.EncodeToString(key)
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println(encodedKey)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var masterKeyRotateCmd = &cobra.Command{
|
||||||
|
Use: "rotate",
|
||||||
|
Short: "Rotate the master encryption key",
|
||||||
|
Long: `Rotate the master encryption key by re-encrypting all encrypted file keys with a new master key.
|
||||||
|
This operation:
|
||||||
|
1. Retrieves the current master key
|
||||||
|
2. Loads a new master key from file
|
||||||
|
3. Re-encrypts all file encryption keys with the new master key
|
||||||
|
4. Updates the master key in the settings database
|
||||||
|
|
||||||
|
Warning: This is a critical operation. Make sure to backup your database before proceeding.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
ctx := context.Background()
|
||||||
|
dep := dependency.NewDependency(
|
||||||
|
dependency.WithConfigPath(confPath),
|
||||||
|
)
|
||||||
|
logger := dep.Logger()
|
||||||
|
|
||||||
|
logger.Info("Starting master key rotation...")
|
||||||
|
|
||||||
|
// Get the old master key
|
||||||
|
vault := encrypt.NewMasterEncryptKeyVault(ctx, dep.SettingProvider())
|
||||||
|
oldMasterKey, err := vault.GetMasterKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to get current master key: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
logger.Info("Retrieved current master key")
|
||||||
|
|
||||||
|
// Get or generate the new master key
|
||||||
|
var newMasterKey []byte
|
||||||
|
// Load from file
|
||||||
|
keyData, err := os.ReadFile(newMasterKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to read new master key file: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
newMasterKey, err = base64.StdEncoding.DecodeString(string(keyData))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to decode new master key: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if len(newMasterKey) != 32 {
|
||||||
|
logger.Error("Invalid new master key: must be 32 bytes (256 bits), got %d bytes", len(newMasterKey))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
logger.Info("Loaded new master key from file: %s", newMasterKeyFile)
|
||||||
|
|
||||||
|
// Query all entities with encryption metadata
|
||||||
|
db := dep.DBClient()
|
||||||
|
entities, err := db.Entity.Query().
|
||||||
|
Where(entity.Not(entity.PropsIsNil())).
|
||||||
|
All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to query entities: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Found %d entities to check for encryption", len(entities))
|
||||||
|
|
||||||
|
// Re-encrypt each entity's encryption key
|
||||||
|
encryptedCount := 0
|
||||||
|
for _, ent := range entities {
|
||||||
|
if ent.Props == nil || ent.Props.EncryptMetadata == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
encMeta := ent.Props.EncryptMetadata
|
||||||
|
|
||||||
|
// Decrypt the file key with old master key
|
||||||
|
decryptedFileKey, err := encrypt.DecryptWithMasterKey(oldMasterKey, encMeta.Key)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to decrypt key for entity %d: %s", ent.ID, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-encrypt the file key with new master key
|
||||||
|
newEncryptedKey, err := encrypt.EncryptWithMasterKey(newMasterKey, decryptedFileKey)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to re-encrypt key for entity %d: %s", ent.ID, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the entity
|
||||||
|
newProps := *ent.Props
|
||||||
|
newProps.EncryptMetadata = &types.EncryptMetadata{
|
||||||
|
Algorithm: encMeta.Algorithm,
|
||||||
|
Key: newEncryptedKey,
|
||||||
|
KeyPlainText: nil, // Don't store plaintext
|
||||||
|
IV: encMeta.IV,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Entity.UpdateOne(ent).
|
||||||
|
SetProps(&newProps).
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to update entity %d: %s", ent.ID, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Re-encrypted %d file keys", encryptedCount)
|
||||||
|
|
||||||
|
// Update the master key in settings
|
||||||
|
keyStore := dep.SettingProvider().MasterEncryptKeyVault(ctx)
|
||||||
|
if keyStore == setting.MasterEncryptKeyVaultTypeSetting {
|
||||||
|
encodedNewKey := base64.StdEncoding.EncodeToString(newMasterKey)
|
||||||
|
err = dep.SettingClient().Set(ctx, map[string]string{
|
||||||
|
"encrypt_master_key": encodedNewKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to update master key in settings: %s", err)
|
||||||
|
logger.Error("WARNING: File keys have been re-encrypted but master key update failed!")
|
||||||
|
logger.Error("Please manually update the encrypt_master_key setting.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Info("Current master key is stored in %q", keyStore)
|
||||||
|
if keyStore == setting.MasterEncryptKeyVaultTypeEnv {
|
||||||
|
logger.Info("Please update the new master encryption key in your \"CR_ENCRYPT_MASTER_KEY\" environment variable.")
|
||||||
|
} else if keyStore == setting.MasterEncryptKeyVaultTypeFile {
|
||||||
|
logger.Info("Please update the new master encryption key in your key file: %q", dep.SettingProvider().MasterEncryptKeyFile(ctx))
|
||||||
|
}
|
||||||
|
logger.Info("Last step: Please manually update the new master encryption key in your ENV or key file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Master key rotation completed successfully")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -2,14 +2,16 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
confPath string
|
confPath string
|
||||||
|
licenseKey string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,6 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
licenseKey string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(serverCmd)
|
rootCmd.AddCommand(serverCmd)
|
||||||
serverCmd.PersistentFlags().StringVarP(&licenseKey, "license-key", "l", "", "License key of your Cloudreve Pro")
|
serverCmd.PersistentFlags().StringVarP(&licenseKey, "license-key", "l", "", "License key of your Cloudreve Pro")
|
||||||
|
|
@ -29,7 +25,6 @@ var serverCmd = &cobra.Command{
|
||||||
dependency.WithConfigPath(confPath),
|
dependency.WithConfigPath(confPath),
|
||||||
dependency.WithProFlag(constants.IsProBool),
|
dependency.WithProFlag(constants.IsProBool),
|
||||||
dependency.WithRequiredDbVersion(constants.BackendVersion),
|
dependency.WithRequiredDbVersion(constants.BackendVersion),
|
||||||
dependency.WithLicenseKey(licenseKey),
|
|
||||||
)
|
)
|
||||||
server := application.NewServer(dep)
|
server := application.NewServer(dep)
|
||||||
logger := dep.Logger()
|
logger := dep.Logger()
|
||||||
|
|
|
||||||
|
|
@ -665,6 +665,14 @@ var DefaultSettings = map[string]string{
|
||||||
"headless_bottom_html": "",
|
"headless_bottom_html": "",
|
||||||
"sidebar_bottom_html": "",
|
"sidebar_bottom_html": "",
|
||||||
"encrypt_master_key": "",
|
"encrypt_master_key": "",
|
||||||
|
"encrypt_master_key_vault": "setting",
|
||||||
|
"encrypt_master_key_file": "",
|
||||||
|
"show_encryption_status": "1",
|
||||||
|
}
|
||||||
|
|
||||||
|
var RedactedSettings = map[string]struct{}{
|
||||||
|
"encrypt_master_key": {},
|
||||||
|
"secret_key": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
||||||
|
|
@ -161,13 +161,13 @@ type (
|
||||||
EncryptMetadata *EncryptMetadata `json:"encrypt_metadata,omitempty"`
|
EncryptMetadata *EncryptMetadata `json:"encrypt_metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
Algorithm string
|
Cipher string
|
||||||
|
|
||||||
EncryptMetadata struct {
|
EncryptMetadata struct {
|
||||||
Algorithm Algorithm `json:"algorithm"`
|
Algorithm Cipher `json:"algorithm"`
|
||||||
Key []byte `json:"key"`
|
Key []byte `json:"key"`
|
||||||
KeyPlainText []byte `json:"key_plain_text,omitempty"`
|
KeyPlainText []byte `json:"key_plain_text,omitempty"`
|
||||||
IV []byte `json:"iv"`
|
IV []byte `json:"iv"`
|
||||||
}
|
}
|
||||||
|
|
||||||
DavAccountProps struct {
|
DavAccountProps struct {
|
||||||
|
|
@ -361,5 +361,5 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AlgorithmAES256CTR Algorithm = "aes-256-ctr"
|
CipherAES256CTR Cipher = "aes-256-ctr"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
// Using the factory pattern:
|
// Using the factory pattern:
|
||||||
//
|
//
|
||||||
// factory := NewDecrypterFactory(masterKeyVault)
|
// factory := NewDecrypterFactory(masterKeyVault)
|
||||||
// decrypter, err := factory(types.AlgorithmAES256CTR)
|
// decrypter, err := factory(types.CipherAES256CTR)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
|
|
@ -131,7 +131,7 @@ func (e *AES256CTR) GenerateMetadata(ctx context.Context) (*types.EncryptMetadat
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.EncryptMetadata{
|
return &types.EncryptMetadata{
|
||||||
Algorithm: types.AlgorithmAES256CTR,
|
Algorithm: types.CipherAES256CTR,
|
||||||
Key: encryptedKey,
|
Key: encryptedKey,
|
||||||
KeyPlainText: key,
|
KeyPlainText: key,
|
||||||
IV: iv,
|
IV: iv,
|
||||||
|
|
@ -144,7 +144,7 @@ func (e *AES256CTR) LoadMetadata(ctx context.Context, encryptedMetadata *types.E
|
||||||
return fmt.Errorf("encryption metadata is nil")
|
return fmt.Errorf("encryption metadata is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if encryptedMetadata.Algorithm != types.AlgorithmAES256CTR {
|
if encryptedMetadata.Algorithm != types.CipherAES256CTR {
|
||||||
return fmt.Errorf("unsupported algorithm: %s", encryptedMetadata.Algorithm)
|
return fmt.Errorf("unsupported algorithm: %s", encryptedMetadata.Algorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,13 @@ type (
|
||||||
GenerateMetadata(ctx context.Context) (*types.EncryptMetadata, error)
|
GenerateMetadata(ctx context.Context) (*types.EncryptMetadata, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
CryptorFactory func(algorithm types.Algorithm) (Cryptor, error)
|
CryptorFactory func(algorithm types.Cipher) (Cryptor, error)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCryptorFactory(masterKeyVault MasterEncryptKeyVault) CryptorFactory {
|
func NewCryptorFactory(masterKeyVault MasterEncryptKeyVault) CryptorFactory {
|
||||||
return func(algorithm types.Algorithm) (Cryptor, error) {
|
return func(algorithm types.Cipher) (Cryptor, error) {
|
||||||
switch algorithm {
|
switch algorithm {
|
||||||
case types.AlgorithmAES256CTR:
|
case types.CipherAES256CTR:
|
||||||
return NewAES256CTR(masterKeyVault), nil
|
return NewAES256CTR(masterKeyVault), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown algorithm: %s", algorithm)
|
return nil, fmt.Errorf("unknown algorithm: %s", algorithm)
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,33 @@ package encrypt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnvMasterEncryptKey = "CR_ENCRYPT_MASTER_KEY"
|
||||||
|
)
|
||||||
|
|
||||||
// MasterEncryptKeyVault is a vault for the master encrypt key.
|
// MasterEncryptKeyVault is a vault for the master encrypt key.
|
||||||
type MasterEncryptKeyVault interface {
|
type MasterEncryptKeyVault interface {
|
||||||
GetMasterKey(ctx context.Context) ([]byte, error)
|
GetMasterKey(ctx context.Context) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMasterEncryptKeyVault(setting setting.Provider) MasterEncryptKeyVault {
|
func NewMasterEncryptKeyVault(ctx context.Context, settings setting.Provider) MasterEncryptKeyVault {
|
||||||
return &settingMasterEncryptKeyVault{setting: setting}
|
vaultType := settings.MasterEncryptKeyVault(ctx)
|
||||||
|
switch vaultType {
|
||||||
|
case setting.MasterEncryptKeyVaultTypeEnv:
|
||||||
|
return NewEnvMasterEncryptKeyVault()
|
||||||
|
case setting.MasterEncryptKeyVaultTypeFile:
|
||||||
|
return NewFileMasterEncryptKeyVault(settings.MasterEncryptKeyFile(ctx))
|
||||||
|
default:
|
||||||
|
return NewSettingMasterEncryptKeyVault(settings)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// settingMasterEncryptKeyVault is a vault for the master encrypt key that gets the key from the setting KV.
|
// settingMasterEncryptKeyVault is a vault for the master encrypt key that gets the key from the setting KV.
|
||||||
|
|
@ -21,6 +36,10 @@ type settingMasterEncryptKeyVault struct {
|
||||||
setting setting.Provider
|
setting setting.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewSettingMasterEncryptKeyVault(setting setting.Provider) MasterEncryptKeyVault {
|
||||||
|
return &settingMasterEncryptKeyVault{setting: setting}
|
||||||
|
}
|
||||||
|
|
||||||
func (v *settingMasterEncryptKeyVault) GetMasterKey(ctx context.Context) ([]byte, error) {
|
func (v *settingMasterEncryptKeyVault) GetMasterKey(ctx context.Context) ([]byte, error) {
|
||||||
key := v.setting.MasterEncryptKey(ctx)
|
key := v.setting.MasterEncryptKey(ctx)
|
||||||
if key == nil {
|
if key == nil {
|
||||||
|
|
@ -28,3 +47,59 @@ func (v *settingMasterEncryptKeyVault) GetMasterKey(ctx context.Context) ([]byte
|
||||||
}
|
}
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewEnvMasterEncryptKeyVault() MasterEncryptKeyVault {
|
||||||
|
return &envMasterEncryptKeyVault{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type envMasterEncryptKeyVault struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var envMasterKeyCache = []byte{}
|
||||||
|
|
||||||
|
func (v *envMasterEncryptKeyVault) GetMasterKey(ctx context.Context) ([]byte, error) {
|
||||||
|
if len(envMasterKeyCache) > 0 {
|
||||||
|
return envMasterKeyCache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key := os.Getenv(EnvMasterEncryptKey)
|
||||||
|
if key == "" {
|
||||||
|
return nil, errors.New("master encrypt key is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedKey, err := base64.StdEncoding.DecodeString(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode master encrypt key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
envMasterKeyCache = decodedKey
|
||||||
|
return decodedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileMasterEncryptKeyVault(path string) MasterEncryptKeyVault {
|
||||||
|
return &fileMasterEncryptKeyVault{path: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileMasterKeyCache = []byte{}
|
||||||
|
|
||||||
|
type fileMasterEncryptKeyVault struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *fileMasterEncryptKeyVault) GetMasterKey(ctx context.Context) ([]byte, error) {
|
||||||
|
if len(fileMasterKeyCache) > 0 {
|
||||||
|
return fileMasterKeyCache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := os.ReadFile(v.path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid master encrypt key file")
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedKey, err := base64.StdEncoding.DecodeString(string(key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid master encrypt key")
|
||||||
|
}
|
||||||
|
fileMasterKeyCache = decodedKey
|
||||||
|
return fileMasterKeyCache, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -652,8 +652,8 @@ func (f *DBFS) createFile(ctx context.Context, parent *File, name string, fileTy
|
||||||
|
|
||||||
func (f *DBFS) generateEncryptMetadata(ctx context.Context, uploadRequest *fs.UploadRequest, policy *ent.StoragePolicy) (*types.EncryptMetadata, error) {
|
func (f *DBFS) generateEncryptMetadata(ctx context.Context, uploadRequest *fs.UploadRequest, policy *ent.StoragePolicy) (*types.EncryptMetadata, error) {
|
||||||
relayEnabled := policy.Settings != nil && policy.Settings.Relay
|
relayEnabled := policy.Settings != nil && policy.Settings.Relay
|
||||||
if (len(uploadRequest.Props.EncryptionSupported) > 0 && uploadRequest.Props.EncryptionSupported[0] == types.AlgorithmAES256CTR) || relayEnabled {
|
if (len(uploadRequest.Props.EncryptionSupported) > 0 && uploadRequest.Props.EncryptionSupported[0] == types.CipherAES256CTR) || relayEnabled {
|
||||||
encryptor, err := f.encryptorFactory(types.AlgorithmAES256CTR)
|
encryptor, err := f.encryptorFactory(types.CipherAES256CTR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get encryptor: %w", err)
|
return nil, fmt.Errorf("failed to get encryptor: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,7 @@ type (
|
||||||
// with a default version entity. This will be set in update request for existing files.
|
// with a default version entity. This will be set in update request for existing files.
|
||||||
EntityType *types.EntityType
|
EntityType *types.EntityType
|
||||||
ExpireAt time.Time
|
ExpireAt time.Time
|
||||||
EncryptionSupported []types.Algorithm
|
EncryptionSupported []types.Cipher
|
||||||
ClientSideEncrypted bool // Whether the file stream is already encrypted by client side.
|
ClientSideEncrypted bool // Whether the file stream is already encrypted by client side.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ func (m *manager) GetDirectLink(ctx context.Context, urls ...*fs.URI) ([]DirectL
|
||||||
}
|
}
|
||||||
|
|
||||||
source := entitysource.NewEntitySource(target, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(),
|
source := entitysource.NewEntitySource(target, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(),
|
||||||
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory())
|
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(ctx))
|
||||||
sourceUrl, err := source.Url(ctx,
|
sourceUrl, err := source.Url(ctx,
|
||||||
entitysource.WithSpeedLimit(int64(m.user.Edges.Group.SpeedLimit)),
|
entitysource.WithSpeedLimit(int64(m.user.Edges.Group.SpeedLimit)),
|
||||||
entitysource.WithDisplayName(file.Name()),
|
entitysource.WithDisplayName(file.Name()),
|
||||||
|
|
@ -182,7 +182,7 @@ func (m *manager) GetUrlForRedirectedDirectLink(ctx context.Context, dl *ent.Dir
|
||||||
}
|
}
|
||||||
|
|
||||||
source := entitysource.NewEntitySource(primaryEntity, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(),
|
source := entitysource.NewEntitySource(primaryEntity, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(),
|
||||||
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory())
|
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(ctx))
|
||||||
downloadUrl, err := source.Url(ctx,
|
downloadUrl, err := source.Url(ctx,
|
||||||
entitysource.WithExpire(o.Expire),
|
entitysource.WithExpire(o.Expire),
|
||||||
entitysource.WithDownload(o.IsDownload),
|
entitysource.WithDownload(o.IsDownload),
|
||||||
|
|
@ -282,7 +282,7 @@ func (m *manager) GetEntityUrls(ctx context.Context, args []GetEntityUrlArgs, op
|
||||||
|
|
||||||
// Cache miss, Generate new url
|
// Cache miss, Generate new url
|
||||||
source := entitysource.NewEntitySource(target, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(),
|
source := entitysource.NewEntitySource(target, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(),
|
||||||
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory())
|
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(ctx))
|
||||||
downloadUrl, err := source.Url(ctx,
|
downloadUrl, err := source.Url(ctx,
|
||||||
entitysource.WithExpire(o.Expire),
|
entitysource.WithExpire(o.Expire),
|
||||||
entitysource.WithDownload(o.IsDownload),
|
entitysource.WithDownload(o.IsDownload),
|
||||||
|
|
@ -349,7 +349,7 @@ func (m *manager) GetEntitySource(ctx context.Context, entityID int, opts ...fs.
|
||||||
}
|
}
|
||||||
|
|
||||||
return entitysource.NewEntitySource(entity, handler, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(), m.l,
|
return entitysource.NewEntitySource(entity, handler, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(), m.l,
|
||||||
m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(), entitysource.WithContext(ctx), entitysource.WithThumb(o.IsThumb)), nil
|
m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(ctx), entitysource.WithContext(ctx), entitysource.WithThumb(o.IsThumb)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *manager) SetCurrentVersion(ctx context.Context, path *fs.URI, version int) error {
|
func (l *manager) SetCurrentVersion(ctx context.Context, path *fs.URI, version int) error {
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ func NewFileManager(dep dependency.Dep, u *ent.User) FileManager {
|
||||||
settings: dep.SettingProvider(),
|
settings: dep.SettingProvider(),
|
||||||
fs: dbfs.NewDatabaseFS(u, dep.FileClient(), dep.ShareClient(), dep.Logger(), dep.LockSystem(),
|
fs: dbfs.NewDatabaseFS(u, dep.FileClient(), dep.ShareClient(), dep.Logger(), dep.LockSystem(),
|
||||||
dep.SettingProvider(), dep.StoragePolicyClient(), dep.HashIDEncoder(), dep.UserClient(), dep.KV(), dep.NavigatorStateKV(),
|
dep.SettingProvider(), dep.StoragePolicyClient(), dep.HashIDEncoder(), dep.UserClient(), dep.KV(), dep.NavigatorStateKV(),
|
||||||
dep.DirectLinkClient(), dep.EncryptorFactory()),
|
dep.DirectLinkClient(), dep.EncryptorFactory(context.TODO())),
|
||||||
kv: dep.KV(),
|
kv: dep.KV(),
|
||||||
config: config,
|
config: config,
|
||||||
auth: dep.GeneralAuth(),
|
auth: dep.GeneralAuth(),
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,8 @@ func (m *manager) Thumbnail(ctx context.Context, uri *fs.URI) (entitysource.Enti
|
||||||
capabilities := handler.Capabilities()
|
capabilities := handler.Capabilities()
|
||||||
// Check if file extension and size is supported by native policy generator.
|
// Check if file extension and size is supported by native policy generator.
|
||||||
if capabilities.ThumbSupportAllExts || util.IsInExtensionList(capabilities.ThumbSupportedExts, file.DisplayName()) &&
|
if capabilities.ThumbSupportAllExts || util.IsInExtensionList(capabilities.ThumbSupportedExts, file.DisplayName()) &&
|
||||||
(capabilities.ThumbMaxSize == 0 || latest.Size() <= capabilities.ThumbMaxSize) {
|
(capabilities.ThumbMaxSize == 0 || latest.Size() <= capabilities.ThumbMaxSize) &&
|
||||||
|
!latest.Encrypted() {
|
||||||
thumbSource, err := m.GetEntitySource(ctx, 0, fs.WithEntity(latest), fs.WithUseThumb(true))
|
thumbSource, err := m.GetEntitySource(ctx, 0, fs.WithEntity(latest), fs.WithUseThumb(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get latest entity source: %w", err)
|
return nil, fmt.Errorf("failed to get latest entity source: %w", err)
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ func (m *manager) Upload(ctx context.Context, req *fs.UploadRequest, policy *ent
|
||||||
}
|
}
|
||||||
|
|
||||||
if session != nil && session.EncryptMetadata != nil && !req.Props.ClientSideEncrypted {
|
if session != nil && session.EncryptMetadata != nil && !req.Props.ClientSideEncrypted {
|
||||||
cryptor, err := m.dep.EncryptorFactory()(session.EncryptMetadata.Algorithm)
|
cryptor, err := m.dep.EncryptorFactory(ctx)(session.EncryptMetadata.Algorithm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create cryptor: %w", err)
|
return fmt.Errorf("failed to create cryptor: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -331,7 +331,7 @@ func (m *manager) Update(ctx context.Context, req *fs.UploadRequest, opts ...fs.
|
||||||
|
|
||||||
req.Props.UploadSessionID = uuid.Must(uuid.NewV4()).String()
|
req.Props.UploadSessionID = uuid.Must(uuid.NewV4()).String()
|
||||||
// Sever side supported encryption algorithms
|
// Sever side supported encryption algorithms
|
||||||
req.Props.EncryptionSupported = []types.Algorithm{types.AlgorithmAES256CTR}
|
req.Props.EncryptionSupported = []types.Cipher{types.CipherAES256CTR}
|
||||||
|
|
||||||
if m.stateless {
|
if m.stateless {
|
||||||
return m.updateStateless(ctx, req, o)
|
return m.updateStateless(ctx, req, o)
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ func (m *CreateArchiveTask) listEntitiesAndSendToSlave(ctx context.Context, dep
|
||||||
user := inventory.UserFromContext(ctx)
|
user := inventory.UserFromContext(ctx)
|
||||||
fm := manager.NewFileManager(dep, user)
|
fm := manager.NewFileManager(dep, user)
|
||||||
storagePolicyClient := dep.StoragePolicyClient()
|
storagePolicyClient := dep.StoragePolicyClient()
|
||||||
masterKey, _ := dep.MasterEncryptKeyVault().GetMasterKey(ctx)
|
masterKey, _ := dep.MasterEncryptKeyVault(ctx).GetMasterKey(ctx)
|
||||||
|
|
||||||
failed, err := fm.CreateArchive(ctx, uris, io.Discard,
|
failed, err := fm.CreateArchive(ctx, uris, io.Discard,
|
||||||
fs.WithDryRun(func(name string, e fs.Entity) {
|
fs.WithDryRun(func(name string, e fs.Entity) {
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ func (m *ExtractArchiveTask) createSlaveExtractTask(ctx context.Context, dep dep
|
||||||
return task.StatusError, fmt.Errorf("failed to get policy: %w", err)
|
return task.StatusError, fmt.Errorf("failed to get policy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
masterKey, _ := dep.MasterEncryptKeyVault().GetMasterKey(ctx)
|
masterKey, _ := dep.MasterEncryptKeyVault(ctx).GetMasterKey(ctx)
|
||||||
entityModel, err := decryptEntityKeyIfNeeded(masterKey, archiveFile.PrimaryEntity().Model())
|
entityModel, err := decryptEntityKeyIfNeeded(masterKey, archiveFile.PrimaryEntity().Model())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return task.StatusError, fmt.Errorf("failed to decrypt entity key for archive file %q: %s", archiveFile.DisplayName(), err)
|
return task.StatusError, fmt.Errorf("failed to decrypt entity key for archive file %q: %s", archiveFile.DisplayName(), err)
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,12 @@ type (
|
||||||
FFMpegExtraArgs(ctx context.Context) string
|
FFMpegExtraArgs(ctx context.Context) string
|
||||||
// MasterEncryptKey returns the master encrypt key.
|
// MasterEncryptKey returns the master encrypt key.
|
||||||
MasterEncryptKey(ctx context.Context) []byte
|
MasterEncryptKey(ctx context.Context) []byte
|
||||||
|
// MasterEncryptKeyVault returns the master encrypt key vault type.
|
||||||
|
MasterEncryptKeyVault(ctx context.Context) MasterEncryptKeyVaultType
|
||||||
|
// MasterEncryptKeyFile returns the master encrypt key file.
|
||||||
|
MasterEncryptKeyFile(ctx context.Context) string
|
||||||
|
// ShowEncryptionStatus returns true if encryption status is shown.
|
||||||
|
ShowEncryptionStatus(ctx context.Context) bool
|
||||||
}
|
}
|
||||||
UseFirstSiteUrlCtxKey = struct{}
|
UseFirstSiteUrlCtxKey = struct{}
|
||||||
)
|
)
|
||||||
|
|
@ -237,6 +243,18 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s *settingProvider) ShowEncryptionStatus(ctx context.Context) bool {
|
||||||
|
return s.getBoolean(ctx, "show_encryption_status", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingProvider) MasterEncryptKeyFile(ctx context.Context) string {
|
||||||
|
return s.getString(ctx, "encrypt_master_key_file", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingProvider) MasterEncryptKeyVault(ctx context.Context) MasterEncryptKeyVaultType {
|
||||||
|
return MasterEncryptKeyVaultType(s.getString(ctx, "encrypt_master_key_vault", "setting"))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *settingProvider) MasterEncryptKey(ctx context.Context) []byte {
|
func (s *settingProvider) MasterEncryptKey(ctx context.Context) []byte {
|
||||||
encoded := s.getString(ctx, "encrypt_master_key", "")
|
encoded := s.getString(ctx, "encrypt_master_key", "")
|
||||||
key, err := base64.StdEncoding.DecodeString(encoded)
|
key, err := base64.StdEncoding.DecodeString(encoded)
|
||||||
|
|
|
||||||
|
|
@ -223,3 +223,11 @@ type CustomHTML struct {
|
||||||
HeadlessBody string `json:"headless_bottom,omitempty"`
|
HeadlessBody string `json:"headless_bottom,omitempty"`
|
||||||
SidebarBottom string `json:"sidebar_bottom,omitempty"`
|
SidebarBottom string `json:"sidebar_bottom,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MasterEncryptKeyVaultType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
MasterEncryptKeyVaultTypeSetting = MasterEncryptKeyVaultType("setting")
|
||||||
|
MasterEncryptKeyVaultTypeEnv = MasterEncryptKeyVaultType("env")
|
||||||
|
MasterEncryptKeyVaultTypeFile = MasterEncryptKeyVaultType("file")
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,7 @@ func (s *SingleFileService) Url(c *gin.Context) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
es := entitysource.NewEntitySource(fs.NewEntity(primaryEntity), driver, policy, dep.GeneralAuth(),
|
es := entitysource.NewEntitySource(fs.NewEntity(primaryEntity), driver, policy, dep.GeneralAuth(),
|
||||||
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(ctx), dep.EncryptorFactory())
|
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(ctx), dep.EncryptorFactory(ctx))
|
||||||
|
|
||||||
expire := time.Now().Add(time.Hour * 1)
|
expire := time.Now().Add(time.Hour * 1)
|
||||||
url, err := es.Url(ctx, entitysource.WithExpire(&expire), entitysource.WithDisplayName(file.Name))
|
url, err := es.Url(ctx, entitysource.WithExpire(&expire), entitysource.WithDisplayName(file.Name))
|
||||||
|
|
@ -547,7 +547,7 @@ func (s *SingleEntityService) Url(c *gin.Context) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
es := entitysource.NewEntitySource(fs.NewEntity(entity), driver, policy, dep.GeneralAuth(),
|
es := entitysource.NewEntitySource(fs.NewEntity(entity), driver, policy, dep.GeneralAuth(),
|
||||||
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(c), dep.EncryptorFactory())
|
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(c), dep.EncryptorFactory(c))
|
||||||
|
|
||||||
expire := time.Now().Add(time.Hour * 1)
|
expire := time.Now().Add(time.Hour * 1)
|
||||||
url, err := es.Url(c, entitysource.WithDownload(true), entitysource.WithExpire(&expire), entitysource.WithDisplayName(path.Base(entity.Source)))
|
url, err := es.Url(c, entitysource.WithDownload(true), entitysource.WithExpire(&expire), entitysource.WithDisplayName(path.Base(entity.Source)))
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,8 @@ type (
|
||||||
func (s *GetSettingService) GetSetting(c *gin.Context) (map[string]string, error) {
|
func (s *GetSettingService) GetSetting(c *gin.Context) (map[string]string, error) {
|
||||||
dep := dependency.FromContext(c)
|
dep := dependency.FromContext(c)
|
||||||
res, err := dep.SettingClient().Gets(c, lo.Filter(s.Keys, func(item string, index int) bool {
|
res, err := dep.SettingClient().Gets(c, lo.Filter(s.Keys, func(item string, index int) bool {
|
||||||
return item != "secret_key"
|
_, ok := inventory.RedactedSettings[strings.ToLower(item)]
|
||||||
|
return !ok
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get settings", err)
|
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get settings", err)
|
||||||
|
|
|
||||||
|
|
@ -43,16 +43,17 @@ type SiteConfig struct {
|
||||||
PrivacyPolicyUrl string `json:"privacy_policy_url,omitempty"`
|
PrivacyPolicyUrl string `json:"privacy_policy_url,omitempty"`
|
||||||
|
|
||||||
// Explorer section
|
// Explorer section
|
||||||
Icons string `json:"icons,omitempty"`
|
Icons string `json:"icons,omitempty"`
|
||||||
EmojiPreset string `json:"emoji_preset,omitempty"`
|
EmojiPreset string `json:"emoji_preset,omitempty"`
|
||||||
MapProvider setting.MapProvider `json:"map_provider,omitempty"`
|
MapProvider setting.MapProvider `json:"map_provider,omitempty"`
|
||||||
GoogleMapTileType setting.MapGoogleTileType `json:"google_map_tile_type,omitempty"`
|
GoogleMapTileType setting.MapGoogleTileType `json:"google_map_tile_type,omitempty"`
|
||||||
MapboxAK string `json:"mapbox_ak,omitempty"`
|
MapboxAK string `json:"mapbox_ak,omitempty"`
|
||||||
FileViewers []types.ViewerGroup `json:"file_viewers,omitempty"`
|
FileViewers []types.ViewerGroup `json:"file_viewers,omitempty"`
|
||||||
MaxBatchSize int `json:"max_batch_size,omitempty"`
|
MaxBatchSize int `json:"max_batch_size,omitempty"`
|
||||||
ThumbnailWidth int `json:"thumbnail_width,omitempty"`
|
ThumbnailWidth int `json:"thumbnail_width,omitempty"`
|
||||||
ThumbnailHeight int `json:"thumbnail_height,omitempty"`
|
ThumbnailHeight int `json:"thumbnail_height,omitempty"`
|
||||||
CustomProps []types.CustomProps `json:"custom_props,omitempty"`
|
CustomProps []types.CustomProps `json:"custom_props,omitempty"`
|
||||||
|
ShowEncryptionStatus bool `json:"show_encryption_status,omitempty"`
|
||||||
|
|
||||||
// Thumbnail section
|
// Thumbnail section
|
||||||
ThumbExts []string `json:"thumb_exts,omitempty"`
|
ThumbExts []string `json:"thumb_exts,omitempty"`
|
||||||
|
|
@ -100,6 +101,7 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
|
||||||
fileViewers := settings.FileViewers(c)
|
fileViewers := settings.FileViewers(c)
|
||||||
customProps := settings.CustomProps(c)
|
customProps := settings.CustomProps(c)
|
||||||
maxBatchSize := settings.MaxBatchedFile(c)
|
maxBatchSize := settings.MaxBatchedFile(c)
|
||||||
|
showEncryptionStatus := settings.ShowEncryptionStatus(c)
|
||||||
w, h := settings.ThumbSize(c)
|
w, h := settings.ThumbSize(c)
|
||||||
for i := range fileViewers {
|
for i := range fileViewers {
|
||||||
for j := range fileViewers[i].Viewers {
|
for j := range fileViewers[i].Viewers {
|
||||||
|
|
@ -107,15 +109,16 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &SiteConfig{
|
return &SiteConfig{
|
||||||
MaxBatchSize: maxBatchSize,
|
MaxBatchSize: maxBatchSize,
|
||||||
FileViewers: fileViewers,
|
FileViewers: fileViewers,
|
||||||
Icons: explorerSettings.Icons,
|
Icons: explorerSettings.Icons,
|
||||||
MapProvider: mapSettings.Provider,
|
MapProvider: mapSettings.Provider,
|
||||||
GoogleMapTileType: mapSettings.GoogleTileType,
|
GoogleMapTileType: mapSettings.GoogleTileType,
|
||||||
MapboxAK: mapSettings.MapboxAK,
|
MapboxAK: mapSettings.MapboxAK,
|
||||||
ThumbnailWidth: w,
|
ThumbnailWidth: w,
|
||||||
ThumbnailHeight: h,
|
ThumbnailHeight: h,
|
||||||
CustomProps: customProps,
|
CustomProps: customProps,
|
||||||
|
ShowEncryptionStatus: showEncryptionStatus,
|
||||||
}, nil
|
}, nil
|
||||||
case "emojis":
|
case "emojis":
|
||||||
emojis := settings.EmojiPresets(c)
|
emojis := settings.EmojiPresets(c)
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,7 @@ type Entity struct {
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
StoragePolicy *StoragePolicy `json:"storage_policy,omitempty"`
|
StoragePolicy *StoragePolicy `json:"storage_policy,omitempty"`
|
||||||
CreatedBy *user.User `json:"created_by,omitempty"`
|
CreatedBy *user.User `json:"created_by,omitempty"`
|
||||||
|
EncryptedWith types.Cipher `json:"encrypted_with,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Share struct {
|
type Share struct {
|
||||||
|
|
@ -452,6 +453,12 @@ func BuildEntity(extendedInfo *fs.FileExtendedInfo, e fs.Entity, hasher hashid.E
|
||||||
userRedacted := user.BuildUserRedacted(e.CreatedBy(), user.RedactLevelAnonymous, hasher)
|
userRedacted := user.BuildUserRedacted(e.CreatedBy(), user.RedactLevelAnonymous, hasher)
|
||||||
u = &userRedacted
|
u = &userRedacted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encryptedWith := types.Cipher("")
|
||||||
|
if e.Encrypted() {
|
||||||
|
encryptedWith = e.Props().EncryptMetadata.Algorithm
|
||||||
|
}
|
||||||
|
|
||||||
return Entity{
|
return Entity{
|
||||||
ID: hashid.EncodeEntityID(hasher, e.ID()),
|
ID: hashid.EncodeEntityID(hasher, e.ID()),
|
||||||
Type: e.Type(),
|
Type: e.Type(),
|
||||||
|
|
@ -459,6 +466,7 @@ func BuildEntity(extendedInfo *fs.FileExtendedInfo, e fs.Entity, hasher hashid.E
|
||||||
StoragePolicy: BuildStoragePolicy(extendedInfo.EntityStoragePolicies[e.PolicyID()], hasher),
|
StoragePolicy: BuildStoragePolicy(extendedInfo.EntityStoragePolicies[e.PolicyID()], hasher),
|
||||||
Size: e.Size(),
|
Size: e.Size(),
|
||||||
CreatedBy: u,
|
CreatedBy: u,
|
||||||
|
EncryptedWith: encryptedWith,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ type (
|
||||||
PolicyID string `json:"policy_id"`
|
PolicyID string `json:"policy_id"`
|
||||||
Metadata map[string]string `json:"metadata" binding:"max=256"`
|
Metadata map[string]string `json:"metadata" binding:"max=256"`
|
||||||
EntityType string `json:"entity_type" binding:"eq=|eq=live_photo|eq=version"`
|
EntityType string `json:"entity_type" binding:"eq=|eq=live_photo|eq=version"`
|
||||||
EncryptionSupported []types.Algorithm `json:"encryption_supported"`
|
EncryptionSupported []types.Cipher `json:"encryption_supported"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue