Skip to content

Commit 47e2a11

Browse files
committed
split code
1 parent 9555233 commit 47e2a11

File tree

3 files changed

+361
-318
lines changed

3 files changed

+361
-318
lines changed

modules/packages/docker/auth.go

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package docker
6+
7+
import (
8+
"encoding/base64"
9+
"encoding/json"
10+
"fmt"
11+
"math/rand"
12+
"regexp"
13+
"sort"
14+
"strings"
15+
"time"
16+
17+
"code.gitea.io/gitea/models"
18+
"github.com/docker/distribution/registry/auth/token"
19+
"github.com/docker/libtrust"
20+
)
21+
22+
// AuthzResult auth result
23+
type AuthzResult struct {
24+
Scope AuthScope
25+
AutorizedActions []string
26+
}
27+
28+
// AuthScope auth scope
29+
type AuthScope struct {
30+
Type string
31+
Class string
32+
Name string
33+
Actions []string
34+
}
35+
36+
// GenerateTokenOptions options to generate a token
37+
type GenerateTokenOptions struct {
38+
Account string
39+
IssuerName string
40+
AuthzResults []AuthzResult
41+
PublicKey *libtrust.PublicKey
42+
PrivateKey *libtrust.PrivateKey
43+
ServiceName string
44+
Expiration int64
45+
}
46+
47+
// GenerateToken generate token
48+
func GenerateToken(opts GenerateTokenOptions) (string, error) {
49+
now := time.Now().Unix()
50+
51+
// Sign something dummy to find out which algorithm is used.
52+
_, sigAlg, err := (*opts.PrivateKey).Sign(strings.NewReader("dummy"), 0)
53+
if err != nil {
54+
return "", fmt.Errorf("failed to sign: %s", err)
55+
}
56+
header := token.Header{
57+
Type: "JWT",
58+
SigningAlg: sigAlg,
59+
KeyID: (*opts.PublicKey).KeyID(),
60+
}
61+
headerJSON, err := json.Marshal(header)
62+
if err != nil {
63+
return "", fmt.Errorf("failed to marshal header: %s", err)
64+
}
65+
66+
claims := token.ClaimSet{
67+
Issuer: opts.IssuerName,
68+
Subject: opts.Account,
69+
Audience: opts.ServiceName,
70+
NotBefore: now - 10,
71+
IssuedAt: now,
72+
Expiration: now + opts.Expiration,
73+
JWTID: fmt.Sprintf("%d", rand.Int63()),
74+
Access: []*token.ResourceActions{},
75+
}
76+
for _, a := range opts.AuthzResults {
77+
ra := &token.ResourceActions{
78+
Type: a.Scope.Type,
79+
Name: a.Scope.Name,
80+
Actions: a.AutorizedActions,
81+
}
82+
if ra.Actions == nil {
83+
ra.Actions = []string{}
84+
}
85+
sort.Strings(ra.Actions)
86+
claims.Access = append(claims.Access, ra)
87+
}
88+
claimsJSON, err := json.Marshal(claims)
89+
if err != nil {
90+
return "", fmt.Errorf("failed to marshal claims: %s", err)
91+
}
92+
93+
payload := fmt.Sprintf("%s%s%s", joseBase64UrlEncode(headerJSON), token.TokenSeparator, joseBase64UrlEncode(claimsJSON))
94+
95+
sig, sigAlg2, err := (*opts.PrivateKey).Sign(strings.NewReader(payload), 0)
96+
if err != nil || sigAlg2 != sigAlg {
97+
return "", fmt.Errorf("failed to sign token: %s", err)
98+
}
99+
return fmt.Sprintf("%s%s%s", payload, token.TokenSeparator, joseBase64UrlEncode(sig)), nil
100+
}
101+
102+
// Copy-pasted from libtrust where it is private.
103+
func joseBase64UrlEncode(b []byte) string {
104+
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
105+
}
106+
107+
// PermissionCheck Permission check
108+
func PermissionCheck(doer *models.User, scops []AuthScope) ([]AuthzResult, error) {
109+
rs := make([]AuthzResult, 0, len(scops))
110+
repos := make(map[string]*models.Repository)
111+
repoPerms := make(map[int64]models.Permission)
112+
113+
for _, scop := range scops {
114+
hasPush := false
115+
hasPull := false
116+
needCreate := false
117+
for _, act := range scop.Actions {
118+
if act == "push" {
119+
hasPush = true
120+
continue
121+
}
122+
if act == "pull" {
123+
hasPull = true
124+
}
125+
}
126+
if hasPush && doer == nil {
127+
continue
128+
}
129+
splits := strings.SplitN(scop.Name, "/", 3)
130+
var (
131+
owner string
132+
repoName string
133+
image string
134+
)
135+
if len(splits) != 3 {
136+
continue
137+
}
138+
owner = splits[0]
139+
repoName = splits[1]
140+
image = splits[2]
141+
142+
repo, has := repos[owner+"/"+repoName]
143+
var err error
144+
if !has {
145+
repo, err = models.GetRepositoryByOwnerAndName(owner, repoName)
146+
if err != nil {
147+
if models.IsErrRepoNotExist(err) {
148+
continue
149+
}
150+
return nil, err
151+
}
152+
repos[owner+"/"+repoName] = repo
153+
}
154+
155+
_, err = models.GetPackage(repo.ID, models.PackageTypeDockerImage, image)
156+
if err != nil {
157+
if !models.IsErrPackageNotExist(err) {
158+
return nil, err
159+
}
160+
needCreate = true
161+
}
162+
163+
perm, has := repoPerms[repo.ID]
164+
if !has {
165+
perm, err = models.GetUserRepoPermission(repo, doer)
166+
if err != nil {
167+
return nil, err
168+
}
169+
repoPerms[repo.ID] = perm
170+
}
171+
172+
events := make([]string, 0, 2)
173+
accessMode := models.AccessModeRead
174+
if hasPush {
175+
accessMode = models.AccessModeRead
176+
if needCreate {
177+
accessMode = models.AccessModeAdmin
178+
}
179+
}
180+
if perm.CanAccess(accessMode, models.UnitTypePackages) {
181+
if hasPush {
182+
events = append(events, "push")
183+
}
184+
if hasPull {
185+
events = append(events, "pull")
186+
}
187+
}
188+
189+
if len(events) == 0 {
190+
continue
191+
}
192+
193+
rs = append(rs, AuthzResult{
194+
Scope: scop,
195+
AutorizedActions: events,
196+
})
197+
}
198+
199+
return rs, nil
200+
}
201+
202+
var resourceTypeRegex = regexp.MustCompile(`([a-z0-9]+)(\([a-z0-9]+\))?`)
203+
204+
// parseResourceType parse scope type
205+
func parseResourceType(scope string) (string, string, error) {
206+
parts := resourceTypeRegex.FindStringSubmatch(scope)
207+
if parts == nil {
208+
return "", "", fmt.Errorf("malformed scope request")
209+
}
210+
211+
switch len(parts) {
212+
case 3:
213+
return parts[1], "", nil
214+
case 4:
215+
return parts[1], parts[3], nil
216+
default:
217+
return "", "", fmt.Errorf("malformed scope request")
218+
}
219+
}
220+
221+
// SplitScopes split scopes
222+
func SplitScopes(scope string) ([]AuthScope, error) {
223+
scopes := strings.Split(scope, " ")
224+
rs := make([]AuthScope, 0, len(scopes))
225+
for _, scopeStr := range scopes {
226+
parts := strings.Split(scopeStr, ":")
227+
var scope AuthScope
228+
229+
scopeType, scopeClass, err := parseResourceType(parts[0])
230+
if err != nil {
231+
return nil, err
232+
}
233+
234+
switch len(parts) {
235+
case 3:
236+
scope = AuthScope{
237+
Type: scopeType,
238+
Class: scopeClass,
239+
Name: parts[1],
240+
Actions: strings.Split(parts[2], ","),
241+
}
242+
case 4:
243+
scope = AuthScope{
244+
Type: scopeType,
245+
Class: scopeClass,
246+
Name: parts[1] + ":" + parts[2],
247+
Actions: strings.Split(parts[3], ","),
248+
}
249+
default:
250+
return nil, fmt.Errorf("invalid scope: %q", scopeStr)
251+
}
252+
sort.Strings(scope.Actions)
253+
rs = append(rs, scope)
254+
}
255+
256+
return rs, nil
257+
}

modules/packages/docker/event_hook.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package docker
6+
7+
import (
8+
"strings"
9+
10+
"code.gitea.io/gitea/models"
11+
"code.gitea.io/gitea/modules/timeutil"
12+
"github.com/docker/distribution/notifications"
13+
)
14+
15+
// HandleEvents handle event listen
16+
func HandleEvents(data *notifications.Envelope) error {
17+
repos := make(map[string]*models.Repository)
18+
pkgs := make(map[string]*models.Package)
19+
for _, event := range data.Events {
20+
if event.Action == notifications.EventActionPush {
21+
var (
22+
owner string
23+
repoName string
24+
image string
25+
err error
26+
)
27+
splits := strings.SplitN(event.Target.Repository, "/", 3)
28+
if len(splits) != 3 {
29+
continue
30+
}
31+
owner = splits[0]
32+
repoName = splits[1]
33+
image = splits[2]
34+
35+
repo, has := repos[owner+"/"+repoName]
36+
if !has {
37+
repo, err = models.GetRepositoryByOwnerAndName(owner, repoName)
38+
if err != nil {
39+
if models.IsErrRepoNotExist(err) {
40+
continue
41+
}
42+
return err
43+
}
44+
repos[owner+"/"+repoName] = repo
45+
}
46+
47+
pkg, has := pkgs[event.Target.Repository]
48+
if !has {
49+
pkg, err = models.GetPackage(repo.ID, models.PackageTypeDockerImage, image)
50+
if err != nil {
51+
if models.IsErrPackageNotExist(err) {
52+
// create a new pkg
53+
err = models.AddPackage(models.AddPackageOptions{
54+
Repo: repo,
55+
Name: image,
56+
Type: models.PackageTypeDockerImage,
57+
})
58+
if err != nil {
59+
return err
60+
}
61+
continue
62+
}
63+
return err
64+
}
65+
if pkg != nil {
66+
pkgs[event.Target.Repository] = pkg
67+
}
68+
}
69+
70+
// update update time
71+
pkg.UpdatedUnix = timeutil.TimeStamp(event.Timestamp.Unix())
72+
err = pkg.UpdateCols("updated_unix")
73+
if err != nil {
74+
return err
75+
}
76+
}
77+
}
78+
79+
return nil
80+
}

0 commit comments

Comments
 (0)