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/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 _, err = storagePolicyClient.Upsert(c, service.Policy) if err != nil { return nil, serializer.NewError(serializer.CodeDBError, "Failed to update policy", 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.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.CodeIncorrectPassword, "Failed to obtain token", 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 }