From 5f1df76dbf359194eea575cc1d9805273fd3baf9 Mon Sep 17 00:00:00 2001 From: link Date: Fri, 18 Aug 2023 15:46:29 +0800 Subject: [PATCH] Community (#1341) --- .github/workflows/push_test_server.yml | 8 +- .github/workflows/release.yml | 6 + .goreleaser.yaml | 20 ++- common/constants.go | 4 +- drivers/all.go | 1 + drivers/dropbox/drive.go | 3 + drivers/dropbox/meta.go | 11 +- drivers/dropbox/util.go | 14 ++ drivers/google_drive/drive.go | 3 + drivers/google_drive/meta.go | 15 +- drivers/google_drive/util.go | 15 ++ drivers/onedrive/drive.go | 70 ++++++++++ drivers/onedrive/meta.go | 67 +++++++++ drivers/onedrive/util.go | 182 +++++++++++++++++++++++++ internal/driver/driver.go | 6 +- model/drive.go | 7 + route/v1/driver.go | 28 +++- route/v1/recover.go | 129 ++++++++++++++---- route/v2.go | 119 ++++++++++++++++ route/v2/health.go | 1 - 20 files changed, 654 insertions(+), 55 deletions(-) create mode 100644 drivers/onedrive/drive.go create mode 100644 drivers/onedrive/meta.go create mode 100644 drivers/onedrive/util.go create mode 100644 model/drive.go diff --git a/.github/workflows/push_test_server.yml b/.github/workflows/push_test_server.yml index d43e4aa..d328838 100644 --- a/.github/workflows/push_test_server.yml +++ b/.github/workflows/push_test_server.yml @@ -21,7 +21,7 @@ jobs: - name: git global run: sudo git config --global --add safe.directory '*' - name: set version - run: sudo git tag v99.99.99-alpha + run: sudo git tag v00.00.00-alpha - name: Fetch all tags run: sudo git fetch --force --tags @@ -50,6 +50,12 @@ jobs: args: release --rm-dist --snapshot env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GoogleID: ${{ secrets.GoogleID }} + GoogleSecret: ${{ secrets.GoogleSecret }} + DropboxKey: ${{ secrets.DropboxKey }} + DropboxSecret: ${{ secrets.DropboxSecret }} + OneDriveID: ${{ secrets.OneDriveID }} + OneDriveSecret: ${{ secrets.OneDriveSecret }} # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10266b7..5c68a3a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,6 +42,12 @@ jobs: args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GoogleID: ${{ secrets.GoogleID }} + GoogleSecret: ${{ secrets.GoogleSecret }} + DropboxKey: ${{ secrets.DropboxKey }} + DropboxSecret: ${{ secrets.DropboxSecret }} + OneDriveID: ${{ secrets.OneDriveID }} + OneDriveSecret: ${{ secrets.OneDriveSecret }} # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index f4f67c7..1bbebac 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -19,6 +19,12 @@ builds: ldflags: - -X main.commit={{.Commit}} - -X main.date={{.Date}} + - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}} + - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}} + - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}} + - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}} + - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}} + - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}} - -s - -w - -extldflags "-static" @@ -40,6 +46,12 @@ builds: ldflags: - -X main.commit={{.Commit}} - -X main.date={{.Date}} + - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}} + - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}} + - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}} + - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}} + - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}} + - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}} - -s - -w - -extldflags "-static" @@ -61,6 +73,12 @@ builds: ldflags: - -X main.commit={{.Commit}} - -X main.date={{.Date}} + - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}} + - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}} + - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}} + - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}} + - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}} + - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}} - -s - -w - -extldflags "-static" @@ -143,7 +161,7 @@ builds: goarm: - "7" archives: - - name_template: >- + - name_template: >- {{ .Os }}-{{- if eq .Arch "arm" }}arm-7{{- else }}{{ .Arch }}{{- end }}-{{ .ProjectName }}-v{{ .Version }} id: casaos builds: diff --git a/common/constants.go b/common/constants.go index 1f5425d..e7696ab 100644 --- a/common/constants.go +++ b/common/constants.go @@ -2,7 +2,7 @@ package common const ( SERVICENAME = "casaos" - VERSION = "0.4.4" - BODY = "" + VERSION = "0.4.4.1" + BODY = " " RANW_NAME = "IceWhale-RemoteAccess" ) diff --git a/drivers/all.go b/drivers/all.go index 3cdfe84..57a885f 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -3,6 +3,7 @@ package drivers import ( _ "github.com/IceWhaleTech/CasaOS/drivers/dropbox" _ "github.com/IceWhaleTech/CasaOS/drivers/google_drive" + _ "github.com/IceWhaleTech/CasaOS/drivers/onedrive" ) // All do nothing,just for import diff --git a/drivers/dropbox/drive.go b/drivers/dropbox/drive.go index fde830a..852c5ee 100644 --- a/drivers/dropbox/drive.go +++ b/drivers/dropbox/drive.go @@ -96,5 +96,8 @@ func (d *Dropbox) Remove(ctx context.Context, obj model.Obj) error { func (d *Dropbox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { return nil } +func (d *Dropbox) GetInfo(ctx context.Context) (string, string, string, error) { + return "", "", "", nil +} var _ driver.Driver = (*Dropbox)(nil) diff --git a/drivers/dropbox/meta.go b/drivers/dropbox/meta.go index 24d535d..a0f39a4 100644 --- a/drivers/dropbox/meta.go +++ b/drivers/dropbox/meta.go @@ -2,12 +2,9 @@ package dropbox import ( "github.com/IceWhaleTech/CasaOS/internal/driver" - "github.com/IceWhaleTech/CasaOS/internal/op" ) const ICONURL = "./img/driver/Dropbox.svg" -const APPKEY = "tciqajyazzdygt9" -const APPSECRET = "e7gtmv441cwdf0n" type Addition struct { driver.RootID @@ -15,7 +12,7 @@ type Addition struct { AppKey string `json:"app_key" type:"string" default:"tciqajyazzdygt9" omit:"true"` AppSecret string `json:"app_secret" type:"string" default:"e7gtmv441cwdf0n" omit:"true"` OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"` - AuthUrl string `json:"auth_url" type:"string" default:"https://www.dropbox.com/oauth2/authorize?client_id=tciqajyazzdygt9&redirect_uri=https://cloudoauth.files.casaos.app&response_type=code&token_access_type=offline&state=${HOST}%2Fv1%2Frecover%2FDropbox&&force_reapprove=true&force_reauthentication=true"` + AuthUrl string `json:"auth_url" type:"string" default:""` Icon string `json:"icon" type:"string" default:"./img/driver/Dropbox.svg"` Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"` } @@ -25,9 +22,3 @@ var config = driver.Config{ OnlyProxy: true, DefaultRoot: "root", } - -func init() { - op.RegisterDriver(func() driver.Driver { - return &Dropbox{} - }) -} diff --git a/drivers/dropbox/util.go b/drivers/dropbox/util.go index 7cdf6de..b799cde 100644 --- a/drivers/dropbox/util.go +++ b/drivers/dropbox/util.go @@ -10,6 +10,11 @@ import ( "go.uber.org/zap" ) +var ( + app_key = "private build" + app_secret = "private build" +) + func (d *Dropbox) getRefreshToken() error { url := "https://api.dropbox.com/oauth2/token" var resp base.TokenResp @@ -100,3 +105,12 @@ func (d *Dropbox) getFiles(path string) ([]File, error) { return res, nil } +func GetConfig() Dropbox { + dp := Dropbox{} + dp.RootFolderID = "" + dp.AuthUrl = "https://www.dropbox.com/oauth2/authorize?client_id=" + app_key + "&redirect_uri=https://cloudoauth.files.casaos.app&response_type=code&token_access_type=offline&state=${HOST}%2Fv2%2Frecover%2FDropbox&&force_reapprove=true&force_reauthentication=true" + dp.AppKey = app_key + dp.AppSecret = app_secret + dp.Icon = "./img/driver/Dropbox.svg" + return dp +} diff --git a/drivers/google_drive/drive.go b/drivers/google_drive/drive.go index 72babbd..9fcca30 100644 --- a/drivers/google_drive/drive.go +++ b/drivers/google_drive/drive.go @@ -80,6 +80,9 @@ func (d *GoogleDrive) GetUserInfo(ctx context.Context) (string, error) { return user.User.EmailAddress, nil } +func (d *GoogleDrive) GetInfo(ctx context.Context) (string, string, string, error) { + return "", "", "", nil +} func (d *GoogleDrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { data := base.Json{ "name": dirName, diff --git a/drivers/google_drive/meta.go b/drivers/google_drive/meta.go index f771966..c54729f 100644 --- a/drivers/google_drive/meta.go +++ b/drivers/google_drive/meta.go @@ -2,22 +2,19 @@ package google_drive import ( "github.com/IceWhaleTech/CasaOS/internal/driver" - "github.com/IceWhaleTech/CasaOS/internal/op" ) const ICONURL = "./img/driver/GoogleDrive.svg" -const CLIENTID = "921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com" -const CLIENTSECRET = "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" type Addition struct { driver.RootID RefreshToken string `json:"refresh_token" required:"true" omit:"true"` OrderBy string `json:"order_by" type:"string" help:"such as: folder,name,modifiedTime" omit:"true"` OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"` - ClientID string `json:"client_id" required:"true" default:"921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com" omit:"true"` - ClientSecret string `json:"client_secret" required:"true" default:"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" omit:"true"` + ClientID string `json:"client_id" required:"true" default:"" omit:"true"` + ClientSecret string `json:"client_secret" required:"true" default:"" omit:"true"` ChunkSize int64 `json:"chunk_size" type:"number" help:"chunk size while uploading (unit: MB)" omit:"true"` - AuthUrl string `json:"auth_url" type:"string" default:"https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?response_type=code&client_id=921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&approval_prompt=force&state=${HOST}%2Fv1%2Frecover%2FGoogleDrive&service=lso&o2v=1&flowName=GeneralOAuthFlow"` + AuthUrl string `json:"auth_url" type:"string" default:""` Icon string `json:"icon" type:"string" default:"./img/driver/GoogleDrive.svg"` Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"` } @@ -27,9 +24,3 @@ var config = driver.Config{ OnlyProxy: true, DefaultRoot: "root", } - -func init() { - op.RegisterDriver(func() driver.Driver { - return &GoogleDrive{} - }) -} diff --git a/drivers/google_drive/util.go b/drivers/google_drive/util.go index a3b443a..a67cf70 100644 --- a/drivers/google_drive/util.go +++ b/drivers/google_drive/util.go @@ -16,6 +16,11 @@ import ( "go.uber.org/zap" ) +var ( + client_id = "private build" + client_secret = "private build" +) + // do others that not defined in Driver interface func (d *GoogleDrive) getRefreshToken() error { @@ -150,3 +155,13 @@ func (d *GoogleDrive) chunkUpload(ctx context.Context, stream model.FileStreamer } return nil } +func GetConfig() GoogleDrive { + config := GoogleDrive{} + config.ClientID = client_id + config.ClientSecret = client_secret + config.RootFolderID = "root" + config.AuthUrl = "https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?response_type=code&client_id=" + client_id + "&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&approval_prompt=force&state=${HOST}%2Fv2%2Frecover%2FGoogleDrive&service=lso&o2v=1&flowName=GeneralOAuthFlow" + config.Icon = "./img/driver/GoogleDrive.svg" + + return config +} diff --git a/drivers/onedrive/drive.go b/drivers/onedrive/drive.go new file mode 100644 index 0000000..02b6648 --- /dev/null +++ b/drivers/onedrive/drive.go @@ -0,0 +1,70 @@ +package onedrive + +import ( + "context" + "fmt" + "net/http" + "strconv" + + "github.com/IceWhaleTech/CasaOS-Common/utils/logger" + + "github.com/IceWhaleTech/CasaOS/internal/driver" + "github.com/IceWhaleTech/CasaOS/model" + "go.uber.org/zap" +) + +type Onedrive struct { + model.StorageA + Addition + AccessToken string +} + +func (d *Onedrive) Config() driver.Config { + return config +} + +func (d *Onedrive) GetAddition() driver.Additional { + return &d.Addition +} +func (d *Onedrive) Init(ctx context.Context) error { + if d.ChunkSize < 1 { + d.ChunkSize = 5 + } + if len(d.RefreshToken) == 0 { + return d.getRefreshToken() + } + return d.refreshToken() +} +func (d *Onedrive) GetUserInfo(ctx context.Context) (string, error) { + return "", nil +} +func (d *Onedrive) GetInfo(ctx context.Context) (string, string, string, error) { + url := d.GetMetaUrl(false, "/") + user := Info{} + resp, err := d.Request(url, http.MethodGet, nil, &user) + if err != nil { + return "", "", "", err + } + + logger.Info("resp", zap.Any("resp", resp)) + return user.LastModifiedBy.User.DisplayName, user.ParentReference.DriveID, user.ParentReference.DriveType, nil +} + +func (d *Onedrive) GetSpaceSize(ctx context.Context) (used string, total string, err error) { + host := onedriveHostMap[d.Region] + url := fmt.Sprintf("%s/v1.0/me/drive/quota", host.Api) + size := About{} + resp, err := d.Request(url, http.MethodGet, nil, &size) + if err != nil { + return used, total, err + } + logger.Info("resp", zap.Any("resp", resp)) + used = strconv.Itoa(size.Used) + total = strconv.Itoa(size.Total) + return +} +func (d *Onedrive) Drop(ctx context.Context) error { + return nil +} + +var _ driver.Driver = (*Onedrive)(nil) diff --git a/drivers/onedrive/meta.go b/drivers/onedrive/meta.go new file mode 100644 index 0000000..2b56de5 --- /dev/null +++ b/drivers/onedrive/meta.go @@ -0,0 +1,67 @@ +package onedrive + +import ( + "github.com/IceWhaleTech/CasaOS/internal/driver" +) + +type Host struct { + Oauth string + Api string +} + +type TokenErr struct { + Error string `json:"error"` + ErrorDescription string `json:"error_description"` +} + +type RespErr struct { + Error struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"error"` +} +type Addition struct { + Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"` + IsSharepoint bool `json:"is_sharepoint"` + ClientID string `json:"client_id" required:"true"` + ClientSecret string `json:"client_secret" required:"true"` + RedirectUri string `json:"redirect_uri" required:"true" default:""` + RefreshToken string `json:"refresh_token" required:"true"` + SiteId string `json:"site_id"` + ChunkSize int64 `json:"chunk_size" type:"number" default:"5"` + RootFolderID string `json:"root_folder_id"` + AuthUrl string `json:"auth_url" type:"string" default:""` + Icon string `json:"icon" type:"string" default:""` + Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"` +} +type About struct { + Total int `json:"total"` + Used int `json:"used"` + State string `json:"state"` +} + +type Info struct { + LastModifiedBy struct { + Application struct { + DisplayName string `json:"displayName"` + ID string `json:"id"` + } `json:"application"` + Device struct { + ID string `json:"id"` + } `json:"device"` + User struct { + DisplayName string `json:"displayName"` + ID string `json:"id"` + } `json:"user"` + } `json:"lastModifiedBy"` + ParentReference struct { + DriveID string `json:"driveId"` + DriveType string `json:"driveType"` + } `json:"parentReference"` +} + +var config = driver.Config{ + Name: "Onedrive", + LocalSort: true, + DefaultRoot: "/", +} diff --git a/drivers/onedrive/util.go b/drivers/onedrive/util.go new file mode 100644 index 0000000..e6ceeb7 --- /dev/null +++ b/drivers/onedrive/util.go @@ -0,0 +1,182 @@ +package onedrive + +import ( + "errors" + "fmt" + "net/url" + "strings" + + "github.com/IceWhaleTech/CasaOS-Common/utils/logger" + "github.com/IceWhaleTech/CasaOS/drivers/base" + "go.uber.org/zap" +) + +var ( + client_id = "private build" + client_secret = "private build" +) +var onedriveHostMap = map[string]Host{ + "global": { + Oauth: "https://login.microsoftonline.com", + Api: "https://graph.microsoft.com", + }, + "cn": { + Oauth: "https://login.chinacloudapi.cn", + Api: "https://microsoftgraph.chinacloudapi.cn", + }, + "us": { + Oauth: "https://login.microsoftonline.us", + Api: "https://graph.microsoft.us", + }, + "de": { + Oauth: "https://login.microsoftonline.de", + Api: "https://graph.microsoft.de", + }, +} + +func EncodePath(path string, all ...bool) string { + seg := strings.Split(path, "/") + toReplace := []struct { + Src string + Dst string + }{ + {Src: "%", Dst: "%25"}, + {"%", "%25"}, + {"?", "%3F"}, + {"#", "%23"}, + } + for i := range seg { + if len(all) > 0 && all[0] { + seg[i] = url.PathEscape(seg[i]) + } else { + for j := range toReplace { + seg[i] = strings.ReplaceAll(seg[i], toReplace[j].Src, toReplace[j].Dst) + } + } + } + return strings.Join(seg, "/") +} +func (d *Onedrive) GetMetaUrl(auth bool, path string) string { + host := onedriveHostMap[d.Region] + path = EncodePath(path, true) + if auth { + return host.Oauth + } + if d.IsSharepoint { + if path == "/" || path == "\\" { + return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, d.SiteId) + } else { + return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, d.SiteId, path) + } + } else { + if path == "/" || path == "\\" { + return fmt.Sprintf("%s/v1.0/me/drive/root", host.Api) + } else { + return fmt.Sprintf("%s/v1.0/me/drive/root:%s:", host.Api, path) + } + } +} + +func (d *Onedrive) refreshToken() error { + var err error + for i := 0; i < 3; i++ { + err = d._refreshToken() + if err == nil { + break + } + } + return err +} + +func (d *Onedrive) getRefreshToken() error { + url := d.GetMetaUrl(true, "") + "/common/oauth2/v2.0/token" + var resp base.TokenResp + var e TokenErr + + res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{ + "grant_type": "authorization_code", + "client_id": d.ClientID, + "client_secret": d.ClientSecret, + "code": d.Code, + "redirect_uri": d.RedirectUri, + }).Post(url) + if err != nil { + return err + } + logger.Info("get refresh token", zap.String("res", res.String())) + if e.Error != "" { + return fmt.Errorf("%s", e.ErrorDescription) + } + if resp.RefreshToken == "" { + return errors.New("refresh token is empty") + } + d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken + return nil +} + +func (d *Onedrive) _refreshToken() error { + url := d.GetMetaUrl(true, "") + "/common/oauth2/v2.0/token" + var resp base.TokenResp + var e TokenErr + + res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{ + "grant_type": "refresh_token", + "client_id": d.ClientID, + "client_secret": d.ClientSecret, + "redirect_uri": d.RedirectUri, + "refresh_token": d.RefreshToken, + }).Post(url) + if err != nil { + return err + } + logger.Info("get refresh token", zap.String("res", res.String())) + if e.Error != "" { + return fmt.Errorf("%s", e.ErrorDescription) + } + if resp.RefreshToken == "" { + return errors.New("refresh token is empty") + } + d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken + return nil +} + +func (d *Onedrive) Request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { + req := base.RestyClient.R() + req.SetHeader("Authorization", "Bearer "+d.AccessToken) + if callback != nil { + callback(req) + } + if resp != nil { + req.SetResult(resp) + } + var e RespErr + req.SetError(&e) + res, err := req.Execute(method, url) + if err != nil { + return nil, err + } + if e.Error.Code != "" { + if e.Error.Code == "InvalidAuthenticationToken" { + err = d.refreshToken() + if err != nil { + return nil, err + } + return d.Request(url, method, callback, resp) + } + return nil, errors.New(e.Error.Message) + } + return res.Body(), nil +} + +func GetConfig() Onedrive { + config := Onedrive{} + config.ClientID = client_id + config.ClientSecret = client_secret + config.RootFolderID = "/" + config.AuthUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id + "&response_type=code&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app%2Fcallback&scope=offline_access+files.readwrite.all&state=${HOST}%2Fv2%2Frecover%2FOnedrive" + config.Icon = "./img/driver/OneDrive.svg" + config.Region = "global" + config.RedirectUri = "https://cloudoauth.files.casaos.app" + + return config +} diff --git a/internal/driver/driver.go b/internal/driver/driver.go index 7519b5e..c5576fb 100644 --- a/internal/driver/driver.go +++ b/internal/driver/driver.go @@ -34,14 +34,16 @@ type Reader interface { // List files in the path // if identify files by path, need to set ID with path,like path.Join(dir.GetID(), obj.GetName()) // if identify files by id, need to set ID with corresponding id - List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) + // List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) // Link get url/filepath/reader of file - Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) + // Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) } type User interface { // GetRoot get root directory of user GetUserInfo(ctx context.Context) (string, error) + GetInfo(ctx context.Context) (string, string, string, error) } + type Getter interface { GetRoot(ctx context.Context) (model.Obj, error) } diff --git a/model/drive.go b/model/drive.go new file mode 100644 index 0000000..7f23d13 --- /dev/null +++ b/model/drive.go @@ -0,0 +1,7 @@ +package model + +type Drive struct { + Name string `json:"name"` + Icon string `json:"icon"` + AuthUrl string `json:"auth_url"` +} diff --git a/route/v1/driver.go b/route/v1/driver.go index 88c96d8..aeb03e9 100644 --- a/route/v1/driver.go +++ b/route/v1/driver.go @@ -1,12 +1,34 @@ package v1 import ( - "github.com/IceWhaleTech/CasaOS-Common/model" "github.com/IceWhaleTech/CasaOS-Common/utils/common_err" - "github.com/IceWhaleTech/CasaOS/internal/op" + "github.com/IceWhaleTech/CasaOS/drivers/dropbox" + "github.com/IceWhaleTech/CasaOS/drivers/google_drive" + "github.com/IceWhaleTech/CasaOS/drivers/onedrive" + "github.com/IceWhaleTech/CasaOS/model" "github.com/gin-gonic/gin" ) func ListDriverInfo(c *gin.Context) { - c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: op.GetDriverInfoMap()}) + list := []model.Drive{} + + google := google_drive.GetConfig() + list = append(list, model.Drive{ + Name: "Google Drive", + Icon: google.Icon, + AuthUrl: google.AuthUrl, + }) + dp := dropbox.GetConfig() + list = append(list, model.Drive{ + Name: "Dropbox", + Icon: dp.Icon, + AuthUrl: dp.AuthUrl, + }) + od := onedrive.GetConfig() + list = append(list, model.Drive{ + Name: "OneDrive", + Icon: od.Icon, + AuthUrl: od.AuthUrl, + }) + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: list}) } diff --git a/route/v1/recover.go b/route/v1/recover.go index e2f554e..3679469 100644 --- a/route/v1/recover.go +++ b/route/v1/recover.go @@ -8,6 +8,7 @@ import ( "github.com/IceWhaleTech/CasaOS-Common/utils/logger" "github.com/IceWhaleTech/CasaOS/drivers/dropbox" "github.com/IceWhaleTech/CasaOS/drivers/google_drive" + "github.com/IceWhaleTech/CasaOS/drivers/onedrive" "github.com/IceWhaleTech/CasaOS/service" "github.com/gin-gonic/gin" "go.uber.org/zap" @@ -20,23 +21,17 @@ func GetRecoverStorage(c *gin.Context) { currentDate := time.Now().UTC().Format("2006-01-02") notify := make(map[string]interface{}) if t == "GoogleDrive" { - add := google_drive.Addition{} - add.Code = c.Query("code") - if len(add.Code) == 0 { + google_drive := google_drive.GetConfig() + google_drive.Code = c.Query("code") + if len(google_drive.Code) == 0 { c.String(200, `

Code cannot be empty

`) notify["status"] = "fail" notify["message"] = "Code cannot be empty" - logger.Error("Then code is empty: ", zap.String("code", add.Code), zap.Any("name", "google_drive")) + logger.Error("Then code is empty: ", zap.String("code", google_drive.Code), zap.Any("name", "google_drive")) service.MyService.Notify().SendNotify("casaos:file:recover", notify) return } - add.RootFolderID = "root" - add.ClientID = google_drive.CLIENTID - add.ClientSecret = google_drive.CLIENTSECRET - - var google_drive google_drive.GoogleDrive - google_drive.Addition = add err := google_drive.Init(c) if err != nil { c.String(200, `

Initialization failure:`+err.Error()+`

`) @@ -93,8 +88,8 @@ func GetRecoverStorage(c *gin.Context) { //username = fileutil.NameAccumulation(username, "/mnt") username += "_google_drive_" + strconv.FormatInt(time.Now().Unix(), 10) - dmap["client_id"] = add.ClientID - dmap["client_secret"] = add.ClientSecret + dmap["client_id"] = google_drive.ClientID + dmap["client_secret"] = google_drive.ClientSecret dmap["scope"] = "drive" dmap["mount_point"] = "/mnt/" + username dmap["token"] = `{"access_token":"` + google_drive.AccessToken + `","token_type":"Bearer","refresh_token":"` + google_drive.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*1).Add(time.Minute*50).Format("15:04:05") + `Z"}` @@ -106,21 +101,17 @@ func GetRecoverStorage(c *gin.Context) { notify["driver"] = "GoogleDrive" service.MyService.Notify().SendNotify("casaos:file:recover", notify) } else if t == "Dropbox" { - add := dropbox.Addition{} - add.Code = c.Query("code") - if len(add.Code) == 0 { + dropbox := dropbox.GetConfig() + dropbox.Code = c.Query("code") + if len(dropbox.Code) == 0 { c.String(200, `

Code cannot be empty

`) notify["status"] = "fail" notify["message"] = "Code cannot be empty" - logger.Error("Then code is empty error: ", zap.String("code", add.Code), zap.Any("name", "dropbox")) + logger.Error("Then code is empty error: ", zap.String("code", dropbox.Code), zap.Any("name", "dropbox")) service.MyService.Notify().SendNotify("casaos:file:recover", notify) return } - add.RootFolderID = "" - add.AppKey = dropbox.APPKEY - add.AppSecret = dropbox.APPSECRET - var dropbox dropbox.Dropbox - dropbox.Addition = add + err := dropbox.Init(c) if err != nil { c.String(200, `

Initialization failure:`+err.Error()+`

`) @@ -176,8 +167,8 @@ func GetRecoverStorage(c *gin.Context) { } username += "_dropbox_" + strconv.FormatInt(time.Now().Unix(), 10) - dmap["client_id"] = add.AppKey - dmap["client_secret"] = add.AppSecret + dmap["client_id"] = dropbox.AppKey + dmap["client_secret"] = dropbox.AppSecret dmap["token"] = `{"access_token":"` + dropbox.AccessToken + `","token_type":"bearer","refresh_token":"` + dropbox.Addition.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*3).Add(time.Minute*50).Format("15:04:05") + `.780385354Z"}` dmap["mount_point"] = "/mnt/" + username // data.SetValue(username, "type", "dropbox") @@ -199,6 +190,98 @@ func GetRecoverStorage(c *gin.Context) { notify["message"] = "Success" notify["driver"] = "Dropbox" service.MyService.Notify().SendNotify("casaos:file:recover", notify) + } else if t == "Onedrive" { + onedrive := onedrive.GetConfig() + onedrive.Code = c.Query("code") + if len(onedrive.Code) == 0 { + c.String(200, `

Code cannot be empty

`) + notify["status"] = "fail" + notify["message"] = "Code cannot be empty" + logger.Error("Then code is empty error: ", zap.String("code", onedrive.Code), zap.Any("name", "onedrive")) + service.MyService.Notify().SendNotify("casaos:file:recover", notify) + return + } + + err := onedrive.Init(c) + if err != nil { + c.String(200, `

Initialization failure:`+err.Error()+`

`) + notify["status"] = "fail" + notify["message"] = "Initialization failure" + logger.Error("Then init error: ", zap.Error(err), zap.Any("name", "onedrive")) + service.MyService.Notify().SendNotify("casaos:file:recover", notify) + return + } + username, driveId, driveType, err := onedrive.GetInfo(c) + if err != nil { + c.String(200, `

Failed to get user information:`+err.Error()+`

`) + notify["status"] = "fail" + notify["message"] = "Failed to get user information" + logger.Error("Then get user information: ", zap.Error(err), zap.Any("name", "onedrive")) + service.MyService.Notify().SendNotify("casaos:file:recover", notify) + return + } + dmap := make(map[string]string) + dmap["username"] = username + + configs, err := service.MyService.Storage().GetConfig() + if err != nil { + c.String(200, `

Failed to get rclone config:`+err.Error()+`

`) + notify["status"] = "fail" + notify["message"] = "Failed to get rclone config" + logger.Error("Then get config error: ", zap.Error(err), zap.Any("name", "onedrive")) + service.MyService.Notify().SendNotify("casaos:file:recover", notify) + return + } + for _, v := range configs.Remotes { + cf, err := service.MyService.Storage().GetConfigByName(v) + if err != nil { + logger.Error("then get config by name error: ", zap.Error(err), zap.Any("name", v)) + continue + } + if cf["type"] == "onedrive" && cf["username"] == dmap["username"] { + c.String(200, `

The same configuration has been added

`) + err := service.MyService.Storage().CheckAndMountByName(v) + if err != nil { + logger.Error("check and mount by name error: ", zap.Error(err), zap.Any("name", cf["username"])) + } + + notify["status"] = "warn" + notify["message"] = "The same configuration has been added" + service.MyService.Notify().SendNotify("casaos:file:recover", notify) + return + } + } + if len(username) > 0 { + a := strings.Split(username, "@") + username = a[0] + } + username += "_dropbox_" + strconv.FormatInt(time.Now().Unix(), 10) + + dmap["client_id"] = onedrive.ClientID + dmap["client_secret"] = onedrive.ClientSecret + dmap["token"] = `{"access_token":"` + onedrive.AccessToken + `","token_type":"bearer","refresh_token":"` + onedrive.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*3).Add(time.Minute*50).Format("15:04:05") + `.780385354Z"}` + dmap["mount_point"] = "/mnt/" + username + dmap["drive_id"] = driveId + dmap["drive_type"] = driveType + // data.SetValue(username, "type", "dropbox") + // data.SetValue(username, "client_id", add.AppKey) + // data.SetValue(username, "client_secret", add.AppSecret) + // data.SetValue(username, "mount_point", "/mnt/"+username) + + // data.SetValue(username, "token", `{"access_token":"`+dropbox.AccessToken+`","token_type":"bearer","refresh_token":"`+dropbox.Addition.RefreshToken+`","expiry":"`+currentDate+`T`+currentTime.Add(time.Hour*3).Format("15:04:05")+`.780385354Z"}`) + // e = data.Save() + // if e != nil { + // c.String(200, `

保存配置失败:`+e.Error()+`

`) + + // return + // } + service.MyService.Storage().CreateConfig(dmap, username, "onedrive") + service.MyService.Storage().MountStorage("/mnt/"+username, username+":") + + notify["status"] = "success" + notify["message"] = "Success" + notify["driver"] = "Onedrive" + service.MyService.Notify().SendNotify("casaos:file:recover", notify) } c.String(200, `

Just close the page

`) diff --git a/route/v2.go b/route/v2.go index 00832a8..dbe7ead 100644 --- a/route/v2.go +++ b/route/v2.go @@ -2,13 +2,17 @@ package route import ( "crypto/ecdsa" + "log" "net/http" "net/url" + "path" + "path/filepath" "strconv" "strings" "github.com/IceWhaleTech/CasaOS/codegen" "github.com/IceWhaleTech/CasaOS/pkg/config" + "github.com/IceWhaleTech/CasaOS/pkg/utils/file" "github.com/IceWhaleTech/CasaOS-Common/external" "github.com/IceWhaleTech/CasaOS-Common/utils/jwt" @@ -134,3 +138,118 @@ func InitV2DocRouter(docHTML string, docYAML string) http.Handler { } }) } + +func InitFile() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.URL.Query().Get("token") + if len(token) == 0 { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"message": "token not found"}`)) + return + } + + valid, _, errs := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) }) + if errs != nil || !valid { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"message": "validation failure"}`)) + return + } + filePath := r.URL.Query().Get("path") + fileName := path.Base(filePath) + w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(fileName)) + http.ServeFile(w, r, filePath) + //http.ServeFile(w, r, filePath) + }) +} + +func InitDir() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.URL.Query().Get("token") + if len(token) == 0 { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"message": "token not found"}`)) + return + } + + valid, _, errs := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) }) + if errs != nil || !valid { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"message": "validation failure"}`)) + return + } + t := r.URL.Query().Get("format") + files := r.URL.Query().Get("files") + + if len(files) == 0 { + // w.JSON(common_err.CLIENT_ERROR, model.Result{ + // Success: common_err.INVALID_PARAMS, + // Message: common_err.GetMsg(common_err.INVALID_PARAMS), + // }) + return + } + list := strings.Split(files, ",") + for _, v := range list { + if !file.Exists(v) { + // c.JSON(common_err.SERVICE_ERROR, model.Result{ + // Success: common_err.FILE_DOES_NOT_EXIST, + // Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST), + // }) + return + } + } + w.Header().Add("Content-Type", "application/octet-stream") + w.Header().Add("Content-Transfer-Encoding", "binary") + w.Header().Add("Cache-Control", "no-cache") + // handles only single files not folders and multiple files + // if len(list) == 1 { + + // filePath := list[0] + // info, err := os.Stat(filePath) + // if err != nil { + + // w.JSON(http.StatusOK, model.Result{ + // Success: common_err.FILE_DOES_NOT_EXIST, + // Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST), + // }) + //return + // } + //} + + extension, ar, err := file.GetCompressionAlgorithm(t) + if err != nil { + // w.JSON(common_err.CLIENT_ERROR, model.Result{ + // Success: common_err.INVALID_PARAMS, + // Message: common_err.GetMsg(common_err.INVALID_PARAMS), + // }) + return + } + + err = ar.Create(w) + if err != nil { + // c.JSON(common_err.SERVICE_ERROR, model.Result{ + // Success: common_err.SERVICE_ERROR, + // Message: common_err.GetMsg(common_err.SERVICE_ERROR), + // Data: err.Error(), + // }) + return + } + defer ar.Close() + commonDir := file.CommonPrefix(filepath.Separator, list...) + + currentPath := filepath.Base(commonDir) + + name := "_" + currentPath + name += extension + w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name)) + for _, fname := range list { + err = file.AddFile(ar, fname, commonDir) + if err != nil { + log.Printf("Failed to archive %s: %v", fname, err) + } + } + }) +} diff --git a/route/v2/health.go b/route/v2/health.go index 24b2dbf..33a5ddb 100644 --- a/route/v2/health.go +++ b/route/v2/health.go @@ -46,7 +46,6 @@ func (s *CasaOS) GetHealthPorts(ctx echo.Context) error { }, }) } - func (c *CasaOS) GetHealthlogs(ctx echo.Context) error { var name, currentPath, commonDir, extension string var err error