feat(policy): add Google Drive Oauth client

pull/1741/head
Aaron Liu 1 year ago
parent 4c18e5acd1
commit 37926e3133

@ -1 +1 @@
Subproject commit 88e4b7fbf3d5e5806ad0cedce99d845ef21704c7
Subproject commit 5e66c6fd9c8b50f31a7a2d1f1e77ef13543ee366

@ -47,8 +47,8 @@ type PolicyOption struct {
FileType []string `json:"file_type"`
// MimeType
MimeType string `json:"mimetype"`
// OdRedirect Onedrive 重定向地址
OdRedirect string `json:"od_redirect,omitempty"`
// OauthRedirect Oauth 重定向地址
OauthRedirect string `json:"od_redirect,omitempty"`
// OdProxy Onedrive 反代地址
OdProxy string `json:"od_proxy,omitempty"`
// OdDriver OneDrive 驱动器定位符
@ -155,23 +155,23 @@ func (policy *Policy) GenerateFileName(uid uint, origin string) string {
fileRule := policy.FileNameRule
replaceTable := map[string]string{
"{randomkey16}": util.RandStringRunes(16),
"{randomkey8}": util.RandStringRunes(8),
"{timestamp}": strconv.FormatInt(time.Now().Unix(), 10),
"{timestamp_nano}": strconv.FormatInt(time.Now().UnixNano(), 10),
"{uid}": strconv.Itoa(int(uid)),
"{datetime}": time.Now().Format("20060102150405"),
"{date}": time.Now().Format("20060102"),
"{year}": time.Now().Format("2006"),
"{month}": time.Now().Format("01"),
"{day}": time.Now().Format("02"),
"{hour}": time.Now().Format("15"),
"{minute}": time.Now().Format("04"),
"{second}": time.Now().Format("05"),
"{originname}": origin,
"{ext}": filepath.Ext(origin),
"{randomkey16}": util.RandStringRunes(16),
"{randomkey8}": util.RandStringRunes(8),
"{timestamp}": strconv.FormatInt(time.Now().Unix(), 10),
"{timestamp_nano}": strconv.FormatInt(time.Now().UnixNano(), 10),
"{uid}": strconv.Itoa(int(uid)),
"{datetime}": time.Now().Format("20060102150405"),
"{date}": time.Now().Format("20060102"),
"{year}": time.Now().Format("2006"),
"{month}": time.Now().Format("01"),
"{day}": time.Now().Format("02"),
"{hour}": time.Now().Format("15"),
"{minute}": time.Now().Format("04"),
"{second}": time.Now().Format("05"),
"{originname}": origin,
"{ext}": filepath.Ext(origin),
"{originname_without_ext}": strings.TrimSuffix(origin, filepath.Ext(origin)),
"{uuid}": uuid.Must(uuid.NewV4()).String(),
"{uuid}": uuid.Must(uuid.NewV4()).String(),
}
fileRule = util.Replace(replaceTable, fileRule)

@ -25,7 +25,7 @@ func TestGetPolicyByID(t *testing.T) {
asserts.NoError(err)
asserts.NoError(mock.ExpectationsWereMet())
asserts.Equal("默认存储策略", policy.Name)
asserts.Equal("123", policy.OptionsSerialized.OdRedirect)
asserts.Equal("123", policy.OptionsSerialized.OauthRedirect)
rows = sqlmock.NewRows([]string{"name", "type", "options"})
mock.ExpectQuery("^SELECT(.+)").WillReturnRows(rows)
@ -39,7 +39,7 @@ func TestGetPolicyByID(t *testing.T) {
policy, err := GetPolicyByID(uint(22))
asserts.NoError(err)
asserts.Equal("默认存储策略", policy.Name)
asserts.Equal("123", policy.OptionsSerialized.OdRedirect)
asserts.Equal("123", policy.OptionsSerialized.OauthRedirect)
}
@ -50,7 +50,7 @@ func TestPolicy_BeforeSave(t *testing.T) {
testPolicy := Policy{
OptionsSerialized: PolicyOption{
OdRedirect: "123",
OauthRedirect: "123",
},
}
expected, _ := json.Marshal(testPolicy.OptionsSerialized)

@ -35,8 +35,8 @@ type Controller interface {
// Get master node info
GetMasterInfo(string) (*MasterInfo, error)
// Get master OneDrive policy credential
GetOneDriveToken(string, uint) (string, error)
// Get master Oauth based policy credential
GetPolicyOauthToken(string, uint) (string, error)
}
type slaveController struct {
@ -181,8 +181,8 @@ func (c *slaveController) GetMasterInfo(id string) (*MasterInfo, error) {
return nil, ErrMasterNotFound
}
// GetOneDriveToken 获取主机OneDrive凭证
func (c *slaveController) GetOneDriveToken(id string, policyID uint) (string, error) {
// GetPolicyOauthToken 获取主机存储策略 Oauth 凭证
func (c *slaveController) GetPolicyOauthToken(id string, policyID uint) (string, error) {
c.lock.RLock()
if node, ok := c.masters[id]; ok {
@ -190,7 +190,7 @@ func (c *slaveController) GetOneDriveToken(id string, policyID uint) (string, er
res, err := node.Client.Request(
"GET",
fmt.Sprintf("/api/v3/slave/credential/onedrive/%d", policyID),
fmt.Sprintf("/api/v3/slave/credential/%d", policyID),
nil,
).CheckHTTPResponse(200).DecodeResponse()
if err != nil {

@ -320,7 +320,7 @@ func TestSlaveController_GetOneDriveToken(t *testing.T) {
// node not exit
{
res, err := c.GetOneDriveToken("2", 1)
res, err := c.GetPolicyOauthToken("2", 1)
a.Equal(ErrMasterNotFound, err)
a.Empty(res)
}
@ -336,7 +336,7 @@ func TestSlaveController_GetOneDriveToken(t *testing.T) {
"1": {Client: mockRequest},
},
}
res, err := c.GetOneDriveToken("1", 1)
res, err := c.GetPolicyOauthToken("1", 1)
a.Error(err)
a.Empty(res)
mockRequest.AssertExpectations(t)
@ -356,7 +356,7 @@ func TestSlaveController_GetOneDriveToken(t *testing.T) {
"1": {Client: mockRequest},
},
}
res, err := c.GetOneDriveToken("1", 1)
res, err := c.GetPolicyOauthToken("1", 1)
a.Equal(1, err.(serializer.AppError).Code)
a.Empty(res)
mockRequest.AssertExpectations(t)
@ -376,7 +376,7 @@ func TestSlaveController_GetOneDriveToken(t *testing.T) {
"1": {Client: mockRequest},
},
}
res, err := c.GetOneDriveToken("1", 1)
res, err := c.GetPolicyOauthToken("1", 1)
a.NoError(err)
a.Equal("expected", res)
mockRequest.AssertExpectations(t)

@ -0,0 +1,72 @@
package googledrive
import (
"errors"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
)
// Client Google Drive client
type Client struct {
Endpoints *Endpoints
Policy *model.Policy
Credential *Credential
ClientID string
ClientSecret string
Redirect string
Request request.Client
ClusterController cluster.Controller
}
// Endpoints OneDrive客户端相关设置
type Endpoints struct {
UserConsentEndpoint string // OAuth认证的基URL
TokenEndpoint string // OAuth token 基URL
EndpointURL string // 接口请求的基URL
}
const (
TokenCachePrefix = "googledrive_"
oauthEndpoint = "https://oauth2.googleapis.com/token"
userConsentBase = "https://accounts.google.com/o/oauth2/auth"
v3DriveEndpoint = "https://www.googleapis.com/drive/v3"
)
var (
// Defualt required scopes
RequiredScope = []string{
"https://www.googleapis.com/auth/drive",
"openid",
"profile",
"https://www.googleapis.com/auth/userinfo.profile",
}
// ErrInvalidRefreshToken 上传策略无有效的RefreshToken
ErrInvalidRefreshToken = errors.New("no valid refresh token in this policy")
)
// NewClient 根据存储策略获取新的client
func NewClient(policy *model.Policy) (*Client, error) {
client := &Client{
Endpoints: &Endpoints{
TokenEndpoint: oauthEndpoint,
UserConsentEndpoint: userConsentBase,
EndpointURL: v3DriveEndpoint,
},
Credential: &Credential{
RefreshToken: policy.AccessKey,
},
Policy: policy,
ClientID: policy.BucketName,
ClientSecret: policy.SecretKey,
Redirect: policy.OptionsSerialized.OauthRedirect,
Request: request.NewClient(),
ClusterController: cluster.DefaultController,
}
return client, nil
}

@ -0,0 +1,70 @@
package googledrive
import (
"context"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"net/url"
)
// Driver Google Drive 适配器
type Driver struct {
Policy *model.Policy
HTTPClient request.Client
}
// http://localhost:3000/api/v3/callback/googledrive/auth?code=4/0AVHEtk4AaNbo5YoCrMSGgoJfZfe6SgEOVmA7XtalZl8BMtdsAIRWqxt6jO4NKJCxGVxyQA&scope=profile%20openid%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/drive&authuser=0&prompt=consent
// https://accounts.google.com/o/oauth2/v2/auth?client_id=89866991293-5uja7qsbl8btuak3hb41h3o8u9jhlckg.apps.googleusercontent.com&response_type=code&redirect_uri=http://localhost:3000/api/v3/callback/googledrive/auth&scope=openid+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&prompt=consent
//https://accounts.google.com/o/oauth2/auth?client_id=202264815644.apps.googleusercontent.com&response_type=code&redirect_uri=http%3A%2F%2F127.0.0.1%3A53682%2F&scope=openid+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fphotoslibrary&access_type=offline&prompt=consent&state=MjAyMjY0ODE1NjQ0LmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tOjpYNFozY2E4eGZXRGIxVm9vLUY5YTdaeEo6Omh0dHA6Ly8xMjcuMC4wLjE6NTM2ODIv
// NewDriver 从存储策略初始化新的Driver实例
func NewDriver(policy *model.Policy) (driver.Handler, error) {
return &Driver{
Policy: policy,
HTTPClient: request.NewClient(),
}, nil
}
func (d *Driver) Put(ctx context.Context, file fsctx.FileHeader) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Delete(ctx context.Context, files []string) ([]string, error) {
//TODO implement me
panic("implement me")
}
func (d *Driver) Get(ctx context.Context, path string) (response.RSCloser, error) {
//TODO implement me
panic("implement me")
}
func (d *Driver) Thumb(ctx context.Context, file *model.File) (*response.ContentResponse, error) {
//TODO implement me
panic("implement me")
}
func (d *Driver) Source(ctx context.Context, path string, url url.URL, ttl int64, isDownload bool, speed int) (string, error) {
//TODO implement me
panic("implement me")
}
func (d *Driver) Token(ctx context.Context, ttl int64, uploadSession *serializer.UploadSession, file fsctx.FileHeader) (*serializer.UploadCredential, error) {
//TODO implement me
panic("implement me")
}
func (d *Driver) CancelToken(ctx context.Context, uploadSession *serializer.UploadSession) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) List(ctx context.Context, path string, recursive bool) ([]response.Object, error) {
//TODO implement me
panic("implement me")
}

@ -0,0 +1,174 @@
package googledrive
import (
"context"
"encoding/json"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/oauth"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"io"
"net/http"
"net/url"
"strings"
"time"
)
// Credential 获取token时返回的凭证
type Credential struct {
ExpiresIn int64 `json:"expires_in"`
Scope string `json:"scope"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
UserID string `json:"user_id"`
}
// OAuthError OAuth相关接口的错误响应
type OAuthError struct {
ErrorType string `json:"error"`
ErrorDescription string `json:"error_description"`
}
// Error 实现error接口
func (err OAuthError) Error() string {
return err.ErrorDescription
}
// OAuthURL 获取OAuth认证页面URL
func (client *Client) OAuthURL(ctx context.Context, scope []string) string {
query := url.Values{
"client_id": {client.ClientID},
"scope": {strings.Join(scope, " ")},
"response_type": {"code"},
"redirect_uri": {client.Redirect},
"access_type": {"offline"},
"prompt": {"consent"},
}
u, _ := url.Parse(client.Endpoints.UserConsentEndpoint)
u.RawQuery = query.Encode()
return u.String()
}
// ObtainToken 通过code或refresh_token兑换token
func (client *Client) ObtainToken(ctx context.Context, code, refreshToken string) (*Credential, error) {
body := url.Values{
"client_id": {client.ClientID},
"redirect_uri": {client.Redirect},
"client_secret": {client.ClientSecret},
}
if code != "" {
body.Add("grant_type", "authorization_code")
body.Add("code", code)
} else {
body.Add("grant_type", "refresh_token")
body.Add("refresh_token", refreshToken)
}
strBody := body.Encode()
res := client.Request.Request(
"POST",
client.Endpoints.TokenEndpoint,
io.NopCloser(strings.NewReader(strBody)),
request.WithHeader(http.Header{
"Content-Type": {"application/x-www-form-urlencoded"}},
),
request.WithContentLength(int64(len(strBody))),
)
if res.Err != nil {
return nil, res.Err
}
respBody, err := res.GetResponse()
if err != nil {
return nil, err
}
var (
errResp OAuthError
credential Credential
decodeErr error
)
if res.Response.StatusCode != 200 {
decodeErr = json.Unmarshal([]byte(respBody), &errResp)
} else {
decodeErr = json.Unmarshal([]byte(respBody), &credential)
}
if decodeErr != nil {
return nil, decodeErr
}
if errResp.ErrorType != "" {
return nil, errResp
}
return &credential, nil
}
// UpdateCredential 更新凭证,并检查有效期
func (client *Client) UpdateCredential(ctx context.Context, isSlave bool) error {
if isSlave {
return client.fetchCredentialFromMaster(ctx)
}
oauth.GlobalMutex.Lock(client.Policy.ID)
defer oauth.GlobalMutex.Unlock(client.Policy.ID)
// 如果已存在凭证
if client.Credential != nil && client.Credential.AccessToken != "" {
// 检查已有凭证是否过期
if client.Credential.ExpiresIn > time.Now().Unix() {
// 未过期,不要更新
return nil
}
}
// 尝试从缓存中获取凭证
if cacheCredential, ok := cache.Get(TokenCachePrefix + client.ClientID); ok {
credential := cacheCredential.(Credential)
if credential.ExpiresIn > time.Now().Unix() {
client.Credential = &credential
return nil
}
}
// 获取新的凭证
if client.Credential == nil || client.Credential.RefreshToken == "" {
// 无有效的RefreshToken
util.Log().Error("Failed to refresh credential for policy %q, please login your Google account again.", client.Policy.Name)
return ErrInvalidRefreshToken
}
credential, err := client.ObtainToken(ctx, "", client.Credential.RefreshToken)
if err != nil {
return err
}
// 更新有效期为绝对时间戳
expires := credential.ExpiresIn - 60
credential.ExpiresIn = time.Now().Add(time.Duration(expires) * time.Second).Unix()
// refresh token for Google Drive does not expire in production
credential.RefreshToken = client.Credential.RefreshToken
client.Credential = credential
// 更新缓存
cache.Set(TokenCachePrefix+client.ClientID, *credential, int(expires))
return nil
}
func (client *Client) AccessToken() string {
return client.Credential.AccessToken
}
// UpdateCredential 更新凭证,并检查有效期
func (client *Client) fetchCredentialFromMaster(ctx context.Context) error {
res, err := client.ClusterController.GetPolicyOauthToken(client.Policy.MasterID, client.Policy.ID)
if err != nil {
return err
}
client.Credential = &Credential{AccessToken: res}
return nil
}

@ -58,7 +58,7 @@ func NewClient(policy *model.Policy) (*Client, error) {
Policy: policy,
ClientID: policy.BucketName,
ClientSecret: policy.SecretKey,
Redirect: policy.OptionsSerialized.OdRedirect,
Redirect: policy.OptionsSerialized.OauthRedirect,
Request: request.NewClient(),
ClusterController: cluster.DefaultController,
}

@ -10,6 +10,7 @@ import (
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/oauth"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
@ -128,8 +129,8 @@ func (client *Client) UpdateCredential(ctx context.Context, isSlave bool) error
return client.fetchCredentialFromMaster(ctx)
}
GlobalMutex.Lock(client.Policy.ID)
defer GlobalMutex.Unlock(client.Policy.ID)
oauth.GlobalMutex.Lock(client.Policy.ID)
defer oauth.GlobalMutex.Unlock(client.Policy.ID)
// 如果已存在凭证
if client.Credential != nil && client.Credential.AccessToken != "" {
@ -175,9 +176,13 @@ func (client *Client) UpdateCredential(ctx context.Context, isSlave bool) error
return nil
}
func (client *Client) AccessToken() string {
return client.Credential.AccessToken
}
// UpdateCredential 更新凭证,并检查有效期
func (client *Client) fetchCredentialFromMaster(ctx context.Context) error {
res, err := client.ClusterController.GetOneDriveToken(client.Policy.MasterID, client.Policy.ID)
res, err := client.ClusterController.GetPolicyOauthToken(client.Policy.MasterID, client.Policy.ID)
if err != nil {
return err
}

@ -368,7 +368,7 @@ func TestClient_UpdateCredential(t *testing.T) {
// slave failed
{
mockController := &controllermock.SlaveControllerMock{}
mockController.On("GetOneDriveToken", testMock.Anything, testMock.Anything).Return("", errors.New("error"))
mockController.On("GetPolicyOauthToken", testMock.Anything, testMock.Anything).Return("", errors.New("error"))
client.ClusterController = mockController
err := client.UpdateCredential(context.Background(), true)
asserts.Error(err)
@ -377,7 +377,7 @@ func TestClient_UpdateCredential(t *testing.T) {
// slave success
{
mockController := &controllermock.SlaveControllerMock{}
mockController.On("GetOneDriveToken", testMock.Anything, testMock.Anything).Return("AccessToken3", nil)
mockController.On("GetPolicyOauthToken", testMock.Anything, testMock.Anything).Return("AccessToken3", nil)
client.ClusterController = mockController
err := client.UpdateCredential(context.Background(), true)
asserts.NoError(err)

@ -176,6 +176,10 @@ func (fs *FileSystem) DispatchHandler() error {
handler, err := s3.NewDriver(currentPolicy)
fs.Handler = handler
return err
case "googledrive":
handler, err := googledrive.NewDriver(policy)
fs.Handler = handler
return err
default:
return ErrUnknownPolicyType
}

@ -1,4 +1,4 @@
package onedrive
package oauth
import "sync"

@ -0,0 +1,8 @@
package oauth
import "context"
type TokenProvider interface {
UpdateCredential(ctx context.Context, isSlave bool) error
AccessToken() string
}

@ -37,7 +37,7 @@ func (s SlaveControllerMock) GetMasterInfo(s2 string) (*cluster.MasterInfo, erro
return args.Get(0).(*cluster.MasterInfo), args.Error(1)
}
func (s SlaveControllerMock) GetOneDriveToken(s2 string, u uint) (string, error) {
func (s SlaveControllerMock) GetPolicyOauthToken(s2 string, u uint) (string, error) {
args := s.Called(s2, u)
return args.String(0), args.Error(1)
}

@ -192,14 +192,16 @@ func AdminAddSCF(c *gin.Context) {
}
}
// AdminOneDriveOAuth 获取 OneDrive OAuth URL
func AdminOneDriveOAuth(c *gin.Context) {
var service admin.PolicyService
if err := c.ShouldBindUri(&service); err == nil {
res := service.GetOAuth(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// AdminOAuthURL 获取 OneDrive OAuth URL
func AdminOAuthURL(policyType string) gin.HandlerFunc {
return func(c *gin.Context) {
var service admin.PolicyService
if err := c.ShouldBindUri(&service); err == nil {
res := service.GetOAuth(c, policyType)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
}

@ -83,9 +83,27 @@ func OneDriveCallback(c *gin.Context) {
// OneDriveOAuth OneDrive 授权回调
func OneDriveOAuth(c *gin.Context) {
var callbackBody callback.OneDriveOauthService
var callbackBody callback.OauthService
if err := c.ShouldBindQuery(&callbackBody); err == nil {
res := callbackBody.Auth(c)
res := callbackBody.OdAuth(c)
redirect := model.GetSiteURL()
redirect.Path = path.Join(redirect.Path, "/admin/policy")
queries := redirect.Query()
queries.Add("code", strconv.Itoa(res.Code))
queries.Add("msg", res.Msg)
queries.Add("err", res.Error)
redirect.RawQuery = queries.Encode()
c.Redirect(303, redirect.String())
} else {
c.JSON(200, ErrorResponse(err))
}
}
// GoogleDriveOAuth Google Drive 授权回调
func GoogleDriveOAuth(c *gin.Context) {
var callbackBody callback.OauthService
if err := c.ShouldBindQuery(&callbackBody); err == nil {
res := callbackBody.GDriveAuth(c)
redirect := model.GetSiteURL()
redirect.Path = path.Join(redirect.Path, "/admin/policy")
queries := redirect.Query()

@ -223,9 +223,9 @@ func SlaveNotificationPush(c *gin.Context) {
}
}
// SlaveGetOneDriveCredential 从机获取主机的OneDrive存储策略凭证
func SlaveGetOneDriveCredential(c *gin.Context) {
var service node.OneDriveCredentialService
// SlaveGetOauthCredential 从机获取主机的OneDrive存储策略凭证
func SlaveGetOauthCredential(c *gin.Context) {
var service node.OauthCredentialService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Get(c)
c.JSON(200, res)

@ -260,8 +260,8 @@ func InitMasterRouter() *gin.Engine {
// 删除上传会话
upload.DELETE(":sessionId", controllers.SlaveDeleteUploadSession)
}
// OneDrive 存储策略凭证
slave.GET("credential/onedrive/:id", controllers.SlaveGetOneDriveCredential)
// Oauth 存储策略凭证
slave.GET("credential/:id", controllers.SlaveGetOauthCredential)
}
// 回调接口
@ -310,6 +310,15 @@ func InitMasterRouter() *gin.Engine {
controllers.OneDriveOAuth,
)
}
// Google Drive related
gdrive := callback.Group("googledrive")
{
// OAuth 完成
gdrive.GET(
"auth",
controllers.GoogleDriveOAuth,
)
}
// 腾讯云COS策略上传回调
callback.GET(
"cos/:sessionID",
@ -454,7 +463,14 @@ func InitMasterRouter() *gin.Engine {
// 创建COS回调函数
policy.POST("scf", controllers.AdminAddSCF)
// 获取 OneDrive OAuth URL
policy.GET(":id/oauth", controllers.AdminOneDriveOAuth)
oauth := policy.Group(":id/oauth")
{
// 获取 OneDrive OAuth URL
oauth.GET("onedrive", controllers.AdminOAuthURL("onedrive"))
// 获取 Google Drive OAuth URL
oauth.GET("googledrive", controllers.AdminOAuthURL("googledrive"))
}
// 获取 存储策略
policy.GET(":id", controllers.AdminGetPolicy)
// 删除 存储策略

@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/googledrive"
"net/http"
"net/url"
"os"
@ -104,27 +105,41 @@ func (service *PolicyService) Get() serializer.Response {
}
// GetOAuth 获取 OneDrive OAuth 地址
func (service *PolicyService) GetOAuth(c *gin.Context) serializer.Response {
func (service *PolicyService) GetOAuth(c *gin.Context, policyType string) serializer.Response {
policy, err := model.GetPolicyByID(service.ID)
if err != nil || policy.Type != "onedrive" {
if err != nil || policy.Type != policyType {
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
}
client, err := onedrive.NewClient(&policy)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Failed to initialize OneDrive client", err)
}
util.SetSession(c, map[string]interface{}{
"onedrive_oauth_policy": policy.ID,
policyType + "_oauth_policy": policy.ID,
})
cache.Deletes([]string{policy.BucketName}, "onedrive_")
var redirect string
switch policy.Type {
case "onedrive":
client, err := onedrive.NewClient(&policy)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Failed to initialize OneDrive client", err)
}
redirect = client.OAuthURL(context.Background(), []string{
"offline_access",
"files.readwrite.all",
})
case "googledrive":
client, err := googledrive.NewClient(&policy)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Failed to initialize Google Drive client", err)
}
redirect = client.OAuthURL(context.Background(), googledrive.RequiredScope)
}
// Delete token cache
cache.Deletes([]string{policy.BucketName}, policyType+"_")
return serializer.Response{Data: client.OAuthURL(context.Background(), []string{
"offline_access",
"files.readwrite.all",
})}
return serializer.Response{Data: redirect}
}
// AddSCF 创建回调云函数

@ -6,22 +6,70 @@ import (
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/googledrive"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/onedrive"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"strings"
)
// OneDriveOauthService OneDrive 授权回调服务
type OneDriveOauthService struct {
// OauthService OAuth 存储策略授权回调服务
type OauthService struct {
Code string `form:"code"`
Error string `form:"error"`
ErrorMsg string `form:"error_description"`
Scope string `form:"scope"`
}
// Auth 更新认证信息
func (service *OneDriveOauthService) Auth(c *gin.Context) serializer.Response {
// GDriveAuth Google Drive 更新认证信息
func (service *OauthService) GDriveAuth(c *gin.Context) serializer.Response {
if service.Error != "" {
return serializer.ParamErr(service.Error, nil)
}
// validate required scope
if missing, found := lo.Find[string](googledrive.RequiredScope, func(item string) bool {
return !strings.Contains(service.Scope, item)
}); found {
return serializer.ParamErr(fmt.Sprintf("Missing required scope: %s", missing), nil)
}
policyID, ok := util.GetSession(c, "googledrive_oauth_policy").(uint)
if !ok {
return serializer.Err(serializer.CodeNotFound, "", nil)
}
util.DeleteSession(c, "googledrive_oauth_policy")
policy, err := model.GetPolicyByID(policyID)
if err != nil {
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
}
client, err := googledrive.NewClient(&policy)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Failed to initialize Google Drive client", err)
}
credential, err := client.ObtainToken(c, service.Code, "")
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Failed to fetch AccessToken", err)
}
// 更新存储策略的 RefreshToken
client.Policy.AccessKey = credential.RefreshToken
if err := client.Policy.SaveAndClearCache(); err != nil {
return serializer.DBErr("Failed to update RefreshToken", err)
}
cache.Deletes([]string{client.Policy.AccessKey}, googledrive.TokenCachePrefix)
return serializer.Response{}
}
// OdAuth OneDrive 更新认证信息
func (service *OauthService) OdAuth(c *gin.Context) serializer.Response {
if service.Error != "" {
return serializer.ParamErr(service.ErrorMsg, nil)
}

@ -5,7 +5,9 @@ import (
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/googledrive"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/onedrive"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/oauth"
"github.com/cloudreve/Cloudreve/v3/pkg/mq"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/gin-gonic/gin"
@ -15,7 +17,7 @@ type SlaveNotificationService struct {
Subject string `uri:"subject" binding:"required"`
}
type OneDriveCredentialService struct {
type OauthCredentialService struct {
PolicyID uint `uri:"id" binding:"required"`
}
@ -43,21 +45,32 @@ func (s *SlaveNotificationService) HandleSlaveNotificationPush(c *gin.Context) s
return serializer.Response{}
}
// Get 获取主机OneDrive策略的AccessToken
func (s *OneDriveCredentialService) Get(c *gin.Context) serializer.Response {
// Get 获取主机Oauth策略的AccessToken
func (s *OauthCredentialService) Get(c *gin.Context) serializer.Response {
policy, err := model.GetPolicyByID(s.PolicyID)
if err != nil {
return serializer.Err(serializer.CodePolicyNotExist, "", err)
}
client, err := onedrive.NewClient(&policy)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Cannot initialize OneDrive client", err)
var client oauth.TokenProvider
switch policy.Type {
case "onedrive":
client, err = onedrive.NewClient(&policy)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Cannot initialize OneDrive client", err)
}
case "googledrive":
client, err = googledrive.NewClient(&policy)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Cannot initialize Google Drive client", err)
}
default:
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
}
if err := client.UpdateCredential(c, conf.SystemConfig.Mode == "slave"); err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Cannot refresh OneDrive credential", err)
}
return serializer.Response{Data: client.Credential.AccessToken}
return serializer.Response{Data: client.AccessToken()}
}

Loading…
Cancel
Save