mirror of
https://github.com/cristicalin/synology-csi.git
synced 2026-03-26 19:03:12 +00:00
209 lines
4.5 KiB
Go
209 lines
4.5 KiB
Go
/*
|
|
* Copyright 2021 Synology Inc.
|
|
*/
|
|
|
|
package webapi
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
log "github.com/sirupsen/logrus"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"github.com/SynologyOpenSource/synology-csi/pkg/logger"
|
|
)
|
|
|
|
type DSM struct {
|
|
Ip string
|
|
Port int
|
|
Username string
|
|
Password string
|
|
Sid string
|
|
Https bool
|
|
}
|
|
|
|
type errData struct {
|
|
Code int `json:"code"`
|
|
}
|
|
|
|
type dsmApiResp struct {
|
|
Success bool `json:"success"`
|
|
Err errData `json:"error"`
|
|
}
|
|
|
|
type Response struct {
|
|
StatusCode int
|
|
ErrorCode int
|
|
Success bool
|
|
Data interface{}
|
|
}
|
|
|
|
func (dsm *DSM) sendRequest(data string, apiTemplate interface{}, params url.Values, cgiPath string) (Response, error) {
|
|
resp, err := dsm.sendRequestWithoutConnectionCheck(data, apiTemplate, params, cgiPath)
|
|
if err != nil && resp.ErrorCode == 119 { // WEBAPI_ERR_SID_NOT_FOUND
|
|
// Re-login
|
|
if err := dsm.Login(); err != nil {
|
|
return Response{}, fmt.Errorf("Failed to re-login to DSM: [%s]. err: %v", dsm.Ip, err)
|
|
}
|
|
log.Info("Re-login succeeded.")
|
|
return dsm.sendRequestWithoutConnectionCheck(data, apiTemplate, params, cgiPath);
|
|
}
|
|
|
|
return resp, err
|
|
}
|
|
|
|
func (dsm *DSM) sendRequestWithoutConnectionCheck(data string, apiTemplate interface{}, params url.Values, cgiPath string) (Response, error) {
|
|
client := &http.Client{}
|
|
var req *http.Request
|
|
var err error
|
|
var cgiUrl string
|
|
|
|
// Ex: http://10.12.12.14:5000/webapi/auth.cgi
|
|
if dsm.Https {
|
|
// TODO: input CA certificate and fill in tls config
|
|
// Skip Verify when https
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
client = &http.Client{Transport: tr}
|
|
cgiUrl = fmt.Sprintf("https://%s:%d/%s", dsm.Ip, dsm.Port, cgiPath)
|
|
} else {
|
|
cgiUrl = fmt.Sprintf("http://%s:%d/%s", dsm.Ip, dsm.Port, cgiPath)
|
|
}
|
|
|
|
baseUrl, err := url.Parse(cgiUrl)
|
|
if err != nil {
|
|
return Response{}, err
|
|
}
|
|
|
|
baseUrl.RawQuery = params.Encode()
|
|
|
|
if logger.WebapiDebug {
|
|
log.Debugln(baseUrl.RawQuery)
|
|
}
|
|
|
|
if data != "" {
|
|
req, err = http.NewRequest("POST", baseUrl.String(), nil)
|
|
} else {
|
|
req, err = http.NewRequest("GET", baseUrl.String(), nil)
|
|
}
|
|
|
|
if dsm.Sid != "" {
|
|
cookie := http.Cookie{Name: "id", Value: dsm.Sid}
|
|
req.AddCookie(&cookie)
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return Response{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// For debug print text body
|
|
var bodyText []byte
|
|
if logger.WebapiDebug {
|
|
bodyText, err = ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return Response{}, err
|
|
}
|
|
s := string(bodyText)
|
|
log.Debugln(s)
|
|
}
|
|
|
|
if resp.StatusCode != 200 && resp.StatusCode != 302 {
|
|
return Response{}, fmt.Errorf("Bad response status code: %d", resp.StatusCode)
|
|
}
|
|
|
|
// Strip data json data from response
|
|
type envelop struct {
|
|
dsmApiResp
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
|
|
e := envelop{}
|
|
var outResp Response
|
|
|
|
if logger.WebapiDebug {
|
|
if err := json.Unmarshal(bodyText, &e); err != nil {
|
|
return Response{}, err
|
|
}
|
|
} else {
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
if err := decoder.Decode(&e); err != nil {
|
|
return Response{}, err
|
|
}
|
|
}
|
|
outResp.Success = e.Success
|
|
outResp.ErrorCode = e.Err.Code
|
|
outResp.StatusCode = resp.StatusCode
|
|
|
|
if !e.Success {
|
|
return outResp, fmt.Errorf("DSM Api error. Error code:%d", outResp.ErrorCode)
|
|
}
|
|
|
|
if e.Data != nil {
|
|
if err := json.Unmarshal(e.Data, apiTemplate); err != nil {
|
|
return Response{}, err
|
|
}
|
|
}
|
|
|
|
outResp.Data = apiTemplate
|
|
|
|
if err != nil {
|
|
return Response{}, err
|
|
}
|
|
|
|
return outResp, nil
|
|
}
|
|
|
|
// Login by given user name and password
|
|
func (dsm *DSM) Login() error {
|
|
params := url.Values{}
|
|
params.Add("api", "SYNO.API.Auth")
|
|
params.Add("method", "login")
|
|
params.Add("version", "3")
|
|
params.Add("account", dsm.Username)
|
|
params.Add("passwd", dsm.Password)
|
|
params.Add("format", "sid")
|
|
|
|
type LoginResp struct {
|
|
Sid string `json:"sid"`
|
|
}
|
|
|
|
resp, err := dsm.sendRequestWithoutConnectionCheck("", &LoginResp{}, params, "webapi/auth.cgi")
|
|
if err != nil {
|
|
r, _ := regexp.Compile("passwd=.*&")
|
|
temp := r.ReplaceAllString(err.Error(), "")
|
|
|
|
return fmt.Errorf("%s", temp)
|
|
}
|
|
|
|
loginResp, ok := resp.Data.(*LoginResp)
|
|
if !ok {
|
|
return fmt.Errorf("Failed to assert response to %T", &LoginResp{})
|
|
}
|
|
dsm.Sid = loginResp.Sid
|
|
|
|
return nil
|
|
}
|
|
|
|
// Logout on current IP and reset the synoToken
|
|
func (dsm *DSM) Logout() error {
|
|
params := url.Values{}
|
|
params.Add("api", "SYNO.API.Auth")
|
|
params.Add("method", "logout")
|
|
params.Add("version", "1")
|
|
|
|
_, err := dsm.sendRequestWithoutConnectionCheck("", &struct{}{}, params, "webapi/entry.cgi")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dsm.Sid = ""
|
|
|
|
return nil
|
|
}
|