DodoApp: Sync deleted users and keys
Change-Id: Ib71fbdd142fd13fbaa4c24fb8971afe157a184b6
diff --git a/core/installer/soft/client.go b/core/installer/soft/client.go
index 3e58279..a285ffd 100644
--- a/core/installer/soft/client.go
+++ b/core/installer/soft/client.go
@@ -3,7 +3,6 @@
import (
"errors"
"fmt"
- "golang.org/x/crypto/ssh"
"log"
"net"
"os"
@@ -12,6 +11,8 @@
"strings"
"time"
+ "golang.org/x/crypto/ssh"
+
"github.com/cenkalti/backoff/v4"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
@@ -33,9 +34,12 @@
AddRepository(name string) error
UserExists(name string) (bool, error)
FindUser(pubKey string) (string, error)
+ GetAllUsers() ([]string, error)
AddUser(name, pubKey string) error
+ RemoveUser(user string) error
AddPublicKey(user string, pubKey string) error
RemovePublicKey(user string, pubKey string) error
+ GetUserPublicKeys(user string) ([]string, error)
MakeUserAdmin(name string) error
AddReadWriteCollaborator(repo, user string) error
AddReadOnlyCollaborator(repo, user string) error
@@ -96,6 +100,15 @@
return ss.signer
}
+func (ss *realClient) GetAllUsers() ([]string, error) {
+ log.Printf("Getting all users")
+ out, err := ss.RunCommand("user", "list")
+ if err != nil {
+ return nil, err
+ }
+ return strings.Fields(out), nil
+}
+
func (ss *realClient) UserExists(name string) (bool, error) {
log.Printf("Checking user exists %s", name)
out, err := ss.RunCommand("user", "list")
@@ -132,6 +145,12 @@
return ss.AddPublicKey(name, pubKey)
}
+func (ss *realClient) RemoveUser(user string) error {
+ log.Printf("Removing user: %s\n", user)
+ _, err := ss.RunCommand("user", "delete", user)
+ return err
+}
+
func (ss *realClient) MakeUserAdmin(name string) error {
log.Printf("Making user %s admin", name)
_, err := ss.RunCommand("user", "set-admin", name, "true")
@@ -150,6 +169,31 @@
return err
}
+func (ss *realClient) GetUserPublicKeys(user string) ([]string, error) {
+ log.Printf("Getting public keys for user: %s\n", user)
+ out, err := ss.RunCommand("user", "info", user)
+ if err != nil {
+ return nil, err
+ }
+ return extractPublicKeys(out), nil
+}
+
+func extractPublicKeys(userInfo string) []string {
+ var keys []string
+ lines := strings.Split(userInfo, "\n")
+ gettingKeys := false
+ for _, line := range lines {
+ if strings.HasPrefix(line, "Public keys:") {
+ gettingKeys = true
+ continue
+ }
+ if gettingKeys {
+ keys = append(keys, strings.TrimSpace(line))
+ }
+ }
+ return keys
+}
+
func (ss *realClient) RunCommand(args ...string) (string, error) {
cmd := strings.Join(args, " ")
log.Printf("Running command %s", cmd)
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 488047c..6ccbb6a 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -7,7 +7,6 @@
"encoding/json"
"errors"
"fmt"
- "golang.org/x/crypto/bcrypt"
"html/template"
"io"
"io/fs"
@@ -17,6 +16,9 @@
"sync"
"time"
+ "golang.org/x/crypto/bcrypt"
+ "golang.org/x/exp/rand"
+
"github.com/giolekva/pcloud/core/installer"
"github.com/giolekva/pcloud/core/installer/soft"
@@ -203,12 +205,18 @@
}()
if !s.external {
go func() {
+ rand.Seed(uint64(time.Now().UnixNano()))
s.syncUsers()
// TODO(dtabidze): every sync delay should be randomized to avoid all client
// applications hitting memberships service at the same time.
// For every next sync new delay should be randomly generated from scratch.
// We can choose random delay from 1 to 2 minutes.
- for range time.Tick(1 * time.Minute) {
+ // for range time.Tick(1 * time.Minute) {
+ // s.syncUsers()
+ // }
+ for {
+ delay := time.Duration(rand.Intn(60)+60) * time.Second
+ time.Sleep(delay)
s.syncUsers()
}
}()
@@ -1056,30 +1064,67 @@
fmt.Println(err)
return
}
+ validUsernames := make(map[string]user)
+ for _, u := range users {
+ validUsernames[u.Username] = u
+ }
+ allClientUsers, err := s.client.GetAllUsers()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ keyToUser := make(map[string]string)
+ for _, clientUser := range allClientUsers {
+ userData, ok := validUsernames[clientUser]
+ if !ok {
+ if err := s.client.RemoveUser(clientUser); err != nil {
+ fmt.Println(err)
+ return
+ }
+ } else {
+ existingKeys, err := s.client.GetUserPublicKeys(clientUser)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ for _, existingKey := range existingKeys {
+ cleanKey := CleanKey(existingKey)
+ keyOk := slices.ContainsFunc(userData.SSHPublicKeys, func(key string) bool {
+ return cleanKey == CleanKey(key)
+ })
+ if !keyOk {
+ if err := s.client.RemovePublicKey(clientUser, existingKey); err != nil {
+ fmt.Println(err)
+ }
+ } else {
+ keyToUser[cleanKey] = clientUser
+ }
+ }
+ }
+ }
for _, u := range users {
if len(u.SSHPublicKeys) == 0 {
continue
}
- if ok, err := s.client.UserExists(u.Username); err != nil {
+ ok, err := s.client.UserExists(u.Username)
+ if err != nil {
fmt.Println(err)
return
- } else if !ok {
- for i, k := range u.SSHPublicKeys {
- if i == 0 {
- if err := s.client.AddUser(u.Username, k); err != nil {
- fmt.Println(err)
- return
- }
- } else {
- if err := s.client.AddPublicKey(u.Username, k); err != nil {
- fmt.Println(err)
- // TODO(dtabidze): If current public key is already registered
- // with Git server, this method call will return an error.
- // We need to differentiate such errors, and only add key which
- // are missing.
- continue // return
- }
- // TODO(dtabidze): Implement RemovePublicKey
+ }
+ if !ok {
+ if err := s.client.AddUser(u.Username, u.SSHPublicKeys[0]); err != nil {
+ fmt.Println(err)
+ return
+ }
+ } else {
+ for _, key := range u.SSHPublicKeys {
+ cleanKey := CleanKey(key)
+ if user, ok := keyToUser[cleanKey]; ok && u.Username == user {
+ panic("MUST NOT REACH!")
+ }
+ if err := s.client.AddPublicKey(u.Username, key); err != nil {
+ fmt.Println(err)
+ return
}
}
}
@@ -1100,3 +1145,11 @@
}
}
}
+
+func CleanKey(key string) string {
+ fields := strings.Fields(key)
+ if len(fields) < 2 {
+ return key
+ }
+ return fields[0] + " " + fields[1]
+}
diff --git a/core/installer/welcome/env_test.go b/core/installer/welcome/env_test.go
index 1ab6b39..040607d 100644
--- a/core/installer/welcome/env_test.go
+++ b/core/installer/welcome/env_test.go
@@ -3,7 +3,6 @@
import (
"bytes"
"encoding/json"
- "golang.org/x/crypto/ssh"
"io"
"io/fs"
"log"
@@ -14,11 +13,11 @@
"testing"
"time"
+ "golang.org/x/crypto/ssh"
+
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-billy/v5/util"
- // "github.com/go-git/go-git/v5"
- // "github.com/go-git/go-git/v5/storage/memory"
"github.com/giolekva/pcloud/core/installer"
"github.com/giolekva/pcloud/core/installer/soft"
@@ -137,14 +136,26 @@
return "", nil
}
+func (f fakeSoftServeClient) GetAllUsers() ([]string, error) {
+ return []string{}, nil
+}
+
func (f fakeSoftServeClient) AddUser(name, pubKey string) error {
return nil
}
+func (f fakeSoftServeClient) RemoveUser(name string) error {
+ return nil
+}
+
func (f fakeSoftServeClient) AddPublicKey(user string, pubKey string) error {
return nil
}
+func (f fakeSoftServeClient) GetUserPublicKeys(username string) ([]string, error) {
+ return []string{}, nil
+}
+
func (f fakeSoftServeClient) RemovePublicKey(user string, pubKey string) error {
return nil
}