mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-05 23:07:44 +00:00
ipn/localapi: add LocalAPI endpoints for profile switching
Exposes a REST-y API for interacting with the profile switching introduced in #6022. Updates #713 Signed-off-by: Mihai Parparita <mihai@tailscale.com>
This commit is contained in:
parent
3271daf7a3
commit
0cc65b4bbe
@ -25,6 +25,7 @@
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health"
|
||||
@ -53,6 +54,7 @@
|
||||
"cert/": (*Handler).serveCert,
|
||||
"file-put/": (*Handler).serveFilePut,
|
||||
"files/": (*Handler).serveFiles,
|
||||
"profiles/": (*Handler).serveProfiles,
|
||||
|
||||
// The other /localapi/v0/NAME handlers are exact matches and contain only NAME
|
||||
// without a trailing slash:
|
||||
@ -1099,6 +1101,89 @@ func (h *Handler) serveTKADisable(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
// serveProfiles serves profile switching-related endpoints. Supported methods
|
||||
// and paths are:
|
||||
// - GET /profiles/: list all profiles (JSON-encoded array of ipn.LoginProfiles)
|
||||
// - PUT /profiles/: add new profile (no response). A separate
|
||||
// StartLoginInteractive() is needed to populate and persist the new profile.
|
||||
// - GET /profiles/current: current profile (JSON-ecoded ipn.LoginProfile)
|
||||
// - GET /profiles/<id>: output profile (JSON-ecoded ipn.LoginProfile)
|
||||
// - POST /profiles/<id>: switch to profile (no response)
|
||||
// - DELETE /profiles/<id>: delete profile (no response)
|
||||
func (h *Handler) serveProfiles(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "profiles access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
suffix, ok := strs.CutPrefix(r.URL.EscapedPath(), "/localapi/v0/profiles/")
|
||||
if !ok {
|
||||
panic("misconfigured")
|
||||
}
|
||||
if suffix == "" {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(h.b.ListProfiles())
|
||||
case http.MethodPut:
|
||||
err := h.b.NewProfile()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
default:
|
||||
http.Error(w, "use GET or PUT", http.StatusMethodNotAllowed)
|
||||
}
|
||||
return
|
||||
}
|
||||
suffix, err := url.PathUnescape(suffix)
|
||||
if err != nil {
|
||||
http.Error(w, "bad profile ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if suffix == "current" {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(h.b.CurrentProfile())
|
||||
default:
|
||||
http.Error(w, "use GET", http.StatusMethodNotAllowed)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
profileID := ipn.ProfileID(suffix)
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
profiles := h.b.ListProfiles()
|
||||
profileIndex := slices.IndexFunc(profiles, func(p ipn.LoginProfile) bool {
|
||||
return p.ID == profileID
|
||||
})
|
||||
if profileIndex == -1 {
|
||||
http.Error(w, "Profile not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(profiles[profileIndex])
|
||||
case http.MethodPost:
|
||||
err := h.b.SwitchProfile(profileID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
case http.MethodDelete:
|
||||
err := h.b.DeleteProfile(profileID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
default:
|
||||
http.Error(w, "use POST or DELETE", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func defBool(a string, def bool) bool {
|
||||
if a == "" {
|
||||
return def
|
||||
|
Loading…
x
Reference in New Issue
Block a user