@@ -59,43 +59,79 @@ func (cfg *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
return cfg.accessForKey("", pk) != gm.NoAccess
}
+func (cfg *Config) anonAccessLevel() gm.AccessLevel {
+ switch cfg.AnonAccess {
+ case "no-access":
+ return gm.NoAccess
+ case "read-only":
+ return gm.ReadOnlyAccess
+ case "read-write":
+ return gm.ReadWriteAccess
+ case "admin-access":
+ return gm.AdminAccess
+ default:
+ return gm.NoAccess
+ }
+}
+
+// accessForKey returns the access level for the given repo.
+//
+// If repo doesn't exist, then access is based on user's admin privileges, or
+// config.AnonAccess.
+// If repo exists, and private, then admins and collabs are allowed access.
+// If repo exists, and not private, then access is based on config.AnonAccess.
func (cfg *Config) accessForKey(repo string, pk ssh.PublicKey) gm.AccessLevel {
- private := cfg.isPrivate(repo)
- for _, u := range cfg.Users {
- for _, k := range u.PublicKeys {
+ var u *User
+ var r *RepoConfig
+ anon := cfg.anonAccessLevel()
+OUT:
+ // Find user
+ for _, user := range cfg.Users {
+ for _, k := range user.PublicKeys {
apk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(strings.TrimSpace(k)))
if err != nil {
log.Printf("error: malformed authorized key: '%s'", k)
return gm.NoAccess
}
if ssh.KeysEqual(pk, apk) {
- if u.Admin {
- return gm.AdminAccess
- }
- for _, r := range u.CollabRepos {
- if repo == r {
- return gm.ReadWriteAccess
- }
- }
- if !private {
- return gm.ReadOnlyAccess
- }
+ us := user
+ u = &us
+ break OUT
}
}
}
- if private && len(cfg.Users) > 0 {
- return gm.NoAccess
+ // Find repo
+ for _, rp := range cfg.Repos {
+ if rp.Repo == repo {
+ rr := rp
+ r = &rr
+ break
+ }
}
- switch cfg.AnonAccess {
- case "no-access":
- return gm.NoAccess
- case "read-only":
- return gm.ReadOnlyAccess
- case "read-write":
- return gm.ReadWriteAccess
- case "admin-access":
+ if u != nil && u.Admin {
return gm.AdminAccess
- default:
- return gm.NoAccess
}
+ if r == nil || len(cfg.Users) == 0 {
+ return anon
+ }
+ // Collabs default access is read-write
+ if u != nil {
+ ac := gm.ReadWriteAccess
+ if anon > ac {
+ ac = anon
+ }
+ for _, rr := range u.CollabRepos {
+ if rr == r.Repo {
+ return ac
+ }
+ }
+ }
+ // Users default access is read-only
+ if !r.Private {
+ if anon > gm.ReadOnlyAccess {
+ return anon
+ }
+ return gm.ReadOnlyAccess
+ }
+ return gm.NoAccess
}
@@ -27,11 +27,6 @@ import (
"github.com/go-git/go-git/v5/storage/memory"
)
-var (
- // ErrNoConfig is returned when no config file is found.
- ErrNoConfig = errors.New("no config file found")
-)
-
// Config is the Soft Serve configuration.
type Config struct {
Name string `yaml:"name" json:"name"`
@@ -40,7 +35,7 @@ type Config struct {
AnonAccess string `yaml:"anon-access" json:"anon-access"`
AllowKeyless bool `yaml:"allow-keyless" json:"allow-keyless"`
Users []User `yaml:"users" json:"users"`
- Repos []MenuRepo `yaml:"repos" json:"repos"`
+ Repos []RepoConfig `yaml:"repos" json:"repos"`
Source *RepoSource `yaml:"-" json:"-"`
Cfg *config.Config `yaml:"-" json:"-"`
mtx sync.Mutex
@@ -54,8 +49,8 @@ type User struct {
CollabRepos []string `yaml:"collab-repos" json:"collab-repos"`
}
-// Repo contains repository configuration information.
-type MenuRepo struct {
+// RepoConfig is a repository configuration.
+type RepoConfig struct {
Name string `yaml:"name" json:"name"`
Repo string `yaml:"repo" json:"repo"`
Note string `yaml:"note" json:"note"`
@@ -128,38 +123,45 @@ func NewConfig(cfg *config.Config) (*Config, error) {
return c, nil
}
-// Reload reloads the configuration.
-func (cfg *Config) Reload() error {
- cfg.mtx.Lock()
- defer cfg.mtx.Unlock()
- err := cfg.Source.LoadRepos()
+func (cfg *Config) readConfig(repo string, v interface{}) error {
+ cr, err := cfg.Source.GetRepo(repo)
if err != nil {
return err
}
- cr, err := cfg.Source.GetRepo("config")
- if err != nil {
- return err
- }
- cy, _, err := cr.LatestFile("config.yaml")
+ cy, _, err := cr.LatestFile(repo + ".yaml")
if err != nil && !errors.Is(err, git.ErrFileNotFound) {
- return fmt.Errorf("error reading config.yaml: %w", err)
+ return fmt.Errorf("error reading %s.yaml: %w", repo, err)
}
- cj, _, err := cr.LatestFile("config.json")
+ cj, _, err := cr.LatestFile(repo + ".json")
if err != nil && !errors.Is(err, git.ErrFileNotFound) {
- return fmt.Errorf("error reading config.json: %w", err)
+ return fmt.Errorf("error reading %s.json: %w", repo, err)
}
if cy != "" {
- err = yaml.Unmarshal([]byte(cy), cfg)
+ err = yaml.Unmarshal([]byte(cy), v)
if err != nil {
- return fmt.Errorf("bad yaml in config.yaml: %s", err)
+ return fmt.Errorf("bad yaml in %s.yaml: %s", repo, err)
}
} else if cj != "" {
- err = json.Unmarshal([]byte(cj), cfg)
+ err = json.Unmarshal([]byte(cj), v)
if err != nil {
- return fmt.Errorf("bad json in config.json: %s", err)
+ return fmt.Errorf("bad json in %s.json: %s", repo, err)
}
} else {
- return ErrNoConfig
+ return fmt.Errorf("no config file found for %q", repo)
+ }
+ return nil
+}
+
+// Reload reloads the configuration.
+func (cfg *Config) Reload() error {
+ cfg.mtx.Lock()
+ defer cfg.mtx.Unlock()
+ err := cfg.Source.LoadRepos()
+ if err != nil {
+ return err
+ }
+ if err := cfg.readConfig("config", cfg); err != nil {
+ return fmt.Errorf("error reading config: %w", err)
}
for _, r := range cfg.Source.AllRepos() {
name := r.Name()
@@ -276,15 +278,6 @@ func (cfg *Config) createDefaultConfigRepo(yaml string) error {
return cfg.Reload()
}
-func (cfg *Config) isPrivate(repo string) bool {
- for _, r := range cfg.Repos {
- if r.Repo == repo {
- return r.Private
- }
- }
- return false
-}
-
func templatize(mdt string, tmpl interface{}) (string, error) {
t, err := template.New("readme").Parse(mdt)
if err != nil {