Files
synology-csi/pkg/dsm/webapi/dsmwebapi.go
2021-08-31 10:18:35 +08:00

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
}