Skip to content

Commit

Permalink
feat: OneDrive 增加 Token 刷新机制 (1Panel-dev#3637)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssongliu authored Jan 19, 2024
1 parent 0f8eadb commit 4277990
Show file tree
Hide file tree
Showing 16 changed files with 342 additions and 161 deletions.
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

0 comments on commit 4277990

Please sign in to comment.