[MM-53454] Add export file settings + slash command for public link (#23915)
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -41,6 +41,10 @@ type FileBackend interface {
|
||||
RemoveDirectory(path string) error
|
||||
}
|
||||
|
||||
type FileBackendWithLinkGenerator interface {
|
||||
GeneratePublicLink(path string) (string, time.Duration, error)
|
||||
}
|
||||
|
||||
type FileBackendSettings struct {
|
||||
DriverName string
|
||||
Directory string
|
||||
@@ -56,6 +60,7 @@ type FileBackendSettings struct {
|
||||
AmazonS3Trace bool
|
||||
SkipVerify bool
|
||||
AmazonS3RequestTimeoutMilliseconds int64
|
||||
AmazonS3PresignExpiresSeconds int64
|
||||
}
|
||||
|
||||
func NewFileBackendSettingsFromConfig(fileSettings *model.FileSettings, enableComplianceFeature bool, skipVerify bool) FileBackendSettings {
|
||||
@@ -82,6 +87,31 @@ func NewFileBackendSettingsFromConfig(fileSettings *model.FileSettings, enableCo
|
||||
}
|
||||
}
|
||||
|
||||
func NewExportFileBackendSettingsFromConfig(fileSettings *model.FileSettings, enableComplianceFeature bool, skipVerify bool) FileBackendSettings {
|
||||
if *fileSettings.ExportDriverName == model.ImageDriverLocal {
|
||||
return FileBackendSettings{
|
||||
DriverName: *fileSettings.ExportDriverName,
|
||||
Directory: *fileSettings.ExportDirectory,
|
||||
}
|
||||
}
|
||||
return FileBackendSettings{
|
||||
DriverName: *fileSettings.ExportDriverName,
|
||||
AmazonS3AccessKeyId: *fileSettings.ExportAmazonS3AccessKeyId,
|
||||
AmazonS3SecretAccessKey: *fileSettings.ExportAmazonS3SecretAccessKey,
|
||||
AmazonS3Bucket: *fileSettings.ExportAmazonS3Bucket,
|
||||
AmazonS3PathPrefix: *fileSettings.ExportAmazonS3PathPrefix,
|
||||
AmazonS3Region: *fileSettings.ExportAmazonS3Region,
|
||||
AmazonS3Endpoint: *fileSettings.ExportAmazonS3Endpoint,
|
||||
AmazonS3SSL: fileSettings.ExportAmazonS3SSL == nil || *fileSettings.AmazonS3SSL,
|
||||
AmazonS3SignV2: fileSettings.ExportAmazonS3SignV2 != nil && *fileSettings.AmazonS3SignV2,
|
||||
AmazonS3SSE: fileSettings.ExportAmazonS3SSE != nil && *fileSettings.AmazonS3SSE && enableComplianceFeature,
|
||||
AmazonS3Trace: fileSettings.ExportAmazonS3Trace != nil && *fileSettings.AmazonS3Trace,
|
||||
AmazonS3RequestTimeoutMilliseconds: *fileSettings.ExportAmazonS3RequestTimeoutMilliseconds,
|
||||
AmazonS3PresignExpiresSeconds: *fileSettings.ExportAmazonS3PresignExpiresSeconds,
|
||||
SkipVerify: skipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
func (settings *FileBackendSettings) CheckMandatoryS3Fields() error {
|
||||
if settings.AmazonS3Bucket == "" {
|
||||
return errors.New("missing s3 bucket settings")
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Code generated by mockery v2.23.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make filestore-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// FileBackendWithLinkGenerator is an autogenerated mock type for the FileBackendWithLinkGenerator type
|
||||
type FileBackendWithLinkGenerator struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GeneratePublicLink provides a mock function with given fields: path
|
||||
func (_m *FileBackendWithLinkGenerator) GeneratePublicLink(path string) (string, time.Duration, error) {
|
||||
ret := _m.Called(path)
|
||||
|
||||
var r0 string
|
||||
var r1 time.Duration
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(string) (string, time.Duration, error)); ok {
|
||||
return rf(path)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(path)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) time.Duration); ok {
|
||||
r1 = rf(path)
|
||||
} else {
|
||||
r1 = ret.Get(1).(time.Duration)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(string) error); ok {
|
||||
r2 = rf(path)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewFileBackendWithLinkGenerator interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewFileBackendWithLinkGenerator creates a new instance of FileBackendWithLinkGenerator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewFileBackendWithLinkGenerator(t mockConstructorTestingTNewFileBackendWithLinkGenerator) *FileBackendWithLinkGenerator {
|
||||
mock := &FileBackendWithLinkGenerator{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -26,20 +27,21 @@ import (
|
||||
// S3FileBackend contains all necessary information to communicate with
|
||||
// an AWS S3 compatible API backend.
|
||||
type S3FileBackend struct {
|
||||
endpoint string
|
||||
accessKey string
|
||||
secretKey string
|
||||
secure bool
|
||||
signV2 bool
|
||||
region string
|
||||
bucket string
|
||||
pathPrefix string
|
||||
encrypt bool
|
||||
trace bool
|
||||
client *s3.Client
|
||||
skipVerify bool
|
||||
timeout time.Duration
|
||||
isCloud bool // field to indicate whether this is running under Mattermost cloud or not.
|
||||
endpoint string
|
||||
accessKey string
|
||||
secretKey string
|
||||
secure bool
|
||||
signV2 bool
|
||||
region string
|
||||
bucket string
|
||||
pathPrefix string
|
||||
encrypt bool
|
||||
trace bool
|
||||
client *s3.Client
|
||||
skipVerify bool
|
||||
timeout time.Duration
|
||||
presignExpires time.Duration
|
||||
isCloud bool // field to indicate whether this is running under Mattermost cloud or not.
|
||||
}
|
||||
|
||||
type S3FileBackendAuthError struct {
|
||||
@@ -61,7 +63,8 @@ var (
|
||||
|
||||
var (
|
||||
// Ensure that the ReaderAt interface is implemented.
|
||||
_ io.ReaderAt = (*s3WithCancel)(nil)
|
||||
_ io.ReaderAt = (*s3WithCancel)(nil)
|
||||
_ FileBackendWithLinkGenerator = (*S3FileBackend)(nil)
|
||||
)
|
||||
|
||||
func isFileExtImage(ext string) bool {
|
||||
@@ -89,18 +92,19 @@ func (s *S3FileBackendNoBucketError) Error() string {
|
||||
func NewS3FileBackend(settings FileBackendSettings) (*S3FileBackend, error) {
|
||||
timeout := time.Duration(settings.AmazonS3RequestTimeoutMilliseconds) * time.Millisecond
|
||||
backend := &S3FileBackend{
|
||||
endpoint: settings.AmazonS3Endpoint,
|
||||
accessKey: settings.AmazonS3AccessKeyId,
|
||||
secretKey: settings.AmazonS3SecretAccessKey,
|
||||
secure: settings.AmazonS3SSL,
|
||||
signV2: settings.AmazonS3SignV2,
|
||||
region: settings.AmazonS3Region,
|
||||
bucket: settings.AmazonS3Bucket,
|
||||
pathPrefix: settings.AmazonS3PathPrefix,
|
||||
encrypt: settings.AmazonS3SSE,
|
||||
trace: settings.AmazonS3Trace,
|
||||
skipVerify: settings.SkipVerify,
|
||||
timeout: timeout,
|
||||
endpoint: settings.AmazonS3Endpoint,
|
||||
accessKey: settings.AmazonS3AccessKeyId,
|
||||
secretKey: settings.AmazonS3SecretAccessKey,
|
||||
secure: settings.AmazonS3SSL,
|
||||
signV2: settings.AmazonS3SignV2,
|
||||
region: settings.AmazonS3Region,
|
||||
bucket: settings.AmazonS3Bucket,
|
||||
pathPrefix: settings.AmazonS3PathPrefix,
|
||||
encrypt: settings.AmazonS3SSE,
|
||||
trace: settings.AmazonS3Trace,
|
||||
skipVerify: settings.SkipVerify,
|
||||
timeout: timeout,
|
||||
presignExpires: time.Duration(settings.AmazonS3PresignExpiresSeconds) * time.Second,
|
||||
}
|
||||
isCloud := os.Getenv("MM_CLOUD_FILESTORE_BIFROST") != ""
|
||||
cli, err := backend.s3New(isCloud)
|
||||
@@ -609,6 +613,25 @@ func (b *S3FileBackend) RemoveDirectory(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *S3FileBackend) GeneratePublicLink(path string) (string, time.Duration, error) {
|
||||
path, err := b.prefixedPath(path)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrapf(err, "unable to prefix path %s", path)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
|
||||
reqParams := make(url.Values)
|
||||
reqParams.Set("response-content-disposition", "attachment")
|
||||
|
||||
req, err := b.client.PresignedGetObject(ctx, b.bucket, path, b.presignExpires, reqParams)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrapf(err, "unable to generate public link for %s", path)
|
||||
}
|
||||
|
||||
return req.String(), b.presignExpires, nil
|
||||
}
|
||||
|
||||
// prefixedPathFast is a variation of prefixedPath
|
||||
// where we don't check for the file path. This is for cases
|
||||
// where we know the file won't exist - like while writing a new file.
|
||||
|
||||
Ссылка в новой задаче
Block a user