Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OneDrive 增加 Token 刷新机制 #3637

Merged
merged 1 commit into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions backend/app/api/v1/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
helper.SuccessWithData(c, nil)
}

// @Tags Backup Account
// @Summary Refresh OneDrive token
// @Description 刷新 OneDrive token
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/backup/refresh/onedrive [post]
func (b *BaseApi) RefreshOneDriveToken(c *gin.Context) {
backupService.Run()
helper.SuccessWithData(c, nil)
}

// @Tags Backup Account
// @Summary List buckets
// @Description 获取 bucket 列表
Expand Down
122 changes: 87 additions & 35 deletions backend/app/service/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,18 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"strings"
"time"

"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage/client"
fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
Expand Down Expand Up @@ -55,6 +54,8 @@ type IBackupService interface {

AppBackup(db dto.CommonBackup) error
AppRecover(req dto.CommonRecover) error

Run()
}

func NewIBackupService() IBackupService {
Expand Down Expand Up @@ -205,6 +206,9 @@ func (u *BackupService) Create(req dto.BackupOperate) error {
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
}
}
if backup.Type == constant.OneDrive {
StartRefreshOneDriveToken()
}
if err := backupRepo.Create(&backup); err != nil {
return err
}
Expand Down Expand Up @@ -232,6 +236,13 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
}

func (u *BackupService) Delete(id uint) error {
backup, _ := backupRepo.Get(commonRepo.WithByID(id))
if backup.ID == 0 {
return constant.ErrRecordNotFound
}
if backup.Type == constant.OneDrive {
global.Cron.Remove(global.OneDriveCronID)
}
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByBackupID(id))
if len(cronjobs) != 0 {
return buserr.New(constant.ErrBackupInUsed)
Expand Down Expand Up @@ -387,6 +398,15 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
if err := copier.Copy(&item, &account); err != nil {
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
}
if account.Type == constant.OneDrive {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
return dto.BackupInfo{Type: accountType}
}
delete(varMap, "refresh_token")
itemVars, _ := json.Marshal(varMap)
item.Vars = string(itemVars)
}
return item
}
}
Expand All @@ -398,44 +418,23 @@ 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)
}

data := url.Values{}
data.Set("client_id", global.CONF.System.OneDriveID)
data.Set("client_secret", global.CONF.System.OneDriveSc)
data.Set("grant_type", "authorization_code")
data.Set("code", varMap["code"].(string))
data.Set("redirect_uri", constant.OneDriveRedirectURI)
client := &http.Client{}
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
if err != nil {
return fmt.Errorf("new http post client for access token failed, err: %v", err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request for access token failed, err: %v", err)
code, ok := varMap["code"]
if !ok {
return errors.New("no such token in request, please retry!")
}
delete(varMap, "code")
respBody, err := io.ReadAll(resp.Body)
token, refreshToken, err := client.RefreshToken("authorization_code", code.(string))
if err != nil {
return fmt.Errorf("read data from response body failed, err: %v", err)
}
defer resp.Body.Close()

token := map[string]interface{}{}
if err := json.Unmarshal(respBody, &token); err != nil {
return fmt.Errorf("unmarshal data from response body failed, err: %v", err)
}
accessToken, ok := token["refresh_token"].(string)
if !ok {
return errors.New("no such access token in response")
return err
}

itemVars, err := json.Marshal(varMap)
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)
if err != nil {
return fmt.Errorf("json marshal var map failed, err: %v", err)
}
backup.Credential = accessToken
backup.Vars = string(itemVars)
return nil
}
Expand Down Expand Up @@ -521,3 +520,56 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
targetPath := strings.TrimPrefix(path.Join(backup.BackupPath, "test/1panel"), "/")
return client.Upload(fileItem, targetPath)
}

func StartRefreshOneDriveToken() {
service := NewIBackupService()
oneDriveCronID, err := global.Cron.AddJob("0 * * * *", service)
if err != nil {
global.LOG.Errorf("can not add OneDrive corn job: %s", err.Error())
return
}
global.OneDriveCronID = oneDriveCronID
}

func (u *BackupService) Run() {
var backupItem model.BackupAccount
_ = global.DB.Where("`type` = ?", "OneDrive").First(&backupItem)
if backupItem.ID == 0 {
return
}
if len(backupItem.Credential) == 0 {
global.LOG.Error("OneDrive configuration lacks token information, please rebind.")
return
}
global.LOG.Info("start to refresh token of OneDrive ...")
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil {
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))
varMap["refresh_status"] = constant.StatusSuccess
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
if err != nil {
varMap["refresh_status"] = constant.StatusFailed
varMap["refresh_msg"] = err.Error()
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
return
}
varMap["refresh_token"] = refreshToken

varsItem, _ := json.Marshal(varMap)
_ = global.DB.Model(&model.BackupAccount{}).
Where("id = ?", backupItem.ID).
Updates(map[string]interface{}{
"credential": token,
"vars": varsItem,
}).Error
global.LOG.Info("Successfully refreshed OneDrive token.")
}
2 changes: 1 addition & 1 deletion backend/app/service/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,6 @@ func StartMonitor(removeBefore bool, interval string) error {
go service.saveIODataToDB(ctx, float64(intervalItem))
go service.saveNetDataToDB(ctx, float64(intervalItem))

global.MonitorCronID = int(monitorID)
global.MonitorCronID = monitorID
return nil
}
6 changes: 6 additions & 0 deletions backend/cron/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ func Run() {
if _, err := global.Cron.AddJob("@daily", job.NewAppStoreJob()); err != nil {
global.LOG.Errorf("can not add appstore corn job: %s", err.Error())
}

var backup model.BackupAccount
_ = global.DB.Where("type = ?", "OneDrive").Find(&backup).Error
if backup.ID != 0 {
service.StartRefreshOneDriveToken()
}
global.Cron.Start()

var cronJobs []model.Cronjob
Expand Down
5 changes: 3 additions & 2 deletions backend/global/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var (
CACHE *badger_db.Cache
Viper *viper.Viper

Cron *cron.Cron
MonitorCronID int
Cron *cron.Cron
MonitorCronID cron.EntryID
OneDriveCronID cron.EntryID
)
1 change: 1 addition & 0 deletions backend/init/migration/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func Init() {
migrations.AddTableDatabasePostgresql,
migrations.AddPostgresqlSuperUser,
migrations.UpdateCronjobWithWebsite,
migrations.UpdateOneDriveToken,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
Expand Down
61 changes: 61 additions & 0 deletions backend/init/migration/migrations/v_1_9.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package migrations

import (
"encoding/base64"
"encoding/json"
"time"

"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage/client"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
Expand Down Expand Up @@ -188,3 +194,58 @@ var UpdateCronjobWithWebsite = &gormigrate.Migration{
return nil
},
}

var UpdateOneDriveToken = &gormigrate.Migration{
ID: "20240117-update-onedrive-token",
Migrate: func(tx *gorm.DB) error {
var (
backup model.BackupAccount
clientSetting model.Setting
secretSetting model.Setting
)
_ = tx.Where("type = ?", "OneDrive").First(&backup).Error
if backup.ID == 0 {
return nil
}
if len(backup.Credential) == 0 {
global.LOG.Error("OneDrive configuration lacks token information, please rebind.")
return nil
}

_ = tx.Where("key = ?", "OneDriveID").First(&clientSetting).Error
if clientSetting.ID == 0 {
global.LOG.Error("system configuration lacks clientID information, please retry.")
return nil
}
_ = tx.Where("key = ?", "OneDriveSc").First(&secretSetting).Error
if secretSetting.ID == 0 {
global.LOG.Error("system configuration lacks clientID information, please retry.")
return nil
}
idItem, _ := base64.StdEncoding.DecodeString(clientSetting.Value)
global.CONF.System.OneDriveID = string(idItem)
scItem, _ := base64.StdEncoding.DecodeString(secretSetting.Value)
global.CONF.System.OneDriveSc = string(scItem)

varMap := make(map[string]interface{})
token, refreshToken, err := client.RefreshToken("refresh_token", backup.Credential)
varMap["refresh_status"] = constant.StatusSuccess
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
if err != nil {
varMap["refresh_msg"] = err.Error()
varMap["refresh_status"] = constant.StatusFailed
}
varMap["refresh_token"] = refreshToken
itemVars, _ := json.Marshal(varMap)
if err := tx.Model(&model.BackupAccount{}).
Where("id = ?", backup.ID).
Updates(map[string]interface{}{
"credential": token,
"vars": string(itemVars),
}).Error; err != nil {
return err
}

return nil
},
}
1 change: 1 addition & 0 deletions backend/router/ro_setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
settingRouter.GET("/backup/search", baseApi.ListBackup)
settingRouter.GET("/backup/onedrive", baseApi.LoadOneDriveInfo)
settingRouter.POST("/backup/backup", baseApi.Backup)
settingRouter.POST("/backup/refresh/onedrive", baseApi.RefreshOneDriveToken)
settingRouter.POST("/backup/recover", baseApi.Recover)
settingRouter.POST("/backup/recover/byupload", baseApi.RecoverByUpload)
settingRouter.POST("/backup/search/files", baseApi.LoadFilesFromBackup)
Expand Down
Loading
Loading