d88ccb97d3e756be489d680d29e7dc24b9792fb3

Author
Ayman Bagabas <ayman.bagabas@gmail.com>
Committer
Ayman Bagabas <ayman.bagabas@gmail.com>
Date

Message

ref(config): clarify repo auth for key

Fix edge cases when anonAccess is greater than collab

Diff

This diff is truncated to protect this page.

  1diff --git a/config/auth.go b/config/auth.go
  2index a1ba59fe9de47cd8a9ca1981f9326da4560158e4..0bbcaa6e1dbaaebc7ac1d3ec966a709bc540d3b4 100644
  3--- a/config/auth.go
  4+++ b/config/auth.go
  5@@ -59,43 +59,79 @@ func (cfg *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
  6 	return cfg.accessForKey("", pk) != gm.NoAccess
  7 }
  8 
  9+func (cfg *Config) anonAccessLevel() gm.AccessLevel {
 10+	switch cfg.AnonAccess {
 11+	case "no-access":
 12+		return gm.NoAccess
 13+	case "read-only":
 14+		return gm.ReadOnlyAccess
 15+	case "read-write":
 16+		return gm.ReadWriteAccess
 17+	case "admin-access":
 18+		return gm.AdminAccess
 19+	default:
 20+		return gm.NoAccess
 21+	}
 22+}
 23+
 24+// accessForKey returns the access level for the given repo.
 25+//
 26+// If repo doesn't exist, then access is based on user's admin privileges, or
 27+// config.AnonAccess.
 28+// If repo exists, and private, then admins and collabs are allowed access.
 29+// If repo exists, and not private, then access is based on config.AnonAccess.
 30 func (cfg *Config) accessForKey(repo string, pk ssh.PublicKey) gm.AccessLevel {
 31-	private := cfg.isPrivate(repo)
 32-	for _, u := range cfg.Users {
 33-		for _, k := range u.PublicKeys {
 34+	var u *User
 35+	var r *RepoConfig
 36+	anon := cfg.anonAccessLevel()
 37+OUT:
 38+	// Find user
 39+	for _, user := range cfg.Users {
 40+		for _, k := range user.PublicKeys {
 41 			apk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(strings.TrimSpace(k)))
 42 			if err != nil {
 43 				log.Printf("error: malformed authorized key: '%s'", k)
 44 				return gm.NoAccess
 45 			}
 46 			if ssh.KeysEqual(pk, apk) {
 47-				if u.Admin {
 48-					return gm.AdminAccess
 49-				}
 50-				for _, r := range u.CollabRepos {
 51-					if repo == r {
 52-						return gm.ReadWriteAccess
 53-					}
 54-				}
 55-				if !private {
 56-					return gm.ReadOnlyAccess
 57-				}
 58+				us := user
 59+				u = &us
 60+				break OUT
 61 			}
 62 		}
 63 	}
 64-	if private && len(cfg.Users) > 0 {
 65-		return gm.NoAccess
 66+	// Find repo
 67+	for _, rp := range cfg.Repos {
 68+		if rp.Repo == repo {
 69+			rr := rp
 70+			r = &rr
 71+			break
 72+		}
 73 	}
 74-	switch cfg.AnonAccess {
 75-	case "no-access":
 76-		return gm.NoAccess
 77-	case "read-only":
 78-		return gm.ReadOnlyAccess
 79-	case "read-write":
 80-		return gm.ReadWriteAccess
 81-	case "admin-access":
 82+	if u != nil && u.Admin {
 83 		return gm.AdminAccess
 84-	default:
 85-		return gm.NoAccess
 86 	}
 87+	if r == nil || len(cfg.Users) == 0 {
 88+		return anon
 89+	}
 90+	// Collabs default access is read-write
 91+	if u != nil {
 92+		ac := gm.ReadWriteAccess
 93+		if anon > ac {
 94+			ac = anon
 95+		}
 96+		for _, rr := range u.CollabRepos {
 97+			if rr == r.Repo {
 98+				return ac
 99+			}
100+		}
101+	}
102+	// Users default access is read-only
103+	if !r.Private {
104+		if anon > gm.ReadOnlyAccess {
105diff --git a/config/config.go b/config/config.go
106index 85e7ce831e16c5c3b075324a1ea6a9d0cc0e3ce3..da6ff3e5d0c9d657e7d3e8c01d89197350c54946 100644
107--- a/config/config.go
108+++ b/config/config.go
109@@ -27,11 +27,6 @@ import (
110 	"github.com/go-git/go-git/v5/storage/memory"
111 )
112 
113-var (
114-	// ErrNoConfig is returned when no config file is found.
115-	ErrNoConfig = errors.New("no config file found")
116-)
117-
118 // Config is the Soft Serve configuration.
119 type Config struct {
120 	Name         string         `yaml:"name" json:"name"`
121@@ -40,7 +35,7 @@ type Config struct {
122 	AnonAccess   string         `yaml:"anon-access" json:"anon-access"`
123 	AllowKeyless bool           `yaml:"allow-keyless" json:"allow-keyless"`
124 	Users        []User         `yaml:"users" json:"users"`
125-	Repos        []MenuRepo     `yaml:"repos" json:"repos"`
126+	Repos        []RepoConfig   `yaml:"repos" json:"repos"`
127 	Source       *RepoSource    `yaml:"-" json:"-"`
128 	Cfg          *config.Config `yaml:"-" json:"-"`
129 	mtx          sync.Mutex
130@@ -54,8 +49,8 @@ type User struct {
131 	CollabRepos []string `yaml:"collab-repos" json:"collab-repos"`
132 }
133 
134-// Repo contains repository configuration information.
135-type MenuRepo struct {
136+// RepoConfig is a repository configuration.
137+type RepoConfig struct {
138 	Name    string `yaml:"name" json:"name"`
139 	Repo    string `yaml:"repo" json:"repo"`
140 	Note    string `yaml:"note" json:"note"`
141@@ -128,38 +123,45 @@ func NewConfig(cfg *config.Config) (*Config, error) {
142 	return c, nil
143 }
144 
145-// Reload reloads the configuration.
146-func (cfg *Config) Reload() error {
147-	cfg.mtx.Lock()
148-	defer cfg.mtx.Unlock()
149-	err := cfg.Source.LoadRepos()
150+func (cfg *Config) readConfig(repo string, v interface{}) error {
151+	cr, err := cfg.Source.GetRepo(repo)
152 	if err != nil {
153 		return err
154 	}
155-	cr, err := cfg.Source.GetRepo("config")
156-	if err != nil {
157-		return err
158-	}
159-	cy, _, err := cr.LatestFile("config.yaml")
160+	cy, _, err := cr.LatestFile(repo + ".yaml")
161 	if err != nil && !errors.Is(err, git.ErrFileNotFound) {
162-		return fmt.Errorf("error reading config.yaml: %w", err)
163+		return fmt.Errorf("error reading %s.yaml: %w", repo, err)
164 	}
165-	cj, _, err := cr.LatestFile("config.json")
166+	cj, _, err := cr.LatestFile(repo + ".json")
167 	if err != nil && !errors.Is(err, git.ErrFileNotFound) {
168-		return fmt.Errorf("error reading config.json: %w", err)
169+		return fmt.Errorf("error reading %s.json: %w", repo, err)
170 	}
171 	if cy != "" {
172-		err = yaml.Unmarshal([]byte(cy), cfg)
173+		err = yaml.Unmarshal([]byte(cy), v)
174 		if err != nil {
175-			return fmt.Errorf("bad yaml in config.yaml: %s", err)
176+			return fmt.Errorf("bad yaml in %s.yaml: %s", repo, err)
177 		}
178 	} else if cj != "" {
179-		err = json.Unmarshal([]byte(cj), cfg)
180+		err = json.Unmarshal([]byte(cj), v)
181 		if err != nil {
182-			return fmt.Errorf("bad json in config.json: %s", err)
183+			return fmt.Errorf("bad json in %s.json: %s", repo, err)
184 		}
185 	} else {
186-		return ErrNoConfig
187+		return fmt.Errorf("no config file found for %q", repo)
188+	}
189+	return nil
190+}
191+
192+// Reload reloads the configuration.
193+func (cfg *Config) Reload() error {
194+	cfg.mtx.Lock()
195+	defer cfg.mtx.Unlock()
196+	err := cfg.Source.LoadRepos()
197+	if err != nil {
198+		return err
199+	}
200+	if err := cfg.readConfig("config", cfg); err != nil {
201+		return fmt.Errorf("error reading config: %w", err)
202 	}
203 	for _, r := range cfg.Source.AllRepos() {
204 		name := r.Name()
205@@ -276,15 +278,6 @@ func (cfg *Config) createDefaultConfigRepo(yaml string) error {
206 	return cfg.Reload()
207 }
208