Add support for new api paths.

This commit is contained in:
davidnewhall2 2020-02-04 01:46:34 -08:00
parent ad4aca0f53
commit 5197a72390
4 changed files with 87 additions and 7 deletions

View File

@ -6,4 +6,5 @@ require (
github.com/davecgh/go-spew v1.1.1
github.com/pmezard/go-difflib v1.0.0
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
)

View File

@ -6,6 +6,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -23,16 +23,32 @@ const (
APIClientPath string = "/api/s/%s/stat/sta"
// APIDevicePath is where we get data about Unifi devices.
APIDevicePath string = "/api/s/%s/stat/device"
// APINetworkPath contains network-configuration data. Not really graphable.
APINetworkPath string = "/api/s/%s/rest/networkconf"
// APIUserGroupPath contains usergroup configurations.
APIUserGroupPath string = "/api/s/%s/rest/usergroup"
// APILoginPath is Unifi Controller Login API Path
APILoginPath string = "/api/login"
// APILoginPathNew is how we log into UDM 5.12.55+
APILoginPathNew string = "/api/auth/login"
// APIIPSEvents returns Intrusion Detection Systems Events
APIIPSEvents string = "/api/s/%s/stat/ips/event"
// APIPrefixNew is the prefix added to the new API paths; except login. duh.
APIPrefixNew string = "/proxy/network"
)
// path returns the correct api path based on the new variable.
// new is based on the unifi-controller output. is it new or old output?
func (u *Unifi) path(path string) string {
if u.isNew {
if path == APILoginPath {
return APILoginPathNew
}
if !strings.HasPrefix(path, APIPrefixNew) && path != APILoginPathNew {
return APIPrefixNew + path
}
}
return path
}
// Logger is a base type to deal with changing log outputs. Create a logger
// that matches this interface to capture debug and error logs.
type Logger func(msg string, fmt ...interface{})
@ -70,6 +86,7 @@ type Unifi struct {
*http.Client
*Config
*server
isNew bool
}
// server is the /status endpoint from the Unifi controller.

View File

@ -14,15 +14,18 @@ import (
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"time"
"golang.org/x/net/publicsuffix"
)
// NewUnifi creates a http.Client with authenticated cookies.
// Used to make additional, authenticated requests to the APIs.
// Start here.
func NewUnifi(config *Config) (*Unifi, error) {
jar, err := cookiejar.New(nil)
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
return nil, err
}
@ -45,6 +48,11 @@ func NewUnifi(config *Config) (*Unifi, error) {
},
},
}
if err := u.checkNewStyleAPI(); err != nil {
return u, err
}
if err := u.Login(); err != nil {
return u, err
}
@ -58,6 +66,7 @@ func NewUnifi(config *Config) (*Unifi, error) {
// Login is a helper method. It can be called to grab a new authentication cookie.
func (u *Unifi) Login() error {
APILoginPath := u.path(APILoginPath)
start := time.Now()
// magic login.
@ -84,6 +93,47 @@ func (u *Unifi) Login() error {
return nil
}
// with the release of controller version 5.12.55 on UDM in Jan 2020 the api paths
// changed and broke this library. This function runs when `NewUnifi()` is called to
// check if this is a newer controller or not. If it is, we set new to true.
// Setting new to true makes the path() method return different (new) paths.
func (u *Unifi) checkNewStyleAPI() error {
u.DebugLog("Requesting %s/ to determine API paths", u.URL)
req, err := http.NewRequest("GET", u.URL+"/", nil)
if err != nil {
return err
}
// We can't share these cookies with other requests, so make a new client.
// Checking the return code on the first request so don't follow a redirect.
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !u.VerifySSL},
},
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close() // we need no data here.
_, _ = io.Copy(ioutil.Discard, resp.Body) // avoid leaking.
if resp.StatusCode == http.StatusOK {
// The new version returns a "200" for a / request.
u.isNew = true
u.DebugLog("Using NEW UniFi controller API paths!")
}
// The old version returns a "302" (to /manage) for a / request
return nil
}
// GetServerData sets the controller's version and UUID. Only call this if you
// previously called Login and suspect the controller version has changed.
func (u *Unifi) GetServerData() error {
@ -116,7 +166,7 @@ func (u *Unifi) GetData(apiPath string, v interface{}, params ...string) error {
// And if you're doing that... sumbut a pull request with your new struct. :)
// This is a helper method that is exposed for convenience.
func (u *Unifi) UniReq(apiPath string, params string) (req *http.Request, err error) {
switch params {
switch apiPath = u.path(apiPath); params {
case "":
req, err = http.NewRequest("GET", u.URL+apiPath, nil)
default:
@ -128,7 +178,14 @@ func (u *Unifi) UniReq(apiPath string, params string) (req *http.Request, err er
}
req.Header.Add("Accept", "application/json")
u.DebugLog("Requesting %s, with params: %v", apiPath, params != "")
req.Header.Add("Content-Type", "application/json; charset=utf-8")
if u.Client.Jar != nil {
parsedURL, _ := url.Parse(u.URL + apiPath)
u.DebugLog("Requesting %s, with params: %v, cookies: %d", apiPath, params != "", len(u.Client.Jar.Cookies(parsedURL)))
} else {
u.DebugLog("Requesting %s, with params: %v,", apiPath, params != "")
}
return
}