1package config
  2
  3import (
  4	"strings"
  5
  6	"github.com/charmbracelet/soft-serve/proto"
  7	"github.com/gliderlabs/ssh"
  8	gossh "golang.org/x/crypto/ssh"
  9)
 10
 11var _ proto.Access = &Config{}
 12
 13// AuthRepo grants repo authorization to the given key.
 14func (c *Config) AuthRepo(repo string, pk ssh.PublicKey) proto.AccessLevel {
 15	return c.accessForKey(repo, pk)
 16}
 17
 18// PasswordHandler returns whether or not password access is allowed.
 19func (c *Config) PasswordHandler(ctx ssh.Context, password string) bool {
 20	return (c.AnonAccess != proto.NoAccess) && c.SSH.AllowKeyless &&
 21		c.SSH.AllowPassword && (c.SSH.Password == password)
 22}
 23
 24// KeyboardInteractiveHandler returns whether or not keyboard interactive is allowed.
 25func (c *Config) KeyboardInteractiveHandler(ctx ssh.Context, _ gossh.KeyboardInteractiveChallenge) bool {
 26	return (c.AnonAccess != proto.NoAccess) && c.SSH.AllowKeyless
 27}
 28
 29// PublicKeyHandler returns whether or not the given public key may access the
 30// repo.
 31func (c *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
 32	return c.accessForKey("", pk) != proto.NoAccess
 33}
 34
 35// accessForKey returns the access level for the given repo.
 36//
 37// If repo doesn't exist, then access is based on user's admin privileges, or
 38// config.AnonAccess.
 39// If repo exists, and private, then admins and collabs are allowed access.
 40// If repo exists, and not private, then access is based on config.AnonAccess.
 41func (c *Config) accessForKey(repo string, pk ssh.PublicKey) proto.AccessLevel {
 42	anon := c.AnonAccess
 43	private := c.isPrivate(repo)
 44	// Find user
 45	if pk != nil {
 46		if u := c.findUser(pk); u != nil {
 47			if u.IsAdmin() {
 48				return proto.AdminAccess
 49			}
 50			if c.isCollab(repo, pk) {
 51				if anon > proto.ReadWriteAccess {
 52					return anon
 53				}
 54				return proto.ReadWriteAccess
 55			}
 56			if !private {
 57				if anon > proto.ReadOnlyAccess {
 58					return anon
 59				}
 60				return proto.ReadOnlyAccess
 61			}
 62		}
 63	}
 64	// Don't restrict access to private repos if no users are configured.
 65	// Return anon access level.
 66	if private && c.countUsers() > 0 {
 67		return proto.NoAccess
 68	}
 69	return anon
 70}
 71
 72func (c *Config) countUsers() int {
 73	count, err := c.db.CountUsers()
 74	if err != nil {
 75		return 0
 76	}
 77	return count
 78}
 79
 80func (c *Config) findUser(pk ssh.PublicKey) proto.User {
 81	k := strings.TrimSpace(string(gossh.MarshalAuthorizedKey(pk)))
 82	u, err := c.DB().GetUserByPublicKey(k)
 83	if err != nil {
 84		return nil
 85	}
 86	ks, err := c.DB().GetUserPublicKeys(u)
 87	if err != nil {
 88		return nil
 89	}
 90	return &user{user: u, keys: ks}
 91}
 92
 93func (c *Config) findRepo(repo string) proto.Repository {
 94	r, err := c.DB().Open(repo)
 95	if err != nil {
 96		return nil
 97	}
 98	return r
 99}
100
101func (c *Config) isPrivate(repo string) bool {
102	if r := c.findRepo(repo); r != nil {
103		return r.IsPrivate()
104	}
105	return false
106}
107
108func (c *Config) isCollab(repo string, pk ssh.PublicKey) bool {
109	pks, err := c.DB().ListRepoPublicKeys(repo)
110	if err != nil {
111		return false
112	}
113	for _, k := range pks {
114		if ssh.KeysEqual(pk, k) {
115			return true
116		}
117	}
118	return false
119}