1package config
2
3import (
4 "log"
5 "strings"
6
7 "github.com/charmbracelet/soft-serve/proto"
8 "github.com/gliderlabs/ssh"
9 gossh "golang.org/x/crypto/ssh"
10)
11
12var _ proto.Access = &Config{}
13
14// AuthRepo grants repo authorization to the given key.
15func (c *Config) AuthRepo(repo string, pk ssh.PublicKey) proto.AccessLevel {
16 return c.accessForKey(repo, pk)
17}
18
19// User returns the user for the given public key. Can return nil if no user is
20// found.
21func (c *Config) User(pk ssh.PublicKey) (proto.User, error) {
22 k := authorizedKey(pk)
23 u, err := c.DB().GetUserByPublicKey(k)
24 if err != nil {
25 log.Printf("error getting user for key: %s", err)
26 return nil, err
27 }
28 if u == nil {
29 return nil, nil
30 }
31 return &user{
32 cfg: c,
33 user: u,
34 }, nil
35}
36
37// IsCollab returns whether or not the given key is a collaborator on the given
38// repository.
39func (c *Config) IsCollab(repo string, pk ssh.PublicKey) bool {
40 if c.isInitialAdminKey(pk) {
41 return true
42 }
43
44 isCollab, err := c.DB().IsRepoPublicKeyCollab(repo, authorizedKey(pk))
45 if err != nil {
46 log.Printf("error checking if key is repo collab: %v", err)
47 return false
48 }
49 if isCollab {
50 return true
51 }
52 return false
53}
54
55// IsAdmin returns whether or not the given key is an admin.
56func (c *Config) IsAdmin(pk ssh.PublicKey) bool {
57 if c.isInitialAdminKey(pk) {
58 return true
59 }
60
61 u, err := c.User(pk)
62 if err != nil {
63 log.Printf("error getting user for key: %s", err)
64 return false
65 }
66 return u.IsAdmin()
67}
68
69// PasswordHandler returns whether or not password access is allowed.
70func (c *Config) PasswordHandler(ctx ssh.Context, password string) bool {
71 return (c.AnonAccess != proto.NoAccess) && c.SSH.AllowKeyless &&
72 c.SSH.AllowPassword && (c.SSH.Password == password)
73}
74
75// KeyboardInteractiveHandler returns whether or not keyboard interactive is allowed.
76func (c *Config) KeyboardInteractiveHandler(ctx ssh.Context, _ gossh.KeyboardInteractiveChallenge) bool {
77 return (c.AnonAccess != proto.NoAccess) && c.SSH.AllowKeyless
78}
79
80// PublicKeyHandler returns whether or not the given public key may access the
81// repo.
82func (c *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
83 return c.accessForKey("", pk) != proto.NoAccess
84}
85
86// accessForKey returns the access level for the given repo.
87//
88// If repo doesn't exist, then access is based on user's admin privileges, or
89// config.AnonAccess.
90// If repo exists, and private, then admins and collabs are allowed access.
91// If repo exists, and not private, then access is based on config.AnonAccess.
92func (c *Config) accessForKey(repo string, pk ssh.PublicKey) proto.AccessLevel {
93 if c.isInitialAdminKey(pk) {
94 return proto.AdminAccess
95 }
96
97 var private bool
98 anon := c.AnonAccess
99 info, err := c.Metadata(repo)
100 if err == nil && info != nil {
101 private = info.IsPrivate()
102 }
103
104 log.Printf("auth key %s", authorizedKey(pk))
105 if pk != nil {
106 isAdmin := c.IsAdmin(pk)
107 if isAdmin {
108 return proto.AdminAccess
109 }
110 isCollab := c.IsCollab(repo, pk)
111 if isCollab {
112 if anon > proto.ReadWriteAccess {
113 return anon
114 }
115 return proto.ReadWriteAccess
116 }
117 if !private {
118 if anon > proto.ReadOnlyAccess {
119 return anon
120 }
121 return proto.ReadOnlyAccess
122 }
123 }
124 // Don't restrict access to private repos if no users are configured.
125 // Return anon access level.
126 if private {
127 return proto.NoAccess
128 }
129 return anon
130}
131
132func (c *Config) countUsers() int {
133 count, err := c.DB().CountUsers()
134 if err != nil {
135 return 0
136 }
137 return count
138}
139
140func (c *Config) isInitialAdminKey(key ssh.PublicKey) bool {
141 for _, k := range c.InitialAdminKeys {
142 pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
143 if err != nil {
144 log.Printf("error parsing initial admin key: %v", err)
145 continue
146 }
147 if ssh.KeysEqual(key, pk) {
148 return true
149 }
150 }
151 return false
152}
153
154func authorizedKey(key ssh.PublicKey) string {
155 if key == nil {
156 return ""
157 }
158 return strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key)))
159}