github.com/dim13/unifi@v0.0.0-20230308161331-9b04946f5e93/unifi.go (about)

     1  // Copyright (c) 2014 The unifi Authors. All rights reserved.
     2  // Use of this source code is governed by ISC-style license
     3  // that can be found in the LICENSE file.
     4  
     5  package unifi
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/tls"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"log"
    15  	"net/http"
    16  	"net/http/cookiejar"
    17  	"net/url"
    18  	"reflect"
    19  )
    20  
    21  var (
    22  	ErrLoginFirst  = errors.New("login first")
    23  	minimalVersion = 5
    24  )
    25  
    26  type Unifi struct {
    27  	client  *http.Client
    28  	baseURL string
    29  	apiURL  string
    30  	version int
    31  }
    32  
    33  type meta struct {
    34  	Rc string
    35  }
    36  
    37  type login struct {
    38  	Username string `json:"username"`
    39  	Password string `json:"password"`
    40  }
    41  
    42  // Initializes a session.  Only controller versions 5 and newer are supported
    43  func Login(user, pass, host, port, site string, version int) (*Unifi, error) {
    44  	if version < 4 {
    45  		return nil, fmt.Errorf("API version %d unsuported. (Minimal version: %d)", version, minimalVersion)
    46  	}
    47  
    48  	u := new(Unifi)
    49  	tr := &http.Transport{
    50  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    51  	}
    52  
    53  	cj, _ := cookiejar.New(nil)
    54  	u.client = &http.Client{
    55  		Transport: tr,
    56  		Jar:       cj,
    57  	}
    58  	u.baseURL = "https://" + host + ":" + port + "/"
    59  	u.version = version
    60  
    61  	l := new(login)
    62  	l.Username = user
    63  	l.Password = pass
    64  	j, err := json.Marshal(l)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	r := bytes.NewReader(j)
    70  	if _, err := u.client.Post(u.baseURL+"api/login", "application/json", r); err != nil {
    71  		fmt.Println(err)
    72  		return nil, err
    73  	}
    74  
    75  	u.apiURL = u.baseURL + "api/"
    76  
    77  	return u, nil
    78  }
    79  
    80  // Terminates a session
    81  func (u *Unifi) Logout() {
    82  	u.client.Get(u.baseURL + "logout")
    83  }
    84  
    85  func (u *Unifi) apicmd(site *Site, cmd string, payload any) ([]byte, error) {
    86  
    87  	url := u.apiURL
    88  
    89  	// For site specific command, add site settings
    90  	if site != nil {
    91  		url += fmt.Sprintf("s/%s/", site.Name)
    92  	}
    93  
    94  	// Add the command to the url
    95  	url += cmd
    96  
    97  	var resp *http.Response
    98  	var err error
    99  
   100  	if payload == nil {
   101  		resp, err = u.client.Get(url)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  	} else {
   106  		json, err := json.Marshal(payload)
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  
   111  		resp, err = u.client.Post(url, "application/json", bytes.NewBuffer(json))
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  	}
   116  
   117  	defer resp.Body.Close()
   118  	body, err := io.ReadAll(resp.Body)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	return body, nil
   123  }
   124  
   125  func (u *Unifi) apicmdPut(site *Site, cmd string, data any) error {
   126  
   127  	url := u.apiURL
   128  
   129  	// For site specific command, add site settings
   130  	if site != nil {
   131  		url += fmt.Sprintf("s/%s/", site.Name)
   132  	}
   133  
   134  	// Add the command to the url
   135  	url += cmd
   136  
   137  	j, err := json.Marshal(data)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	r := bytes.NewReader(j)
   143  
   144  	req, err := http.NewRequest(http.MethodPut, url, r)
   145  
   146  	if err != nil {
   147  		return err
   148  	}
   149  	req.Header.Add("Content-Type", "application/json;charset=UTF-8")
   150  
   151  	// Send request
   152  	resp, err := u.client.Do(req)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	defer resp.Body.Close()
   158  
   159  	if err != nil {
   160  		return err
   161  	}
   162  	fmt.Println(resp.Status)
   163  	if resp.StatusCode != 200 {
   164  		return fmt.Errorf(resp.Status)
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  type command struct {
   171  	Mac     string `json:"mac"`
   172  	Cmd     string `json:"cmd"`
   173  	Minutes int    `json:"minutes,omitempty"`
   174  }
   175  
   176  func (u *Unifi) devcmd(mac, cmd string) error {
   177  	return u.maccmd("devmgr", command{Mac: mac, Cmd: cmd})
   178  }
   179  
   180  func (u *Unifi) stacmd(mac, cmd string, min ...int) error {
   181  	minutes := 0
   182  	if len(min) > 0 {
   183  		minutes = min[0]
   184  	}
   185  	return u.maccmd("stamgr", command{Mac: mac, Cmd: cmd, Minutes: minutes})
   186  }
   187  
   188  func (u *Unifi) maccmd(mgr string, args any) error {
   189  	param, err := json.Marshal(args)
   190  	if err != nil {
   191  		return err
   192  	}
   193  	val := url.Values{"json": {string(param)}}
   194  	_, err = u.client.PostForm(u.apiURL+"cmd/"+mgr, val)
   195  	return err
   196  }
   197  
   198  func (u *Unifi) parse(site *Site, cmd string, payload any, v any) error {
   199  	body, err := u.apicmd(site, cmd, payload)
   200  	if err != nil {
   201  		return err
   202  	}
   203  	if err := json.Unmarshal(body, &v); err != nil {
   204  		log.Println(cmd)
   205  		log.Println(string(body))
   206  		return err
   207  	}
   208  	m := reflect.ValueOf(v).Elem().FieldByName("Meta").Interface().(meta)
   209  	if m.Rc != "ok" {
   210  		return fmt.Errorf("%s returned result code: %s", cmd, m.Rc)
   211  	}
   212  	return nil
   213  }
   214  
   215  // Returns a slice of stations
   216  func (u *Unifi) Sta(site *Site) ([]Sta, error) {
   217  	var response struct {
   218  		Data []Sta
   219  		Meta meta
   220  	}
   221  	err := u.parse(site, "stat/sta", nil, &response)
   222  	for i := range response.Data {
   223  		response.Data[i].u = u
   224  	}
   225  	return response.Data, err
   226  }
   227  
   228  // Returns a map of stations with MAC as a key
   229  func (u *Unifi) StaMap(site *Site) (StaMap, error) {
   230  	sta, err := u.Sta(site)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  	m := make(StaMap)
   235  	for _, s := range sta {
   236  		m[s.Mac] = s
   237  	}
   238  	return m, nil
   239  }
   240  
   241  // Returns a slice of known users
   242  func (u *Unifi) Users(site *Site) ([]User, error) {
   243  	var response struct {
   244  		Data []User
   245  		Meta meta
   246  	}
   247  	err := u.parse(site, "list/user", nil, &response)
   248  	return response.Data, err
   249  }
   250  
   251  // Returns a slice of known portconfigs
   252  func (u *Unifi) PortProfiles(site *Site) ([]PortProfile, error) {
   253  	var response struct {
   254  		Data []PortProfile
   255  		Meta meta
   256  	}
   257  	err := u.parse(site, "list/portconf", nil, &response)
   258  	return response.Data, err
   259  }
   260  
   261  // Returns a map of networkconfigs with ID as key
   262  func (u *Unifi) PortProfileMap(site *Site) (PortprofileMap, error) {
   263  	profiles, err := u.PortProfiles(site)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	m := make(PortprofileMap)
   268  	for _, p := range profiles {
   269  		m[p.ID] = p
   270  	}
   271  	return m, nil
   272  }
   273  
   274  // Returns a map of networkconfigs with ID as key
   275  func (u *Unifi) PortProfile(site *Site, name string) (*PortProfile, error) {
   276  
   277  	profiles, err := u.PortProfiles(site)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	for _, p := range profiles {
   283  		if p.Name == name {
   284  			return &p, err
   285  		}
   286  	}
   287  	return nil, fmt.Errorf("no profile with name: %s", name)
   288  }
   289  
   290  // Sets the portoverrides of a given device
   291  func (u *Unifi) SetPortoverrides(site *Site, deviceid string, o []PortOverride) error {
   292  
   293  	cmd := fmt.Sprintf("rest/device/%s", deviceid)
   294  
   295  	// Create a map with port_overrides as key and a slice of overrides as value
   296  	m := make(map[string][]PortOverride)
   297  	m["port_overrides"] = o
   298  	err := u.apicmdPut(site, cmd, m)
   299  
   300  	return err
   301  }
   302  
   303  // Returns user groups
   304  func (u *Unifi) UserGroups(site *Site) ([]UserGroup, error) {
   305  	var response struct {
   306  		Data []UserGroup
   307  		Meta meta
   308  	}
   309  	err := u.parse(site, "list/usergroup", nil, &response)
   310  	return response.Data, err
   311  }
   312  
   313  // Returns a Wlan config
   314  func (u *Unifi) WlanConf(site *Site) ([]WlanConf, error) {
   315  	var response struct {
   316  		Data []WlanConf
   317  		Meta meta
   318  	}
   319  	err := u.parse(site, "list/wlanconf", nil, &response)
   320  	return response.Data, err
   321  }
   322  
   323  func (u *Unifi) CreateBackup() error {
   324  	return nil
   325  }
   326  
   327  func (u *Unifi) GetBackup() error {
   328  	return nil
   329  }