158 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			158 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
| package unifi
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"crypto/tls"
 | |
| 	"encoding/json"
 | |
| 	"net/http"
 | |
| 	"net/http/cookiejar"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// ClientPath is Unifi Clients API Path
 | |
| 	ClientPath string = "/api/s/default/stat/sta"
 | |
| 	// DevicePath is where we get data about Unifi devices.
 | |
| 	DevicePath string = "/api/s/default/stat/device"
 | |
| 	// NetworkPath contains network-configuration data. Not really graphable.
 | |
| 	NetworkPath string = "/api/s/default/rest/networkconf"
 | |
| 	// UserGroupPath contains usergroup configurations.
 | |
| 	UserGroupPath string = "/api/s/default/rest/usergroup"
 | |
| 	// LoginPath is Unifi Controller Login API Path
 | |
| 	LoginPath string = "/api/login"
 | |
| )
 | |
| 
 | |
| // Logger is a base type to deal with changing log outputs.
 | |
| type Logger func(msg string, fmt ...interface{})
 | |
| 
 | |
| // Devices contains a list of all the unifi devices from a controller.
 | |
| type Devices struct {
 | |
| 	UAPs []UAP
 | |
| 	USGs []USG
 | |
| 	USWs []USW
 | |
| }
 | |
| 
 | |
| // Clients conptains a list of all the unifi clients from a controller.
 | |
| type Clients struct {
 | |
| 	UCLs []UCL
 | |
| }
 | |
| 
 | |
| // Unifi is what you get in return for providing a password!
 | |
| type Unifi struct {
 | |
| 	*http.Client
 | |
| 	baseURL  string
 | |
| 	ErrorLog Logger
 | |
| 	DebugLog Logger
 | |
| }
 | |
| 
 | |
| // FlexInt provides a container and unmarshalling for fields that may be
 | |
| // numbers or strings in the Unifi API
 | |
| type FlexInt struct {
 | |
| 	Number float64
 | |
| 	String string
 | |
| }
 | |
| 
 | |
| // UnmarshalJSON converts a string or number to an integer.
 | |
| func (f *FlexInt) UnmarshalJSON(b []byte) error {
 | |
| 	var unk interface{}
 | |
| 	if err := json.Unmarshal(b, &unk); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	switch i := unk.(type) {
 | |
| 	case float64:
 | |
| 		f.Number = i
 | |
| 		f.String = strconv.FormatFloat(i, 'f', -1, 64)
 | |
| 		return nil
 | |
| 	case string:
 | |
| 		f.String = i
 | |
| 		f.Number, _ = strconv.ParseFloat(i, 64)
 | |
| 		return nil
 | |
| 	default:
 | |
| 		return errors.New("Cannot unmarshal to FlexInt")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // FlexBool provides a container and unmarshalling for fields that may be
 | |
| // boolean or strings in the Unifi API
 | |
| type FlexBool struct {
 | |
| 	Bool   bool
 | |
| 	String string
 | |
| }
 | |
| 
 | |
| // UnmarshalJSO method converts armed/disarmed, yes/no, active/inactive or 0/1 to true/false.
 | |
| // Really it converts ready, up, t, armed, yes, active, enabled, 1, true to true. Anything else is false.
 | |
| func (f *FlexBool) UnmarshalJSON(b []byte) error {
 | |
| 	f.String = strings.Trim(string(b), `"`)
 | |
| 	f.Bool = f.String == "1" || strings.EqualFold(f.String, "true") || strings.EqualFold(f.String, "yes") ||
 | |
| 		strings.EqualFold(f.String, "t") || strings.EqualFold(f.String, "armed") || strings.EqualFold(f.String, "active") ||
 | |
| 		strings.EqualFold(f.String, "enabled") || strings.EqualFold(f.String, "ready") || strings.EqualFold(f.String, "up")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // GetController creates a http.Client with authenticated cookies.
 | |
| // Used to make additional, authenticated requests to the APIs.
 | |
| func GetController(user, pass, url string, verifySSL bool) (*Unifi, error) {
 | |
| 	json := `{"username": "` + user + `","password": "` + pass + `"}`
 | |
| 	jar, err := cookiejar.New(nil)
 | |
| 	if err != nil {
 | |
| 		return nil, errors.Wrap(err, "cookiejar.New(nil)")
 | |
| 	}
 | |
| 	u := &Unifi{
 | |
| 		Client: &http.Client{
 | |
| 			Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !verifySSL}},
 | |
| 			Jar:       jar,
 | |
| 		},
 | |
| 	}
 | |
| 	if u.baseURL = url; strings.HasSuffix(url, "/") {
 | |
| 		u.baseURL = url[:len(url)-1]
 | |
| 	}
 | |
| 	req, err := u.UniReq(LoginPath, json)
 | |
| 	if err != nil {
 | |
| 		return u, errors.Wrap(err, "UniReq(LoginPath, json)")
 | |
| 	}
 | |
| 	resp, err := u.Do(req)
 | |
| 	if err != nil {
 | |
| 		return u, errors.Wrap(err, "authReq.Do(req)")
 | |
| 	}
 | |
| 	defer func() {
 | |
| 		_ = resp.Body.Close()
 | |
| 	}()
 | |
| 	if resp.StatusCode != http.StatusOK {
 | |
| 		return u, errors.Errorf("authentication failed (%v): %v (status: %v/%v)",
 | |
| 			user, url+LoginPath, resp.StatusCode, resp.Status)
 | |
| 	}
 | |
| 	return u, nil
 | |
| }
 | |
| 
 | |
| // UniReq is a small helper function that adds an Accept header.
 | |
| // Use this if you're unmarshalling Unifi data into custom types.
 | |
| // And you're doing that... sumbut a pull request with your new struct. :)
 | |
| func (u *Unifi) UniReq(apiPath string, params string) (req *http.Request, err error) {
 | |
| 	if params != "" {
 | |
| 		req, err = http.NewRequest("POST", u.baseURL+apiPath, bytes.NewBufferString(params))
 | |
| 	} else {
 | |
| 		req, err = http.NewRequest("GET", u.baseURL+apiPath, nil)
 | |
| 	}
 | |
| 	if err == nil {
 | |
| 		req.Header.Add("Accept", "application/json")
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // dLogf logs a debug message.
 | |
| func (u *Unifi) dLogf(msg string, v ...interface{}) {
 | |
| 	if u.DebugLog != nil {
 | |
| 		u.DebugLog("[DEBUG] "+msg, v...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // dLogf logs an error message.
 | |
| func (u *Unifi) eLogf(msg string, v ...interface{}) {
 | |
| 	if u.ErrorLog != nil {
 | |
| 		u.ErrorLog("[ERROR] "+msg, v...)
 | |
| 	}
 | |
| }
 |