1package config
2
3import (
4 "log"
5 "strings"
6
7 gm "github.com/charmbracelet/wish/git"
8 "github.com/gliderlabs/ssh"
9 gossh "golang.org/x/crypto/ssh"
10)
11
12// Push registers Git push functionality for the given repo and key.
13func (cfg *Config) Push(repo string, pk ssh.PublicKey) {
14 go func() {
15 err := cfg.Reload()
16 if err != nil {
17 log.Printf("error reloading after push: %s", err)
18 }
19 if cfg.Cfg.Callbacks != nil {
20 cfg.Cfg.Callbacks.Push(repo)
21 }
22 r, err := cfg.Source.GetRepo(repo)
23 if err != nil {
24 log.Printf("error getting repo after push: %s", err)
25 return
26 }
27 err = r.UpdateServerInfo()
28 if err != nil {
29 log.Printf("error updating server info after push: %s", err)
30 }
31 }()
32}
33
34// Fetch registers Git fetch functionality for the given repo and key.
35func (cfg *Config) Fetch(repo string, pk ssh.PublicKey) {
36 if cfg.Cfg.Callbacks != nil {
37 cfg.Cfg.Callbacks.Fetch(repo)
38 }
39}
40
41// AuthRepo grants repo authorization to the given key.
42func (cfg *Config) AuthRepo(repo string, pk ssh.PublicKey) gm.AccessLevel {
43 return cfg.accessForKey(repo, pk)
44}
45
46// PasswordHandler returns whether or not password access is allowed.
47func (cfg *Config) PasswordHandler(ctx ssh.Context, password string) bool {
48 return (cfg.AnonAccess != "no-access") && cfg.AllowKeyless
49}
50
51// KeyboardInteractiveHandler returns whether or not keyboard interactive is allowed.
52func (cfg *Config) KeyboardInteractiveHandler(ctx ssh.Context, _ gossh.KeyboardInteractiveChallenge) bool {
53 return (cfg.AnonAccess != "no-access") && cfg.AllowKeyless
54}
55
56// PublicKeyHandler returns whether or not the given public key may access the
57// repo.
58func (cfg *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
59 return cfg.accessForKey("", pk) != gm.NoAccess
60}
61
62func (cfg *Config) anonAccessLevel() gm.AccessLevel {
63 switch cfg.AnonAccess {
64 case "no-access":
65 return gm.NoAccess
66 case "read-only":
67 return gm.ReadOnlyAccess
68 case "read-write":
69 return gm.ReadWriteAccess
70 case "admin-access":
71 return gm.AdminAccess
72 default:
73 return gm.NoAccess
74 }
75}
76
77// accessForKey returns the access level for the given repo.
78//
79// If repo doesn't exist, then access is based on user's admin privileges, or
80// config.AnonAccess.
81// If repo exists, and private, then admins and collabs are allowed access.
82// If repo exists, and not private, then access is based on config.AnonAccess.
83func (cfg *Config) accessForKey(repo string, pk ssh.PublicKey) gm.AccessLevel {
84 var u *User
85 var r *RepoConfig
86 anon := cfg.anonAccessLevel()
87OUT:
88 // Find user
89 for _, user := range cfg.Users {
90 for _, k := range user.PublicKeys {
91 apk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(strings.TrimSpace(k)))
92 if err != nil {
93 log.Printf("error: malformed authorized key: '%s'", k)
94 return gm.NoAccess
95 }
96 if ssh.KeysEqual(pk, apk) {
97 us := user
98 u = &us
99 break OUT
100 }
101 }
102 }
103 // Find repo
104 for _, rp := range cfg.Repos {
105 if rp.Repo == repo {
106 rr := rp
107 r = &rr
108 break
109 }
110 }
111 if u != nil && u.Admin {
112 return gm.AdminAccess
113 }
114 if r == nil || len(cfg.Users) == 0 {
115 return anon
116 }
117 // Collabs default access is read-write
118 if u != nil {
119 ac := gm.ReadWriteAccess
120 if anon > ac {
121 ac = anon
122 }
123 for _, rr := range u.CollabRepos {
124 if rr == r.Repo {
125 return ac
126 }
127 }
128 }
129 // Users default access is read-only
130 if !r.Private {
131 if anon > gm.ReadOnlyAccess {
132 return anon
133 }
134 return gm.ReadOnlyAccess
135 }
136 return gm.NoAccess
137}