feat: OneDrive 支持世纪互联 (#3655)

This commit is contained in:
ssongliu 2024-01-19 23:06:41 +08:00 committed by GitHub
parent 24579edad6
commit 6bc4cfc8ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 266 additions and 68 deletions

View File

@ -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

View File

@ -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"`
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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": [

View File

@ -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": [

View File

@ -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: 获取备份账号列表

View File

@ -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;

View File

@ -101,7 +101,7 @@ export const getBackupList = () => {
return http.get<Array<Backup.BackupInfo>>(`/settings/backup/search`);
};
export const getOneDriveInfo = () => {
return http.get<string>(`/settings/backup/onedrive`);
return http.get<Backup.OneDriveInfo>(`/settings/backup/onedrive`);
};
export const getFilesFromBackup = (type: string) => {
return http.post<Array<any>>(`/settings/backup/search/files`, { type: type });

View File

@ -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.',

View File

@ -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 後面的內容粘貼到該輸入框中具體操作可參考官方文檔',

View File

@ -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 后面的内容粘贴到该输入框中具体操作可参考官方文档',

View File

@ -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();
};

View File

@ -9,19 +9,50 @@
ref="formRef"
v-loading="loading"
label-position="top"
:model="onedriveData.rowData"
:model="oneDriveData.rowData"
>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + onedriveData.rowData!.type) }}</el-tag>
<el-form-item :label="$t('commons.table.type')" prop="type">
<el-tag>{{ $t('setting.' + oneDriveData.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item>
<el-checkbox
disabled
v-model="onedriveData.rowData!.varsJson['isCN']"
:label="$t('setting.isCN')"
/>
<el-radio-group v-model="oneDriveData.rowData!.varsJson['isCN']" @change="changeFrom">
<el-radio-button :label="false">{{ $t('setting.isNotCN') }}</el-radio-button>
<el-radio-button :label="true">{{ $t('setting.isCN') }}</el-radio-button>
</el-radio-group>
<span class="input-help">
{{ $t('setting.cn_onedrive_helper') }}
<el-link
style="font-size: 12px; margin-left: 5px"
icon="Position"
@click="toDoc()"
type="primary"
>
{{ $t('firewall.quickJump') }}
</el-link>
</span>
</el-form-item>
<el-form-item
:label="$t('setting.client_id')"
prop="varsJson.client_id"
:rules="Rules.requiredInput"
>
<el-input v-model.trim="oneDriveData.rowData!.varsJson['client_id']" />
</el-form-item>
<el-form-item
:label="$t('setting.client_secret')"
prop="varsJson.client_secret"
:rules="Rules.requiredInput"
>
<el-input v-model.trim="oneDriveData.rowData!.varsJson['client_secret']" />
</el-form-item>
<el-form-item
:label="$t('setting.redirect_uri')"
prop="varsJson.redirect_uri"
:rules="Rules.requiredInput"
>
<el-input v-model.trim="oneDriveData.rowData!.varsJson['redirect_uri']" />
</el-form-item>
<el-form-item :label="$t('setting.code')" prop="varsJson.code" :rules="rules.driveCode">
<div style="width: 100%">
@ -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']"
/>
<el-button class="append-button" @click="jumpAzure">
<el-button class="append-button" @click="jumpAzure(formRef)">
{{ $t('setting.loadCode') }}
</el-button>
</div>
@ -49,7 +80,7 @@
</span>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
<el-input clearable v-model.trim="onedriveData.rowData!.backupPath" placeholder="/1panel" />
<el-input clearable v-model.trim="oneDriveData.rowData!.backupPath" placeholder="/1panel" />
</el-form-item>
</el-col>
</el-row>
@ -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<string>('');
const drawerVisible = ref(false);
const onedriveData = ref<DialogProps>({
const oneDriveData = ref<DialogProps>({
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<void> => {
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'));