Compare commits

...

1 Commits

Author SHA1 Message Date
Mihai Parparita 6fa376ae50 ipn/localapi: add LocalAPI support for removing all profiles
Will be used to implement a "Log out all accounts" feature.

Updates #713

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-11-21 15:58:32 -08:00
4 changed files with 46 additions and 3 deletions

View File

@ -4204,6 +4204,16 @@ func (b *LocalBackend) DeleteProfile(p ipn.ProfileID) error {
return b.resetForProfileChangeLockedOnEntry() return b.resetForProfileChangeLockedOnEntry()
} }
// DeleteProfiles removes all known profiles.
func (b *LocalBackend) DeleteAllProfiles() error {
b.mu.Lock()
if err := b.pm.DeleteAllProfiles(); err != nil {
b.mu.Unlock()
return err
}
return b.resetForProfileChangeLockedOnEntry()
}
// CurrentProfile returns the current LoginProfile. // CurrentProfile returns the current LoginProfile.
// The value may be zero if the profile is not persisted. // The value may be zero if the profile is not persisted.
func (b *LocalBackend) CurrentProfile() ipn.LoginProfile { func (b *LocalBackend) CurrentProfile() ipn.LoginProfile {

View File

@ -12,6 +12,7 @@ import (
"runtime" "runtime"
"time" "time"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
@ -349,6 +350,21 @@ func (pm *profileManager) DeleteProfile(id ipn.ProfileID) error {
return pm.writeKnownProfiles() return pm.writeKnownProfiles()
} }
// DeleteAllProfiles removes all known profiles and switches to a new empty
// profile.
func (pm *profileManager) DeleteAllProfiles() error {
metricDeleteAllProfile.Add(1)
for _, kp := range pm.knownProfiles {
if err := pm.store.WriteState(kp.Key, nil); err != nil {
return err
}
}
pm.NewProfile()
maps.Clear(pm.knownProfiles)
return pm.writeKnownProfiles()
}
func (pm *profileManager) writeKnownProfiles() error { func (pm *profileManager) writeKnownProfiles() error {
b, err := json.Marshal(pm.knownProfiles) b, err := json.Marshal(pm.knownProfiles)
if err != nil { if err != nil {
@ -515,6 +531,7 @@ var (
metricNewProfile = clientmetric.NewCounter("profiles_new") metricNewProfile = clientmetric.NewCounter("profiles_new")
metricSwitchProfile = clientmetric.NewCounter("profiles_switch") metricSwitchProfile = clientmetric.NewCounter("profiles_switch")
metricDeleteProfile = clientmetric.NewCounter("profiles_delete") metricDeleteProfile = clientmetric.NewCounter("profiles_delete")
metricDeleteAllProfile = clientmetric.NewCounter("profiles_delete_all")
metricMigration = clientmetric.NewCounter("profiles_migration") metricMigration = clientmetric.NewCounter("profiles_migration")
metricMigrationError = clientmetric.NewCounter("profiles_migration_error") metricMigrationError = clientmetric.NewCounter("profiles_migration_error")

View File

@ -164,6 +164,14 @@ func TestProfileManagement(t *testing.T) {
delete(wantProfiles, "tagged-node.2.ts.net") delete(wantProfiles, "tagged-node.2.ts.net")
wantCurProfile = "user@2.example.com" wantCurProfile = "user@2.example.com"
checkProfiles(t) checkProfiles(t)
t.Logf("Delete all")
pm.DeleteAllProfiles()
wantProfiles = map[string]ipn.PrefsView{
"": emptyPrefs,
}
wantCurProfile = ""
checkProfiles(t)
} }
// TestProfileManagementWindows tests going into and out of Unattended mode on // TestProfileManagementWindows tests going into and out of Unattended mode on

View File

@ -1156,6 +1156,7 @@ func (h *Handler) serveTKADisable(w http.ResponseWriter, r *http.Request) {
// - GET /profiles/: list all profiles (JSON-encoded array of ipn.LoginProfiles) // - GET /profiles/: list all profiles (JSON-encoded array of ipn.LoginProfiles)
// - PUT /profiles/: add new profile (no response). A separate // - PUT /profiles/: add new profile (no response). A separate
// StartLoginInteractive() is needed to populate and persist the new profile. // StartLoginInteractive() is needed to populate and persist the new profile.
// - DELETE /profiles/: delete all profile (no response)
// - GET /profiles/current: current profile (JSON-ecoded ipn.LoginProfile) // - GET /profiles/current: current profile (JSON-ecoded ipn.LoginProfile)
// - GET /profiles/<id>: output profile (JSON-ecoded ipn.LoginProfile) // - GET /profiles/<id>: output profile (JSON-ecoded ipn.LoginProfile)
// - POST /profiles/<id>: switch to profile (no response) // - POST /profiles/<id>: switch to profile (no response)
@ -1182,6 +1183,13 @@ func (h *Handler) serveProfiles(w http.ResponseWriter, r *http.Request) {
return return
} }
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
case http.MethodDelete:
err := h.b.DeleteAllProfiles()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
default: default:
http.Error(w, "use GET or PUT", http.StatusMethodNotAllowed) http.Error(w, "use GET or PUT", http.StatusMethodNotAllowed)
} }