mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
405 lines
11 KiB
Go
405 lines
11 KiB
Go
// Copyright (c) 2018 Kai Schwarz (HEXONET GmbH). All rights reserved.
|
|
//
|
|
// Use of this source code is governed by the MIT
|
|
// license that can be found in the LICENSE.md file.
|
|
|
|
// Package apiclient contains all you need to communicate with the insanely fast HEXONET backend API.
|
|
package apiclient
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
R "github.com/hexonet/go-sdk/response"
|
|
RTM "github.com/hexonet/go-sdk/responsetemplatemanager"
|
|
SC "github.com/hexonet/go-sdk/socketconfig"
|
|
)
|
|
|
|
var rtm = RTM.GetInstance()
|
|
|
|
// APIClient is the entry point class for communicating with the insanely fast HEXONET backend api.
|
|
// It allows two ways of communication:
|
|
// * session based communication
|
|
// * sessionless communication
|
|
//
|
|
// A session based communication makes sense in case you use it to
|
|
// build your own frontend on top. It allows also to use 2FA
|
|
// (2 Factor Auth) by providing "otp" in the config parameter of
|
|
// the login method.
|
|
// A sessionless communication makes sense in case you do not need
|
|
// to care about the above and you have just to request some commands.
|
|
//
|
|
// Possible commands can be found at https://github.com/hexonet/hexonet-api-documentation/tree/master/API
|
|
type APIClient struct {
|
|
socketTimeout time.Duration
|
|
socketURL string
|
|
socketConfig *SC.SocketConfig
|
|
debugMode bool
|
|
ua string
|
|
}
|
|
|
|
// NewAPIClient represents the constructor for struct APIClient.
|
|
func NewAPIClient() *APIClient {
|
|
cl := &APIClient{
|
|
debugMode: false,
|
|
socketTimeout: 300 * time.Second,
|
|
socketURL: "https://api.ispapi.net/api/call.cgi",
|
|
socketConfig: SC.NewSocketConfig(),
|
|
ua: "",
|
|
}
|
|
cl.UseLIVESystem()
|
|
return cl
|
|
}
|
|
|
|
// EnableDebugMode method to enable Debug Output to logger
|
|
func (cl *APIClient) EnableDebugMode() *APIClient {
|
|
cl.debugMode = true
|
|
return cl
|
|
}
|
|
|
|
// DisableDebugMode method to disable Debug Output to logger
|
|
func (cl *APIClient) DisableDebugMode() *APIClient {
|
|
cl.debugMode = false
|
|
return cl
|
|
}
|
|
|
|
// GetPOSTData method to Serialize given command for POST request
|
|
// including connection configuration data
|
|
func (cl *APIClient) GetPOSTData(cmd map[string]string) string {
|
|
data := cl.socketConfig.GetPOSTData()
|
|
var tmp strings.Builder
|
|
keys := []string{}
|
|
for key := range cmd {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
val := cmd[key]
|
|
tmp.WriteString(key)
|
|
tmp.WriteString("=")
|
|
val = strings.Replace(val, "\r", "", -1)
|
|
val = strings.Replace(val, "\n", "", -1)
|
|
tmp.WriteString(val)
|
|
tmp.WriteString("\n")
|
|
}
|
|
str := tmp.String()
|
|
str = str[:len(str)-1] //remove \n at end
|
|
return strings.Join([]string{
|
|
data,
|
|
url.QueryEscape("s_command"),
|
|
"=",
|
|
url.QueryEscape(str),
|
|
}, "")
|
|
}
|
|
|
|
// GetSession method to get the API Session that is currently set
|
|
func (cl *APIClient) GetSession() (string, error) {
|
|
sessid := cl.socketConfig.GetSession()
|
|
if len(sessid) == 0 {
|
|
return "", errors.New("Could not find an active session")
|
|
}
|
|
return sessid, nil
|
|
}
|
|
|
|
// GetURL method to get the API connection url that is currently set
|
|
func (cl *APIClient) GetURL() string {
|
|
return cl.socketURL
|
|
}
|
|
|
|
// SetUserAgent method to customize user-agent header (useful for tools that use our SDK)
|
|
func (cl *APIClient) SetUserAgent(str string, rv string) *APIClient {
|
|
cl.ua = str + " (" + runtime.GOOS + "; " + runtime.GOARCH + "; rv:" + rv + ") go-sdk/" + cl.GetVersion() + " go/" + runtime.Version()
|
|
return cl
|
|
}
|
|
|
|
// GetUserAgent method to return the user agent string
|
|
func (cl *APIClient) GetUserAgent() string {
|
|
if len(cl.ua) == 0 {
|
|
cl.ua = "GO-SDK (" + runtime.GOOS + "; " + runtime.GOARCH + "; rv:" + cl.GetVersion() + ") go/" + runtime.Version()
|
|
}
|
|
return cl.ua
|
|
}
|
|
|
|
// GetVersion method to get current module version
|
|
func (cl *APIClient) GetVersion() string {
|
|
return "2.2.3"
|
|
}
|
|
|
|
// SaveSession method to apply data to a session for later reuse
|
|
// Please save/update that map into user session
|
|
func (cl *APIClient) SaveSession(sessionobj map[string]interface{}) *APIClient {
|
|
sessionobj["socketcfg"] = map[string]string{
|
|
"entity": cl.socketConfig.GetSystemEntity(),
|
|
"session": cl.socketConfig.GetSession(),
|
|
}
|
|
return cl
|
|
}
|
|
|
|
// ReuseSession method to reuse given configuration out of a user session
|
|
// to rebuild and reuse connection settings
|
|
func (cl *APIClient) ReuseSession(sessionobj map[string]interface{}) *APIClient {
|
|
cfg := sessionobj["socketcfg"].(map[string]string)
|
|
cl.socketConfig.SetSystemEntity(cfg["entity"])
|
|
cl.SetSession(cfg["session"])
|
|
return cl
|
|
}
|
|
|
|
// SetURL method to set another connection url to be used for API communication
|
|
func (cl *APIClient) SetURL(value string) *APIClient {
|
|
cl.socketURL = value
|
|
return cl
|
|
}
|
|
|
|
// SetOTP method to set one time password to be used for API communication
|
|
func (cl *APIClient) SetOTP(value string) *APIClient {
|
|
cl.socketConfig.SetOTP(value)
|
|
return cl
|
|
}
|
|
|
|
// SetSession method to set an API session id to be used for API communication
|
|
func (cl *APIClient) SetSession(value string) *APIClient {
|
|
cl.socketConfig.SetSession(value)
|
|
return cl
|
|
}
|
|
|
|
// SetRemoteIPAddress method to set an Remote IP Address to be used for API communication
|
|
func (cl *APIClient) SetRemoteIPAddress(value string) *APIClient {
|
|
cl.socketConfig.SetRemoteAddress(value)
|
|
return cl
|
|
}
|
|
|
|
// SetCredentials method to set Credentials to be used for API communication
|
|
func (cl *APIClient) SetCredentials(uid string, pw string) *APIClient {
|
|
cl.socketConfig.SetLogin(uid)
|
|
cl.socketConfig.SetPassword(pw)
|
|
return cl
|
|
}
|
|
|
|
// SetRoleCredentials method to set Role User Credentials to be used for API communication
|
|
func (cl *APIClient) SetRoleCredentials(uid string, role string, pw string) *APIClient {
|
|
if len(role) > 0 {
|
|
return cl.SetCredentials(uid+"!"+role, pw)
|
|
}
|
|
return cl.SetCredentials(uid, pw)
|
|
}
|
|
|
|
// Login method to perform API login to start session-based communication
|
|
// 1st parameter: one time password
|
|
func (cl *APIClient) Login(params ...string) *R.Response {
|
|
otp := ""
|
|
if len(params) > 0 {
|
|
otp = params[0]
|
|
}
|
|
cl.SetOTP(otp)
|
|
rr := cl.Request(map[string]string{"COMMAND": "StartSession"})
|
|
if rr.IsSuccess() {
|
|
col := rr.GetColumn("SESSION")
|
|
if col != nil {
|
|
cl.SetSession(col.GetData()[0])
|
|
} else {
|
|
cl.SetSession("")
|
|
}
|
|
}
|
|
return rr
|
|
}
|
|
|
|
// LoginExtended method to perform API login to start session-based communication.
|
|
// 1st parameter: map of additional command parameters
|
|
// 2nd parameter: one time password
|
|
func (cl *APIClient) LoginExtended(params ...interface{}) *R.Response {
|
|
otp := ""
|
|
parameters := map[string]string{}
|
|
if len(params) == 2 {
|
|
otp = params[1].(string)
|
|
}
|
|
cl.SetOTP(otp)
|
|
if len(params) > 0 {
|
|
parameters = params[0].(map[string]string)
|
|
}
|
|
cmd := map[string]string{
|
|
"COMMAND": "StartSession",
|
|
}
|
|
for k, v := range parameters {
|
|
cmd[k] = v
|
|
}
|
|
rr := cl.Request(cmd)
|
|
if rr.IsSuccess() {
|
|
col := rr.GetColumn("SESSION")
|
|
if col != nil {
|
|
cl.SetSession(col.GetData()[0])
|
|
} else {
|
|
cl.SetSession("")
|
|
}
|
|
}
|
|
return rr
|
|
}
|
|
|
|
// Logout method to perform API logout to close API session in use
|
|
func (cl *APIClient) Logout() *R.Response {
|
|
rr := cl.Request(map[string]string{
|
|
"COMMAND": "EndSession",
|
|
})
|
|
if rr.IsSuccess() {
|
|
cl.SetSession("")
|
|
}
|
|
return rr
|
|
}
|
|
|
|
// Request method to perform API request using the given command
|
|
func (cl *APIClient) Request(cmd map[string]string) *R.Response {
|
|
data := cl.GetPOSTData(cmd)
|
|
|
|
client := &http.Client{
|
|
Timeout: cl.socketTimeout,
|
|
}
|
|
req, err := http.NewRequest("POST", cl.socketURL, strings.NewReader(data))
|
|
if err != nil {
|
|
tpl := rtm.GetTemplate("httperror").GetPlain()
|
|
r := R.NewResponse(tpl, cmd)
|
|
if cl.debugMode {
|
|
j, _ := json.Marshal(cmd)
|
|
fmt.Printf("%s\n", j)
|
|
fmt.Println("POST: " + data)
|
|
fmt.Println("HTTP communication failed: " + err.Error())
|
|
fmt.Println(r.GetPlain())
|
|
}
|
|
return r
|
|
}
|
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
req.Header.Add("Expect", "")
|
|
req.Header.Add("User-Agent", cl.GetUserAgent())
|
|
resp, err2 := client.Do(req)
|
|
if err2 != nil {
|
|
tpl := rtm.GetTemplate("httperror").GetPlain()
|
|
r := R.NewResponse(tpl, cmd)
|
|
if cl.debugMode {
|
|
j, _ := json.Marshal(cmd)
|
|
fmt.Printf("%s\n", j)
|
|
fmt.Println("POST: " + data)
|
|
fmt.Println("HTTP communication failed: " + err2.Error())
|
|
fmt.Println(r.GetPlain())
|
|
}
|
|
return r
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusOK {
|
|
response, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
tpl := rtm.GetTemplate("httperror").GetPlain()
|
|
r := R.NewResponse(tpl, cmd)
|
|
if cl.debugMode {
|
|
j, _ := json.Marshal(cmd)
|
|
fmt.Printf("%s\n", j)
|
|
fmt.Println("POST: " + data)
|
|
fmt.Println("HTTP communication failed: " + err.Error())
|
|
fmt.Println(r.GetPlain())
|
|
}
|
|
return r
|
|
}
|
|
r := R.NewResponse(string(response), cmd)
|
|
if cl.debugMode {
|
|
j, _ := json.Marshal(cmd)
|
|
fmt.Printf("%s\n", j)
|
|
fmt.Println("POST: " + data)
|
|
fmt.Println(r.GetPlain())
|
|
}
|
|
return r
|
|
}
|
|
tpl := rtm.GetTemplate("httperror").GetPlain()
|
|
r := R.NewResponse(tpl, cmd)
|
|
if cl.debugMode {
|
|
j, _ := json.Marshal(cmd)
|
|
fmt.Printf("%s\n", j)
|
|
fmt.Println("POST: " + data)
|
|
fmt.Println(r.GetPlain())
|
|
}
|
|
return r
|
|
}
|
|
|
|
// RequestNextResponsePage method to request the next page of list entries for the current list query
|
|
// Useful for lists
|
|
func (cl *APIClient) RequestNextResponsePage(rr *R.Response) (*R.Response, error) {
|
|
mycmd := cl.toUpperCaseKeys(rr.GetCommand())
|
|
if _, ok := mycmd["LAST"]; ok {
|
|
return nil, errors.New("Parameter LAST in use. Please remove it to avoid issues in requestNextPage")
|
|
}
|
|
first := 0
|
|
if v, ok := mycmd["FIRST"]; ok {
|
|
first, _ = fmt.Sscan("%s", v)
|
|
}
|
|
total := rr.GetRecordsTotalCount()
|
|
limit := rr.GetRecordsLimitation()
|
|
first += limit
|
|
if first < total {
|
|
mycmd["FIRST"] = fmt.Sprintf("%d", first)
|
|
mycmd["LIMIT"] = fmt.Sprintf("%d", limit)
|
|
return cl.Request(mycmd), nil
|
|
}
|
|
return nil, errors.New("Could not find further existing pages")
|
|
}
|
|
|
|
// RequestAllResponsePages method to request all pages/entries for the given query command
|
|
// Use this method with caution as it requests all list data until done.
|
|
func (cl *APIClient) RequestAllResponsePages(cmd map[string]string) []R.Response {
|
|
var err error
|
|
responses := []R.Response{}
|
|
mycmd := map[string]string{
|
|
"FIRST": "0",
|
|
}
|
|
for k, v := range cmd {
|
|
mycmd[k] = v
|
|
}
|
|
rr := cl.Request(mycmd)
|
|
tmp := rr
|
|
for {
|
|
responses = append(responses, *tmp)
|
|
tmp, err = cl.RequestNextResponsePage(tmp)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return responses
|
|
}
|
|
|
|
// SetUserView method to set a data view to a given subuser
|
|
func (cl *APIClient) SetUserView(uid string) *APIClient {
|
|
cl.socketConfig.SetUser(uid)
|
|
return cl
|
|
}
|
|
|
|
// ResetUserView method to reset data view back from subuser to user
|
|
func (cl *APIClient) ResetUserView() *APIClient {
|
|
cl.socketConfig.SetUser("")
|
|
return cl
|
|
}
|
|
|
|
// UseOTESystem method to set OT&E System for API communication
|
|
func (cl *APIClient) UseOTESystem() *APIClient {
|
|
cl.socketConfig.SetSystemEntity("1234")
|
|
return cl
|
|
}
|
|
|
|
// UseLIVESystem method to set LIVE System for API communication
|
|
// Usage of LIVE System is active by default.
|
|
func (cl *APIClient) UseLIVESystem() *APIClient {
|
|
cl.socketConfig.SetSystemEntity("54cd")
|
|
return cl
|
|
}
|
|
|
|
// toUpperCaseKeys method to translate all command parameter names to uppercase
|
|
func (cl *APIClient) toUpperCaseKeys(cmd map[string]string) map[string]string {
|
|
newcmd := map[string]string{}
|
|
for k, v := range cmd {
|
|
newcmd[strings.ToUpper(k)] = v
|
|
}
|
|
return newcmd
|
|
}
|