|
package aliyundrive_open |
|
|
|
import ( |
|
"context" |
|
"encoding/base64" |
|
"errors" |
|
"fmt" |
|
"net/http" |
|
"strings" |
|
"time" |
|
|
|
"github.com/alist-org/alist/v3/drivers/base" |
|
"github.com/alist-org/alist/v3/internal/op" |
|
"github.com/alist-org/alist/v3/pkg/utils" |
|
"github.com/go-resty/resty/v2" |
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
|
|
|
|
func (d *AliyundriveOpen) _refreshToken() (string, string, error) { |
|
url := API_URL + "/oauth/access_token" |
|
if d.OauthTokenURL != "" && d.ClientID == "" { |
|
url = d.OauthTokenURL |
|
} |
|
|
|
var e ErrResp |
|
res, err := base.RestyClient.R(). |
|
|
|
SetBody(base.Json{ |
|
"client_id": d.ClientID, |
|
"client_secret": d.ClientSecret, |
|
"grant_type": "refresh_token", |
|
"refresh_token": d.RefreshToken, |
|
}). |
|
|
|
SetError(&e). |
|
Post(url) |
|
if err != nil { |
|
return "", "", err |
|
} |
|
log.Debugf("[ali_open] refresh token response: %s", res.String()) |
|
if e.Code != "" { |
|
return "", "", fmt.Errorf("failed to refresh token: %s", e.Message) |
|
} |
|
refresh, access := utils.Json.Get(res.Body(), "refresh_token").ToString(), utils.Json.Get(res.Body(), "access_token").ToString() |
|
if refresh == "" { |
|
return "", "", fmt.Errorf("failed to refresh token: refresh token is empty, resp: %s", res.String()) |
|
} |
|
curSub, err := getSub(d.RefreshToken) |
|
if err != nil { |
|
return "", "", err |
|
} |
|
newSub, err := getSub(refresh) |
|
if err != nil { |
|
return "", "", err |
|
} |
|
if curSub != newSub { |
|
return "", "", errors.New("failed to refresh token: sub not match") |
|
} |
|
return refresh, access, nil |
|
} |
|
|
|
func getSub(token string) (string, error) { |
|
segments := strings.Split(token, ".") |
|
if len(segments) != 3 { |
|
return "", errors.New("not a jwt token because of invalid segments") |
|
} |
|
bs, err := base64.RawStdEncoding.DecodeString(segments[1]) |
|
if err != nil { |
|
return "", errors.New("failed to decode jwt token") |
|
} |
|
return utils.Json.Get(bs, "sub").ToString(), nil |
|
} |
|
|
|
func (d *AliyundriveOpen) refreshToken() error { |
|
if d.ref != nil { |
|
return d.ref.refreshToken() |
|
} |
|
refresh, access, err := d._refreshToken() |
|
for i := 0; i < 3; i++ { |
|
if err == nil { |
|
break |
|
} else { |
|
log.Errorf("[ali_open] failed to refresh token: %s", err) |
|
} |
|
refresh, access, err = d._refreshToken() |
|
} |
|
if err != nil { |
|
return err |
|
} |
|
log.Infof("[ali_open] token exchange: %s -> %s", d.RefreshToken, refresh) |
|
d.RefreshToken, d.AccessToken = refresh, access |
|
op.MustSaveDriverStorage(d) |
|
return nil |
|
} |
|
|
|
func (d *AliyundriveOpen) request(uri, method string, callback base.ReqCallback, retry ...bool) ([]byte, error) { |
|
b, err, _ := d.requestReturnErrResp(uri, method, callback, retry...) |
|
return b, err |
|
} |
|
|
|
func (d *AliyundriveOpen) requestReturnErrResp(uri, method string, callback base.ReqCallback, retry ...bool) ([]byte, error, *ErrResp) { |
|
req := base.RestyClient.R() |
|
|
|
req.SetHeader("Authorization", "Bearer "+d.getAccessToken()) |
|
if method == http.MethodPost { |
|
req.SetHeader("Content-Type", "application/json") |
|
} |
|
if callback != nil { |
|
callback(req) |
|
} |
|
var e ErrResp |
|
req.SetError(&e) |
|
res, err := req.Execute(method, API_URL+uri) |
|
if err != nil { |
|
if res != nil { |
|
log.Errorf("[aliyundrive_open] request error: %s", res.String()) |
|
} |
|
return nil, err, nil |
|
} |
|
isRetry := len(retry) > 0 && retry[0] |
|
if e.Code != "" { |
|
if !isRetry && (utils.SliceContains([]string{"AccessTokenInvalid", "AccessTokenExpired", "I400JD"}, e.Code) || d.getAccessToken() == "") { |
|
err = d.refreshToken() |
|
if err != nil { |
|
return nil, err, nil |
|
} |
|
return d.requestReturnErrResp(uri, method, callback, true) |
|
} |
|
return nil, fmt.Errorf("%s:%s", e.Code, e.Message), &e |
|
} |
|
return res.Body(), nil, nil |
|
} |
|
|
|
func (d *AliyundriveOpen) list(ctx context.Context, data base.Json) (*Files, error) { |
|
var resp Files |
|
_, err := d.request("/adrive/v1.0/openFile/list", http.MethodPost, func(req *resty.Request) { |
|
req.SetBody(data).SetResult(&resp) |
|
}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return &resp, nil |
|
} |
|
|
|
func (d *AliyundriveOpen) getFiles(ctx context.Context, fileId string) ([]File, error) { |
|
marker := "first" |
|
res := make([]File, 0) |
|
for marker != "" { |
|
if marker == "first" { |
|
marker = "" |
|
} |
|
data := base.Json{ |
|
"drive_id": d.DriveId, |
|
"limit": 200, |
|
"marker": marker, |
|
"order_by": d.OrderBy, |
|
"order_direction": d.OrderDirection, |
|
"parent_file_id": fileId, |
|
|
|
|
|
|
|
|
|
|
|
} |
|
resp, err := d.limitList(ctx, data) |
|
if err != nil { |
|
return nil, err |
|
} |
|
marker = resp.NextMarker |
|
res = append(res, resp.Items...) |
|
} |
|
return res, nil |
|
} |
|
|
|
func getNowTime() (time.Time, string) { |
|
nowTime := time.Now() |
|
nowTimeStr := nowTime.Format("2006-01-02T15:04:05.000Z") |
|
return nowTime, nowTimeStr |
|
} |
|
|
|
func (d *AliyundriveOpen) getAccessToken() string { |
|
if d.ref != nil { |
|
return d.ref.getAccessToken() |
|
} |
|
return d.AccessToken |
|
} |
|
|