From 1abc141d13f4c33c673e68a7e9c74ce41af44a2e Mon Sep 17 00:00:00 2001 From: Anders Pitman Date: Sat, 24 Oct 2020 16:48:53 -0600 Subject: [PATCH] Make client selection a dropdown Also implemented adding and deleting clients through the API. --- api.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ client.go | 21 +++++++++++++ database.go | 19 ++++++++++-- todo.md | 3 ++ ui_handler.go | 2 ++ webui/index.tmpl | 7 ++++- 6 files changed, 129 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index d44926d..97918a7 100644 --- a/api.go +++ b/api.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "strconv" + "strings" ) type Api struct { @@ -26,6 +27,7 @@ func NewApi(config *BoringProxyConfig, db *Database, auth *Auth, tunMan *TunnelM api := &Api{config, db, auth, tunMan, mux} mux.Handle("/tunnels", http.StripPrefix("/tunnels", http.HandlerFunc(api.handleTunnels))) + mux.Handle("/users/", http.StripPrefix("/users", http.HandlerFunc(api.handleUsers))) return api } @@ -302,6 +304,84 @@ func (a *Api) DeleteSshKey(tokenData TokenData, params url.Values) error { return nil } +func (a *Api) handleUsers(w http.ResponseWriter, r *http.Request) { + token, err := extractToken("access_token", r) + if err != nil { + w.WriteHeader(401) + io.WriteString(w, "Invalid token") + return + } + + tokenData, exists := a.db.GetTokenData(token) + if !exists { + w.WriteHeader(401) + io.WriteString(w, "Failed to get token") + return + } + + path := r.URL.Path + parts := strings.Split(path[1:], "/") + + r.ParseForm() + + if len(parts) == 3 && parts[1] == "clients" { + ownerId := parts[0] + clientId := parts[2] + if r.Method == "PUT" { + err := a.SetClient(tokenData, r.Form, ownerId, clientId) + if err != nil { + w.WriteHeader(500) + io.WriteString(w, err.Error()) + return + } + } else if r.Method == "DELETE" { + err := a.DeleteClient(tokenData, ownerId, clientId) + if err != nil { + w.WriteHeader(500) + io.WriteString(w, err.Error()) + return + } + } + } else { + w.WriteHeader(400) + io.WriteString(w, "Invalid /users//clients request") + return + } +} + +func (a *Api) SetClient(tokenData TokenData, params url.Values, ownerId, clientId string) error { + + if tokenData.Owner != ownerId { + user, _ := a.db.GetUser(tokenData.Owner) + if !user.IsAdmin { + return errors.New("Unauthorized") + } + } + + // TODO: what if two users try to get then set at the same time? + owner, _ := a.db.GetUser(ownerId) + owner.Clients[clientId] = Client{} + a.db.SetUser(ownerId, owner) + + return nil +} + +func (a *Api) DeleteClient(tokenData TokenData, ownerId, clientId string) error { + + if tokenData.Owner != ownerId { + user, _ := a.db.GetUser(tokenData.Owner) + if !user.IsAdmin { + return errors.New("Unauthorized") + } + } + + owner, _ := a.db.GetUser(ownerId) + delete(owner.Clients, clientId) + a.db.SetUser(ownerId, owner) + + return nil +} + func (a *Api) validateToken(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/client.go b/client.go index 63127d6..9062373 100644 --- a/client.go +++ b/client.go @@ -24,6 +24,7 @@ type BoringProxyClient struct { server string token string clientName string + user string cancelFuncs map[string]context.CancelFunc cancelFuncsMutex *sync.Mutex } @@ -33,6 +34,7 @@ func NewBoringProxyClient() *BoringProxyClient { server := flagSet.String("server", "", "boringproxy server") token := flagSet.String("token", "", "Access token") name := flagSet.String("client-name", "", "Client name") + user := flagSet.String("user", "admin", "user") flagSet.Parse(os.Args[2:]) httpClient := &http.Client{} @@ -47,6 +49,7 @@ func NewBoringProxyClient() *BoringProxyClient { server: *server, token: *token, clientName: *name, + user: *user, cancelFuncs: cancelFuncs, cancelFuncsMutex: cancelFuncsMutex, } @@ -54,6 +57,24 @@ func NewBoringProxyClient() *BoringProxyClient { func (c *BoringProxyClient) RunPuppetClient() { + url := fmt.Sprintf("https://%s/api/users/%s/clients/%s", c.server, c.user, c.clientName) + clientReq, err := http.NewRequest("PUT", url, nil) + if err != nil { + log.Fatal("Failed to PUT client") + } + if len(c.token) > 0 { + clientReq.Header.Add("Authorization", "bearer "+c.token) + } + resp, err := c.httpClient.Do(clientReq) + if err != nil { + log.Fatal("Failed to PUT client") + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + log.Fatal("Failed to PUT client") + } + for { err := c.PollTunnels() if err != nil { diff --git a/database.go b/database.go index 641019a..dc55d88 100644 --- a/database.go +++ b/database.go @@ -21,7 +21,8 @@ type TokenData struct { } type User struct { - IsAdmin bool `json:"is_admin"` + IsAdmin bool `json:"is_admin"` + Clients map[string]Client `json:"clients"` } type SshKey struct { @@ -29,6 +30,9 @@ type SshKey struct { Key string `json:"key"` } +type Client struct { +} + type Tunnel struct { Owner string `json:"owner"` Domain string `json:"domain"` @@ -222,6 +226,16 @@ func (d *Database) GetUser(username string) (User, bool) { return user, true } +func (d *Database) SetUser(username string, user User) error { + d.mutex.Lock() + defer d.mutex.Unlock() + + d.Users[username] = user + d.persist() + + return nil +} + func (d *Database) AddUser(username string, isAdmin bool) error { d.mutex.Lock() defer d.mutex.Unlock() @@ -233,7 +247,8 @@ func (d *Database) AddUser(username string, isAdmin bool) error { } d.Users[username] = User{ - isAdmin, + IsAdmin: isAdmin, + Clients: make(map[string]Client), } d.persist() diff --git a/todo.md b/todo.md index 9ef09f8..e583e22 100644 --- a/todo.md +++ b/todo.md @@ -18,3 +18,6 @@ to manually combine them for custom keys. * Send public key back to clients, so they can automatically try to find the matching private key. +* We might need some sort of a transaction or atomicity system on the db to + prevent things like 2 people setting the user at the same time and one losing + their changes. diff --git a/ui_handler.go b/ui_handler.go index 5ab7fac..8c0245c 100644 --- a/ui_handler.go +++ b/ui_handler.go @@ -36,6 +36,7 @@ type IndexData struct { Tokens map[string]TokenData SshKeys map[string]SshKey Users map[string]User + UserId string IsAdmin bool QrCodes map[string]template.URL } @@ -263,6 +264,7 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request Tokens: tokens, SshKeys: h.api.GetSshKeys(tokenData), Users: users, + UserId: tokenData.Owner, IsAdmin: user.IsAdmin, QrCodes: qrCodes, } diff --git a/webui/index.tmpl b/webui/index.tmpl index fc42ea9..dc5d6c7 100644 --- a/webui/index.tmpl +++ b/webui/index.tmpl @@ -80,7 +80,12 @@
- +