Cloudreve/service/admin/policy.go

528 lines
16 KiB
Go

package admin
import (
"context"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"github.com/cloudreve/Cloudreve/v4/application/constants"
"github.com/cloudreve/Cloudreve/v4/application/dependency"
"github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/inventory"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/cluster/routes"
"github.com/cloudreve/Cloudreve/v4/pkg/credmanager"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver/cos"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver/ks3"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver/obs"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver/onedrive"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver/oss"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver/s3"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager"
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
"github.com/cloudreve/Cloudreve/v4/pkg/util"
"github.com/cloudreve/Cloudreve/v4/pkg/request"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/gin-gonic/gin"
)
// PathTestService 本地路径测试服务
type PathTestService struct {
Path string `json:"path" binding:"required"`
}
// SlaveTestService 从机测试服务
type SlaveTestService struct {
Secret string `json:"secret" binding:"required"`
Server string `json:"server" binding:"required"`
}
type (
SlavePingParameterCtx struct{}
// SlavePingService ping slave node
SlavePingService struct {
Callback string `json:"callback" binding:"required"`
}
)
// AddPolicyService 存储策略添加服务
type AddPolicyService struct {
//Policy model.Policy `json:"policy" binding:"required"`
}
// PolicyService 存储策略ID服务
type PolicyService struct {
ID uint `uri:"id" json:"id" binding:"required"`
Region string `json:"region"`
}
// Delete 删除存储策略
func (service *SingleStoragePolicyService) Delete(c *gin.Context) error {
// 禁止删除默认策略
if service.ID == 1 {
return serializer.NewError(serializer.CodeDeleteDefaultPolicy, "", nil)
}
dep := dependency.FromContext(c)
storagePolicyClient := dep.StoragePolicyClient()
ctx := context.WithValue(c, inventory.LoadStoragePolicyGroup{}, true)
ctx = context.WithValue(ctx, inventory.SkipStoragePolicyCache{}, true)
policy, err := storagePolicyClient.GetPolicyByID(ctx, service.ID)
if err != nil {
return serializer.NewError(serializer.CodePolicyNotExist, "", err)
}
// If policy is used by groups, return error
if len(policy.Edges.Groups) > 0 {
return serializer.NewError(serializer.CodePolicyUsedByGroups, strconv.Itoa(len(policy.Edges.Groups)), nil)
}
used, err := dep.FileClient().IsStoragePolicyUsedByEntities(ctx, service.ID)
if err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to check if policy is used by entities", err)
}
if used {
return serializer.NewError(serializer.CodePolicyUsedByFiles, "", nil)
}
err = storagePolicyClient.Delete(ctx, policy)
if err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to delete policy", err)
}
return nil
}
// Test 从机响应ping
func (service *SlavePingService) Test(c *gin.Context) error {
master, err := url.Parse(service.Callback)
if err != nil {
return serializer.NewError(serializer.CodeParamErr, "Failed to parse callback url", err)
}
dep := dependency.FromContext(c)
r := dep.RequestClient()
res, err := r.Request(
"GET",
routes.MasterPingUrl(master).String(),
nil,
request.WithContext(c),
request.WithLogger(logging.FromContext(c)),
request.WithCorrelationID(),
request.WithTimeout(time.Duration(10)*time.Second),
).DecodeResponse()
if err != nil {
return serializer.NewError(serializer.CodeSlavePingMaster, err.Error(), nil)
}
version := constants.BackendVersion
if strings.TrimSuffix(res.Data.(string), "-pro") != version {
return serializer.NewError(serializer.CodeVersionMismatch, "Master: "+res.Data.(string)+", Slave: "+version, nil)
}
return nil
}
// Test 测试从机通信
func (service *SlaveTestService) Test() serializer.Response {
//slave, err := url.Parse(service.Server)
//if err != nil {
// return serializer.ParamErrDeprecated("Failed to parse slave node server URL: "+err.Error(), nil)
//}
//
//controller, _ := url.Parse("/api/v3/slave/ping")
//
//// 请求正文
//body := map[string]string{
// "callback": model.GetSiteURL().String(),
//}
//bodyByte, _ := json.Marshal(body)
//
//r := request.NewClientDeprecated()
//res, err := r.Request(
// "POST",
// slave.ResolveReference(controller).String(),
// bytes.NewReader(bodyByte),
// request.WithTimeout(time.Duration(10)*time.Second),
// request.WithCredential(
// auth.HMACAuth{SecretKey: []byte(service.Secret)},
// int64(model.GetIntSetting("slave_api_timeout", 60)),
// ),
//).DecodeResponse()
//if err != nil {
// return serializer.ParamErrDeprecated("Failed to connect to slave node: "+err.Error(), nil)
//}
//
//if res.Code != 0 {
// return serializer.ParamErrDeprecated("Successfully connected to slave node, but slave returns: "+res.Msg, nil)
//}
return serializer.Response{}
}
// Test 测试本地路径
func (service *PathTestService) Test() serializer.Response {
//policy := model.Policy{DirNameRule: service.Path}
//path := policy.GeneratePath(1, "/My File")
//path = filepath.Join(path, "test.txt")
//file, err := util.CreatNestedFile(util.RelativePath(path))
//if err != nil {
// return serializer.ParamErrDeprecated(fmt.Sprintf("Failed to create \"%s\": %s", path, err.Error()), nil)
//}
//
//file.Close()
//os.Remove(path)
return serializer.Response{}
}
const (
policyTypeCondition = "policy_type"
)
// Policies 列出存储策略
func (service *AdminListService) Policies(c *gin.Context) (*ListPolicyResponse, error) {
dep := dependency.FromContext(c)
storagePolicyClient := dep.StoragePolicyClient()
ctx := context.WithValue(c, inventory.LoadStoragePolicyGroup{}, true)
res, err := storagePolicyClient.ListPolicies(ctx, &inventory.ListPolicyParameters{
PaginationArgs: &inventory.PaginationArgs{
Page: service.Page - 1,
PageSize: service.PageSize,
OrderBy: service.OrderBy,
Order: inventory.OrderDirection(service.OrderDirection),
},
Type: types.PolicyType(service.Conditions[policyTypeCondition]),
})
if err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to list policies", err)
}
return &ListPolicyResponse{
Pagination: res.PaginationResults,
Policies: res.Policies,
}, nil
}
type (
SingleStoragePolicyService struct {
ID int `uri:"id" json:"id" binding:"required"`
}
GetStoragePolicyParamCtx struct{}
)
const (
countEntityQuery = "countEntity"
)
func (service *SingleStoragePolicyService) Get(c *gin.Context) (*GetStoragePolicyResponse, error) {
dep := dependency.FromContext(c)
storagePolicyClient := dep.StoragePolicyClient()
ctx := context.WithValue(c, inventory.LoadStoragePolicyGroup{}, true)
ctx = context.WithValue(ctx, inventory.SkipStoragePolicyCache{}, true)
policy, err := storagePolicyClient.GetPolicyByID(ctx, service.ID)
if err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get policy", err)
}
res := &GetStoragePolicyResponse{StoragePolicy: policy}
if c.Query(countEntityQuery) != "" {
count, size, err := dep.FileClient().CountEntityByStoragePolicyID(ctx, service.ID)
if err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to count entities", err)
}
res.EntitiesCount = count
res.EntitiesSize = size
}
return res, nil
}
type (
CreateStoragePolicyService struct {
Policy *ent.StoragePolicy `json:"policy" binding:"required"`
}
CreateStoragePolicyParamCtx struct{}
)
func (service *CreateStoragePolicyService) Create(c *gin.Context) (*GetStoragePolicyResponse, error) {
dep := dependency.FromContext(c)
storagePolicyClient := dep.StoragePolicyClient()
if service.Policy.Type == types.PolicyTypeLocal {
service.Policy.DirNameRule = util.DataPath("uploads/{uid}/{path}")
}
service.Policy.ID = 0
policy, err := storagePolicyClient.Upsert(c, service.Policy)
if err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to create policy", err)
}
return &GetStoragePolicyResponse{StoragePolicy: policy}, nil
}
type (
UpdateStoragePolicyService struct {
Policy *ent.StoragePolicy `json:"policy" binding:"required"`
}
UpdateStoragePolicyParamCtx struct{}
)
func (service *UpdateStoragePolicyService) Update(c *gin.Context) (*GetStoragePolicyResponse, error) {
dep := dependency.FromContext(c)
storagePolicyClient := dep.StoragePolicyClient()
id := c.Param("id")
if id == "" {
return nil, serializer.NewError(serializer.CodeParamErr, "ID is required", nil)
}
idInt, err := strconv.Atoi(id)
if err != nil {
return nil, serializer.NewError(serializer.CodeParamErr, "Invalid ID", err)
}
service.Policy.ID = idInt
sc, tx, ctx, err := inventory.WithTx(c, storagePolicyClient)
if err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to create transaction", err)
}
_, err = sc.Upsert(ctx, service.Policy)
if err != nil {
_ = inventory.Rollback(tx)
return nil, serializer.NewError(serializer.CodeDBError, "Failed to update policy", err)
}
if err := inventory.Commit(tx); err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to commit transaction", err)
}
_ = dep.KV().Delete(manager.EntityUrlCacheKeyPrefix)
s := SingleStoragePolicyService{ID: idInt}
return s.Get(c)
}
type (
CreateStoragePolicyCorsService struct {
Policy *ent.StoragePolicy `json:"policy" binding:"required"`
}
CreateStoragePolicyCorsParamCtx struct{}
)
func (service *CreateStoragePolicyCorsService) Create(c *gin.Context) error {
dep := dependency.FromContext(c)
switch service.Policy.Type {
case types.PolicyTypeOss:
handler, err := oss.New(c, service.Policy, dep.SettingProvider(), dep.ConfigProvider(), dep.Logger(), dep.MimeDetector(c))
if err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to create oss driver", err)
}
if err := handler.CORS(); err != nil {
return serializer.NewError(serializer.CodeInternalSetting, "Failed to create cors: "+err.Error(), err)
}
return nil
case types.PolicyTypeCos:
handler, err := cos.New(c, service.Policy, dep.SettingProvider(), dep.ConfigProvider(), dep.Logger(), dep.MimeDetector(c))
if err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to create cos driver", err)
}
if err := handler.CORS(); err != nil {
return serializer.NewError(serializer.CodeInternalSetting, "Failed to create cors: "+err.Error(), err)
}
return nil
case types.PolicyTypeS3:
handler, err := s3.New(c, service.Policy, dep.SettingProvider(), dep.ConfigProvider(), dep.Logger(), dep.MimeDetector(c))
if err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to create s3 driver", err)
}
if err := handler.CORS(); err != nil {
return serializer.NewError(serializer.CodeInternalSetting, "Failed to create cors: "+err.Error(), err)
}
return nil
case types.PolicyTypeKs3:
handler, err := ks3.New(c, service.Policy, dep.SettingProvider(), dep.ConfigProvider(), dep.Logger(), dep.MimeDetector(c))
if err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to create ks3 driver", err)
}
if err := handler.CORS(); err != nil {
return serializer.NewError(serializer.CodeInternalSetting, "Failed to create cors: "+err.Error(), err)
}
return nil
case types.PolicyTypeObs:
handler, err := obs.New(c, service.Policy, dep.SettingProvider(), dep.ConfigProvider(), dep.Logger(), dep.MimeDetector(c))
if err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to create obs driver", err)
}
if err := handler.CORS(); err != nil {
return serializer.NewError(serializer.CodeInternalSetting, "Failed to create cors: "+err.Error(), err)
}
return nil
default:
return serializer.NewError(serializer.CodeParamErr, "Unsupported policy type", nil)
}
}
type (
GetOauthRedirectService struct {
ID int `json:"id" binding:"required"`
Secret string `json:"secret" binding:"required"`
AppID string `json:"app_id" binding:"required"`
}
GetOauthRedirectParamCtx struct{}
)
// GetOAuth 获取 OneDrive OAuth 地址
func (service *GetOauthRedirectService) GetOAuth(c *gin.Context) (string, error) {
dep := dependency.FromContext(c)
storagePolicyClient := dep.StoragePolicyClient()
policy, err := storagePolicyClient.GetPolicyByID(c, service.ID)
if err != nil || policy.Type != types.PolicyTypeOd {
return "", serializer.NewError(serializer.CodePolicyNotExist, "", nil)
}
// Update to latest redirect url
policy.Settings.OauthRedirect = routes.MasterPolicyOAuthCallback(dep.SettingProvider().SiteURL(c)).String()
policy.SecretKey = service.Secret
policy.BucketName = service.AppID
policy, err = storagePolicyClient.Upsert(c, policy)
if err != nil {
return "", serializer.NewError(serializer.CodeDBError, "Failed to update policy", err)
}
client := onedrive.NewClient(policy, dep.RequestClient(), dep.CredManager(), dep.Logger(), dep.SettingProvider(), 0)
redirect := client.OAuthURL(context.Background(), []string{
"offline_access",
"files.readwrite.all",
})
return redirect, nil
}
func GetPolicyOAuthURL(c *gin.Context) string {
dep := dependency.FromContext(c)
return routes.MasterPolicyOAuthCallback(dep.SettingProvider().SiteURL(c)).String()
}
// GetOauthCredentialStatus returns last refresh time of oauth credential
func (service *SingleStoragePolicyService) GetOauthCredentialStatus(c *gin.Context) (*OauthCredentialStatus, error) {
dep := dependency.FromContext(c)
storagePolicyClient := dep.StoragePolicyClient()
policy, err := storagePolicyClient.GetPolicyByID(c, service.ID)
if err != nil || policy.Type != types.PolicyTypeOd {
return nil, serializer.NewError(serializer.CodePolicyNotExist, "", nil)
}
if policy.AccessKey == "" {
return &OauthCredentialStatus{Valid: false}, nil
}
token, err := dep.CredManager().Obtain(c, onedrive.CredentialKey(policy.ID))
if err != nil {
if errors.Is(err, credmanager.ErrNotFound) {
return &OauthCredentialStatus{Valid: false}, nil
}
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get credential", err)
}
return &OauthCredentialStatus{Valid: true, LastRefreshTime: token.RefreshedAt()}, nil
}
type (
FinishOauthCallbackService struct {
Code string `json:"code" binding:"required"`
State string `json:"state" binding:"required"`
}
FinishOauthCallbackParamCtx struct{}
)
func (service *FinishOauthCallbackService) Finish(c *gin.Context) error {
dep := dependency.FromContext(c)
storagePolicyClient := dep.StoragePolicyClient()
policyId, err := strconv.Atoi(service.State)
if err != nil {
return serializer.NewError(serializer.CodeParamErr, "Invalid state", err)
}
policy, err := storagePolicyClient.GetPolicyByID(c, policyId)
if err != nil {
return serializer.NewError(serializer.CodePolicyNotExist, "", nil)
}
if policy.Type != types.PolicyTypeOd {
return serializer.NewError(serializer.CodeParamErr, "Invalid policy type", nil)
}
client := onedrive.NewClient(policy, dep.RequestClient(), dep.CredManager(), dep.Logger(), dep.SettingProvider(), 0)
credential, err := client.ObtainToken(c, onedrive.WithCode(service.Code))
if err != nil {
return serializer.NewError(serializer.CodeParamErr, "Failed to obtain token: "+err.Error(), err)
}
credManager := dep.CredManager()
err = credManager.Upsert(c, credential)
if err != nil {
return serializer.NewError(serializer.CodeInternalSetting, "Failed to upsert credential", err)
}
_, err = credManager.Obtain(c, onedrive.CredentialKey(policy.ID))
if err != nil {
return serializer.NewError(serializer.CodeInternalSetting, "Failed to obtain credential", err)
}
return nil
}
func (service *SingleStoragePolicyService) GetSharePointDriverRoot(c *gin.Context) (string, error) {
dep := dependency.FromContext(c)
storagePolicyClient := dep.StoragePolicyClient()
policy, err := storagePolicyClient.GetPolicyByID(c, service.ID)
if err != nil {
return "", serializer.NewError(serializer.CodePolicyNotExist, "", nil)
}
if policy.Type != types.PolicyTypeOd {
return "", serializer.NewError(serializer.CodeParamErr, "Invalid policy type", nil)
}
client := onedrive.NewClient(policy, dep.RequestClient(), dep.CredManager(), dep.Logger(), dep.SettingProvider(), 0)
root, err := client.GetSiteIDByURL(c, c.Query("url"))
if err != nil {
return "", serializer.NewError(serializer.CodeInternalSetting, "Failed to get site id", err)
}
return fmt.Sprintf("sites/%s/drive", root), nil
}