From 6bc4cfc8acb9ab6dc974d12620e06561b0883f1a Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Fri, 19 Jan 2024 23:06:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20OneDrive=20=E6=94=AF=E6=8C=81=E4=B8=96?= =?UTF-8?q?=E7=BA=AA=E4=BA=92=E8=81=94=20(#3655)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/backup.go | 6 +- backend/app/dto/backup.go | 6 + backend/app/service/backup.go | 51 +++---- backend/init/migration/migrations/v_1_9.go | 7 +- .../utils/cloud_storage/client/onedrive.go | 15 +-- cmd/server/docs/docs.go | 35 ++++- cmd/server/docs/swagger.json | 35 ++++- cmd/server/docs/swagger.yaml | 22 ++- frontend/src/api/interface/backup.ts | 5 + frontend/src/api/modules/setting.ts | 2 +- frontend/src/lang/modules/en.ts | 7 +- frontend/src/lang/modules/tw.ts | 7 +- frontend/src/lang/modules/zh.ts | 7 +- .../views/setting/backup-account/index.vue | 2 + .../setting/backup-account/onedrive/index.vue | 127 ++++++++++++++---- 15 files changed, 266 insertions(+), 68 deletions(-) diff --git a/backend/app/api/v1/backup.go b/backend/app/api/v1/backup.go index 82bdb0d99..a0abfd476 100644 --- a/backend/app/api/v1/backup.go +++ b/backend/app/api/v1/backup.go @@ -102,16 +102,16 @@ func (b *BaseApi) ListBuckets(c *gin.Context) { // @Summary Load OneDrive info // @Description 获取 OneDrive 信息 // @Accept json -// @Success 200 string clientID +// @Success 200 {object} dto.OneDriveInfo // @Security ApiKeyAuth // @Router /settings/backup/onedrive [get] func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) { - clientID, err := backupService.LoadOneDriveInfo() + data, err := backupService.LoadOneDriveInfo() if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } - helper.SuccessWithData(c, clientID) + helper.SuccessWithData(c, data) } // @Tags Backup Account diff --git a/backend/app/dto/backup.go b/backend/app/dto/backup.go index 94b589990..cfe612f4b 100644 --- a/backend/app/dto/backup.go +++ b/backend/app/dto/backup.go @@ -21,6 +21,12 @@ type BackupInfo struct { Vars string `json:"vars"` } +type OneDriveInfo struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RedirectUri string `json:"redirect_uri"` +} + type BackupSearchFile struct { Type string `json:"type" validate:"required"` } diff --git a/backend/app/service/backup.go b/backend/app/service/backup.go index dbb69a6f7..a3b85e232 100644 --- a/backend/app/service/backup.go +++ b/backend/app/service/backup.go @@ -28,7 +28,7 @@ type BackupService struct{} type IBackupService interface { List() ([]dto.BackupInfo, error) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) - LoadOneDriveInfo() (string, error) + LoadOneDriveInfo() (dto.OneDriveInfo, error) DownloadRecord(info dto.DownloadRecord) (string, error) Create(backupDto dto.BackupOperate) error GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error) @@ -129,16 +129,29 @@ type loadSizeHelper struct { client cloud_storage.CloudStorageClient } -func (u *BackupService) LoadOneDriveInfo() (string, error) { - OneDriveID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID")) +func (u *BackupService) LoadOneDriveInfo() (dto.OneDriveInfo, error) { + var data dto.OneDriveInfo + data.RedirectUri = constant.OneDriveRedirectURI + clientID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID")) if err != nil { - return "", err + return data, err } - idItem, err := base64.StdEncoding.DecodeString(OneDriveID.Value) + idItem, err := base64.StdEncoding.DecodeString(clientID.Value) if err != nil { - return "", err + return data, err } - return string(idItem), err + data.ClientID = string(idItem) + clientSecret, err := settingRepo.Get(settingRepo.WithByKey("OneDriveSc")) + if err != nil { + return data, err + } + secretItem, err := base64.StdEncoding.DecodeString(clientSecret.Value) + if err != nil { + return data, err + } + data.ClientSecret = string(secretItem) + + return data, err } func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) { @@ -418,20 +431,16 @@ func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error { if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { return fmt.Errorf("unmarshal backup vars failed, err: %v", err) } - code, ok := varMap["code"] - if !ok { - return errors.New("no such token in request, please retry!") - } - token, refreshToken, err := client.RefreshToken("authorization_code", code.(string)) + token, refreshToken, err := client.RefreshToken("authorization_code", varMap) if err != nil { return err } + delete(varMap, "code") backup.Credential = token - varMapItem := make(map[string]interface{}) - varMapItem["refresh_status"] = constant.StatusSuccess - varMapItem["refresh_time"] = time.Now().Format("2006-01-02 15:04:05") - varMapItem["refresh_token"] = refreshToken - itemVars, err := json.Marshal(varMapItem) + varMap["refresh_status"] = constant.StatusSuccess + varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05") + varMap["refresh_token"] = refreshToken + itemVars, err := json.Marshal(varMap) if err != nil { return fmt.Errorf("json marshal var map failed, err: %v", err) } @@ -547,13 +556,7 @@ func (u *BackupService) Run() { global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err) return } - refreshItem, ok := varMap["refresh_token"] - if !ok { - global.LOG.Error("Failed to refresh OneDrive token, please retry, err: no such refresh token") - return - } - - token, refreshToken, err := client.RefreshToken("refresh_token", refreshItem.(string)) + token, refreshToken, err := client.RefreshToken("refresh_token", varMap) varMap["refresh_status"] = constant.StatusSuccess varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05") if err != nil { diff --git a/backend/init/migration/migrations/v_1_9.go b/backend/init/migration/migrations/v_1_9.go index d725ac6f7..c4784d36a 100644 --- a/backend/init/migration/migrations/v_1_9.go +++ b/backend/init/migration/migrations/v_1_9.go @@ -228,7 +228,12 @@ var UpdateOneDriveToken = &gormigrate.Migration{ global.CONF.System.OneDriveSc = string(scItem) varMap := make(map[string]interface{}) - token, refreshToken, err := client.RefreshToken("refresh_token", backup.Credential) + varMap["isCN"] = false + varMap["client_id"] = global.CONF.System.OneDriveID + varMap["client_secret"] = global.CONF.System.OneDriveSc + varMap["redirect_uri"] = constant.OneDriveRedirectURI + varMap["refresh_token"] = backup.Credential + token, refreshToken, err := client.RefreshToken("refresh_token", varMap) varMap["refresh_status"] = constant.StatusSuccess varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05") if err != nil { diff --git a/backend/utils/cloud_storage/client/onedrive.go b/backend/utils/cloud_storage/client/onedrive.go index 576310f46..115943530 100644 --- a/backend/utils/cloud_storage/client/onedrive.go +++ b/backend/utils/cloud_storage/client/onedrive.go @@ -15,8 +15,6 @@ import ( "strconv" "strings" - "github.com/1Panel-dev/1Panel/backend/constant" - "github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/utils/files" odsdk "github.com/goh-chunlin/go-onedrive/onedrive" "golang.org/x/oauth2" @@ -192,19 +190,18 @@ func (o *oneDriveClient) loadIDByPath(path string) (string, error) { return driveItem.Id, nil } -func RefreshToken(grantType, grantCode string) (string, string, error) { +func RefreshToken(grantType string, varMap map[string]interface{}) (string, string, error) { data := url.Values{} - data.Set("client_id", global.CONF.System.OneDriveID) - data.Set("client_secret", global.CONF.System.OneDriveSc) - data.Set("grant_type", "refresh_token") + data.Set("client_id", loadParamFromVars("client_id", true, varMap)) + data.Set("client_secret", loadParamFromVars("client_secret", true, varMap)) if grantType == "refresh_token" { data.Set("grant_type", "refresh_token") - data.Set("refresh_token", grantCode) + data.Set("refresh_token", loadParamFromVars("refresh_token", true, varMap)) } else { data.Set("grant_type", "authorization_code") - data.Set("code", grantCode) + data.Set("code", loadParamFromVars("code", true, varMap)) } - data.Set("redirect_uri", constant.OneDriveRedirectURI) + data.Set("redirect_uri", loadParamFromVars("redirect_uri", true, varMap)) client := &http.Client{} req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode())) if err != nil { diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 6280c1cf0..e2dc009e0 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -9390,7 +9390,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "string" + "$ref": "#/definitions/dto.OneDriveInfo" } } } @@ -9613,6 +9613,25 @@ const docTemplate = `{ } } }, + "/settings/backup/refresh/onedrive": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "刷新 OneDrive token", + "tags": [ + "Backup Account" + ], + "summary": "Refresh OneDrive token", + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/settings/backup/search": { "get": { "security": [ @@ -16797,6 +16816,20 @@ const docTemplate = `{ "ProxyCache" ] }, + "dto.OneDriveInfo": { + "type": "object", + "properties": { + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + } + } + }, "dto.Operate": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 2ff5a7d59..badace5b1 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -9383,7 +9383,7 @@ "200": { "description": "OK", "schema": { - "type": "string" + "$ref": "#/definitions/dto.OneDriveInfo" } } } @@ -9606,6 +9606,25 @@ } } }, + "/settings/backup/refresh/onedrive": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "刷新 OneDrive token", + "tags": [ + "Backup Account" + ], + "summary": "Refresh OneDrive token", + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/settings/backup/search": { "get": { "security": [ @@ -16790,6 +16809,20 @@ "ProxyCache" ] }, + "dto.OneDriveInfo": { + "type": "object", + "properties": { + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + } + } + }, "dto.Operate": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index a40a2aedd..c2729825b 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -1865,6 +1865,15 @@ definitions: - CACHE - HttpPer - ProxyCache + dto.OneDriveInfo: + properties: + client_id: + type: string + client_secret: + type: string + redirect_uri: + type: string + type: object dto.Operate: properties: operation: @@ -10936,7 +10945,7 @@ paths: "200": description: OK schema: - type: string + $ref: '#/definitions/dto.OneDriveInfo' security: - ApiKeyAuth: [] summary: Load OneDrive info @@ -11083,6 +11092,17 @@ paths: formatEN: recover [type] data [name][detailName] from [file] formatZH: 从 [file] 恢复 [type] 数据 [name][detailName] paramKeys: [] + /settings/backup/refresh/onedrive: + post: + description: 刷新 OneDrive token + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Refresh OneDrive token + tags: + - Backup Account /settings/backup/search: get: description: 获取备份账号列表 diff --git a/frontend/src/api/interface/backup.ts b/frontend/src/api/interface/backup.ts index af2ac7c6a..3624cbac6 100644 --- a/frontend/src/api/interface/backup.ts +++ b/frontend/src/api/interface/backup.ts @@ -12,6 +12,11 @@ export namespace Backup { varsJson: object; createdAt: Date; } + export interface OneDriveInfo { + client_id: string; + client_secret: string; + redirect_uri: string; + } export interface BackupOperate { id: number; type: string; diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index 4385d24f4..c3fa8f02e 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -101,7 +101,7 @@ export const getBackupList = () => { return http.get>(`/settings/backup/search`); }; export const getOneDriveInfo = () => { - return http.get(`/settings/backup/onedrive`); + return http.get(`/settings/backup/onedrive`); }; export const getFilesFromBackup = (type: string) => { return http.post>(`/settings/backup/search/files`, { type: type }); diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index dd81a995b..791da9567 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1241,11 +1241,16 @@ const message = { SFTP: 'SFTP', WebDAV: 'WebDAV', OneDrive: 'Microsoft OneDrive', + isCN: 'Century Internet', + isNotCN: 'International Version', + client_id: 'Client ID', + client_secret: 'Client Secret', + redirect_uri: 'Redirect URL', + onedrive_helper: 'Custom configuration can be referred to in the official documentation', refreshTime: 'Token Refresh Time', refreshStatus: 'Token Refresh Status', backupDir: 'Backup dir', codeWarning: 'The current authorization code format is incorrect, please confirm again!', - isCN: 'Domestic version (not supported at the moment)', code: 'Auth code', codeHelper: 'Please click on the "Acquire" button, then login to OneDrive and copy the content after "code" in the redirected link. Paste it into this input box. For specific instructions, please refer to the official documentation.', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 5685d590d..0328c79de 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1165,11 +1165,16 @@ const message = { SFTP: 'SFTP', WebDAV: 'WebDAV', OneDrive: '微軟 OneDrive', + isCN: '世紀互聯', + isNotCN: '國際版', + client_id: '客戶端 ID', + client_secret: '客戶端密鑰', + redirect_uri: '重定向 URL', + onedrive_helper: '自訂配置可參考官方文件', refreshTime: '令牌刷新時間', refreshStatus: '令牌刷新狀態', codeWarning: '當前授權碼格式錯誤,請重新確認!', backupDir: '備份路徑', - isCN: '國內版 (暫不支持)', code: '授權碼', codeHelper: '請點擊獲取按鈕,然後登錄 OneDrive 復製跳轉鏈接中 code 後面的內容,粘貼到該輸入框中,具體操作可參考官方文檔。', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 9ee113a82..9360e96c0 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1166,11 +1166,16 @@ const message = { SFTP: 'SFTP', WebDAV: 'WebDAV', OneDrive: '微软 OneDrive', + isCN: '世纪互联', + isNotCN: '国际版', + client_id: '客户端 ID', + client_secret: '客户端密钥', + redirect_uri: '重定向 Url', + onedrive_helper: '自定义配置可参考官方文档', refreshTime: '令牌刷新时间', refreshStatus: '令牌刷新状态', codeWarning: '当前授权码格式错误,请重新确认!', backupDir: '备份路径', - isCN: '国内版 (暂不支持)', code: '授权码', codeHelper: '请点击获取按钮,然后登录 OneDrive 复制跳转链接中 code 后面的内容,粘贴到该输入框中,具体操作可参考官方文档。', diff --git a/frontend/src/views/setting/backup-account/index.vue b/frontend/src/views/setting/backup-account/index.vue index 3937b9068..3a3b7e347 100644 --- a/frontend/src/views/setting/backup-account/index.vue +++ b/frontend/src/views/setting/backup-account/index.vue @@ -463,6 +463,7 @@ import webDavDialog from '@/views/setting/backup-account/webdav/index.vue'; import { Backup } from '@/api/interface/backup'; import { ElForm } from 'element-plus'; import i18n from '@/lang'; +import { MsgSuccess } from '@/utils/message'; const data = ref(); const opRef = ref(); @@ -704,6 +705,7 @@ const onOpenDialog = async ( const refreshToken = async () => { await refreshOneDrive(); + MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); search(); }; diff --git a/frontend/src/views/setting/backup-account/onedrive/index.vue b/frontend/src/views/setting/backup-account/onedrive/index.vue index 7ff377f85..ae4d6c00d 100644 --- a/frontend/src/views/setting/backup-account/onedrive/index.vue +++ b/frontend/src/views/setting/backup-account/onedrive/index.vue @@ -9,19 +9,50 @@ ref="formRef" v-loading="loading" label-position="top" - :model="onedriveData.rowData" + :model="oneDriveData.rowData" > - - {{ $t('setting.' + onedriveData.rowData!.type) }} + + {{ $t('setting.' + oneDriveData.rowData!.type) }} - + + {{ $t('setting.isNotCN') }} + {{ $t('setting.isCN') }} + + + {{ $t('setting.cn_onedrive_helper') }} + + {{ $t('firewall.quickJump') }} + + + + + + + + + + +
@@ -30,9 +61,9 @@ :autosize="{ minRows: 3, maxRows: 15 }" type="textarea" clearable - v-model.trim="onedriveData.rowData!.varsJson['code']" + v-model.trim="oneDriveData.rowData!.varsJson['code']" /> - + {{ $t('setting.loadCode') }}
@@ -49,7 +80,7 @@
- +
@@ -94,6 +125,7 @@ function checkDriveCode(rule: any, value: any, callback: any) { } const emit = defineEmits(['search']); +const ondDriveInfo = ref(); interface DialogProps { title: string; @@ -101,29 +133,76 @@ interface DialogProps { } const title = ref(''); const drawerVisible = ref(false); -const onedriveData = ref({ +const oneDriveData = ref({ title: '', }); -const acceptParams = (params: DialogProps): void => { - onedriveData.value = params; - title.value = i18n.global.t('commons.button.' + onedriveData.value.title); +const acceptParams = async (params: DialogProps): Promise => { + oneDriveData.value = params; + oneDriveData.value.rowData.varsJson['isCN'] = oneDriveData.value.rowData.varsJson['isCN'] || false; + title.value = i18n.global.t('commons.button.' + oneDriveData.value.title); drawerVisible.value = true; + const res = await getOneDriveInfo(); + ondDriveInfo.value = res.data; + if (!oneDriveData.value.rowData.id) { + oneDriveData.value.rowData.varsJson = { + isCN: false, + client_id: res.data.client_id, + client_secret: res.data.client_secret, + redirect_uri: res.data.redirect_uri, + }; + } +}; + +const changeFrom = () => { + if (oneDriveData.value.rowData.varsJson['isCN']) { + oneDriveData.value.rowData.varsJson = { + isCN: true, + client_id: '', + client_secret: '', + redirect_uri: '', + }; + } else { + oneDriveData.value.rowData.varsJson = { + isCN: false, + client_id: ondDriveInfo.value.client_id, + client_secret: ondDriveInfo.value.client_secret, + redirect_uri: ondDriveInfo.value.redirect_uri, + }; + } }; const handleClose = () => { emit('search'); drawerVisible.value = false; }; -const jumpAzure = async () => { - const res = await getOneDriveInfo(); - let commonUrl = `response_type=code&client_id=${res.data}&redirect_uri=http://localhost/login/authorized&scope=offline_access+Files.ReadWrite.All+User.Read`; - if (!onedriveData.value.rowData!.varsJson['isCN']) { +const jumpAzure = async (formEl: FormInstance | undefined) => { + if (!formEl) return; + const result = await formEl.validateField('varsJson.client_id', callback); + if (!result) { + return; + } + const result1 = await formEl.validateField('varsJson.redirect_uri', callback); + if (!result1) { + return; + } + let client_id = oneDriveData.value.rowData.varsJson['client_id']; + let redirect_uri = oneDriveData.value.rowData.varsJson['redirect_uri']; + let commonUrl = `response_type=code&client_id=${client_id}&redirect_uri=${redirect_uri}&scope=offline_access+Files.ReadWrite.All+User.Read`; + if (!oneDriveData.value.rowData!.varsJson['isCN']) { window.open('https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' + commonUrl, '_blank'); } else { window.open('https://login.chinacloudapi.cn/common/oauth2/v2.0/authorize?' + commonUrl, '_blank'); } }; +function callback(error: any) { + if (error) { + return error.message; + } else { + return; + } +} + const toDoc = () => { window.open('https://1panel.cn/docs/user_manual/settings/', '_blank', 'noopener,noreferrer'); }; @@ -132,11 +211,11 @@ const onSubmit = async (formEl: FormInstance | undefined) => { if (!formEl) return; formEl.validate(async (valid) => { if (!valid) return; - if (!onedriveData.value.rowData) return; - onedriveData.value.rowData.vars = JSON.stringify(onedriveData.value.rowData!.varsJson); + if (!oneDriveData.value.rowData) return; + oneDriveData.value.rowData.vars = JSON.stringify(oneDriveData.value.rowData!.varsJson); loading.value = true; - if (onedriveData.value.title === 'create') { - await addBackup(onedriveData.value.rowData) + if (oneDriveData.value.title === 'create') { + await addBackup(oneDriveData.value.rowData) .then(() => { loading.value = false; MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); @@ -148,7 +227,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => { }); return; } - await editBackup(onedriveData.value.rowData) + await editBackup(oneDriveData.value.rowData) .then(() => { loading.value = false; MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));