Skip to content

Commit f1941bd

Browse files
committed
Merge branch 'main' into lunny/use_git_model
2 parents eb58cbf + c28aab6 commit f1941bd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+720
-373
lines changed

cmd/serv.go

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"path/filepath"
1414
"strconv"
1515
"strings"
16-
"time"
1716
"unicode"
1817

1918
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -32,7 +31,6 @@ import (
3231
"code.gitea.io/gitea/modules/setting"
3332
"code.gitea.io/gitea/services/lfs"
3433

35-
"github.com/golang-jwt/jwt/v5"
3634
"github.com/kballard/go-shellquote"
3735
"github.com/urfave/cli/v3"
3836
)
@@ -133,27 +131,6 @@ func getAccessMode(verb, lfsVerb string) perm.AccessMode {
133131
return perm.AccessModeNone
134132
}
135133

136-
func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServCommandResults) (string, error) {
137-
now := time.Now()
138-
claims := lfs.Claims{
139-
RegisteredClaims: jwt.RegisteredClaims{
140-
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
141-
NotBefore: jwt.NewNumericDate(now),
142-
},
143-
RepoID: results.RepoID,
144-
Op: lfsVerb,
145-
UserID: results.UserID,
146-
}
147-
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
148-
149-
// Sign and get the complete encoded token as a string using the secret
150-
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
151-
if err != nil {
152-
return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
153-
}
154-
return "Bearer " + tokenString, nil
155-
}
156-
157134
func runServ(ctx context.Context, c *cli.Command) error {
158135
// FIXME: This needs to internationalised
159136
setup(ctx, c.Bool("debug"))
@@ -283,7 +260,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
283260

284261
// LFS SSH protocol
285262
if verb == git.CmdVerbLfsTransfer {
286-
token, err := getLFSAuthToken(ctx, lfsVerb, results)
263+
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
287264
if err != nil {
288265
return err
289266
}
@@ -294,7 +271,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
294271
if verb == git.CmdVerbLfsAuthenticate {
295272
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
296273

297-
token, err := getLFSAuthToken(ctx, lfsVerb, results)
274+
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
298275
if err != nil {
299276
return err
300277
}

models/asymkey/ssh_key.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,6 @@ func (key *PublicKey) OmitEmail() string {
6767
return strings.Join(strings.Split(key.Content, " ")[:2], " ")
6868
}
6969

70-
// AuthorizedString returns formatted public key string for authorized_keys file.
71-
//
72-
// TODO: Consider dropping this function
73-
func (key *PublicKey) AuthorizedString() string {
74-
return AuthorizedStringForKey(key)
75-
}
76-
7770
func addKey(ctx context.Context, key *PublicKey) (err error) {
7871
if len(key.Fingerprint) == 0 {
7972
key.Fingerprint, err = CalcFingerprint(key.Content)

models/asymkey/ssh_key_authorized_keys.go

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,14 @@ import (
1717
"code.gitea.io/gitea/modules/log"
1818
"code.gitea.io/gitea/modules/setting"
1919
"code.gitea.io/gitea/modules/util"
20-
)
2120

22-
// _____ __ .__ .__ .___
23-
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
24-
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
25-
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
26-
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
27-
// \/ \/ \/ \/ \/
28-
// ____ __.
29-
// | |/ _|____ ___.__. ______
30-
// | <_/ __ < | |/ ___/
31-
// | | \ ___/\___ |\___ \
32-
// |____|__ \___ > ____/____ >
33-
// \/ \/\/ \/
34-
//
35-
// This file contains functions for creating authorized_keys files
36-
//
37-
// There is a dependence on the database within RegeneratePublicKeys however most of these functions probably belong in a module
38-
39-
const (
40-
tplCommentPrefix = `# gitea public key`
41-
tplPublicKey = tplCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s` + "\n"
21+
"golang.org/x/crypto/ssh"
4222
)
4323

24+
// AuthorizedStringCommentPrefix is a magic tag
25+
// some functions like RegeneratePublicKeys needs this tag to skip the keys generated by Gitea, while keep other keys
26+
const AuthorizedStringCommentPrefix = `# gitea public key`
27+
4428
var sshOpLocker sync.Mutex
4529

4630
func WithSSHOpLocker(f func() error) error {
@@ -50,17 +34,45 @@ func WithSSHOpLocker(f func() error) error {
5034
}
5135

5236
// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
53-
func AuthorizedStringForKey(key *PublicKey) string {
37+
func AuthorizedStringForKey(key *PublicKey) (string, error) {
5438
sb := &strings.Builder{}
55-
_ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]any{
39+
_, err := writeAuthorizedStringForKey(key, sb)
40+
return sb.String(), err
41+
}
42+
43+
// WriteAuthorizedStringForValidKey writes the authorized key for the provided key. If the key is invalid, it does nothing.
44+
func WriteAuthorizedStringForValidKey(key *PublicKey, w io.Writer) error {
45+
validKey, err := writeAuthorizedStringForKey(key, w)
46+
if !validKey {
47+
log.Debug("WriteAuthorizedStringForValidKey: key %s is not valid: %v", key, err)
48+
return nil
49+
}
50+
return err
51+
}
52+
53+
func writeAuthorizedStringForKey(key *PublicKey, w io.Writer) (keyValid bool, err error) {
54+
const tpl = AuthorizedStringCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s %s` + "\n"
55+
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key.Content))
56+
if err != nil {
57+
return false, err
58+
}
59+
// now the key is valid, the code below could only return template/IO related errors
60+
sbCmd := &strings.Builder{}
61+
err = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sbCmd, map[string]any{
5662
"AppPath": util.ShellEscape(setting.AppPath),
5763
"AppWorkPath": util.ShellEscape(setting.AppWorkPath),
5864
"CustomConf": util.ShellEscape(setting.CustomConf),
5965
"CustomPath": util.ShellEscape(setting.CustomPath),
6066
"Key": key,
6167
})
62-
63-
return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content)
68+
if err != nil {
69+
return true, err
70+
}
71+
sshCommandEscaped := util.ShellEscape(sbCmd.String())
72+
sshKeyMarshalled := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pubKey)))
73+
sshKeyComment := fmt.Sprintf("user-%d", key.OwnerID)
74+
_, err = fmt.Fprintf(w, tpl, sshCommandEscaped, sshKeyMarshalled, sshKeyComment)
75+
return true, err
6476
}
6577

6678
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
@@ -112,18 +124,17 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
112124
if key.Type == KeyTypePrincipal {
113125
continue
114126
}
115-
if _, err = f.WriteString(key.AuthorizedString()); err != nil {
127+
if err = WriteAuthorizedStringForValidKey(key, f); err != nil {
116128
return err
117129
}
118130
}
119131
return nil
120132
}
121133

122134
// RegeneratePublicKeys regenerates the authorized_keys file
123-
func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
135+
func RegeneratePublicKeys(ctx context.Context, t io.Writer) error {
124136
if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
125-
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
126-
return err
137+
return WriteAuthorizedStringForValidKey(bean.(*PublicKey), t)
127138
}); err != nil {
128139
return err
129140
}
@@ -144,11 +155,11 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
144155
scanner := bufio.NewScanner(f)
145156
for scanner.Scan() {
146157
line := scanner.Text()
147-
if strings.HasPrefix(line, tplCommentPrefix) {
158+
if strings.HasPrefix(line, AuthorizedStringCommentPrefix) {
148159
scanner.Scan()
149160
continue
150161
}
151-
_, err = t.WriteString(line + "\n")
162+
_, err = io.WriteString(t, line+"\n")
152163
if err != nil {
153164
return err
154165
}

models/organization/org.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,10 @@ func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User)
429429
return true
430430
}
431431

432+
if !setting.Service.RequireSignInViewStrict && orgOrUser.Visibility == structs.VisibleTypePublic {
433+
return true
434+
}
435+
432436
if (orgOrUser.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !OrgFromUser(orgOrUser).hasMemberWithUserID(ctx, user.ID) {
433437
return false
434438
}

models/organization/org_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import (
1313
repo_model "code.gitea.io/gitea/models/repo"
1414
"code.gitea.io/gitea/models/unittest"
1515
user_model "code.gitea.io/gitea/models/user"
16+
"code.gitea.io/gitea/modules/setting"
1617
"code.gitea.io/gitea/modules/structs"
18+
"code.gitea.io/gitea/modules/test"
1719

1820
"github.com/stretchr/testify/assert"
1921
"github.com/stretchr/testify/require"
@@ -382,6 +384,12 @@ func TestHasOrgVisibleTypePublic(t *testing.T) {
382384
assert.True(t, test1) // owner of org
383385
assert.True(t, test2) // user not a part of org
384386
assert.True(t, test3) // logged out user
387+
388+
restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29, IsRestricted: true})
389+
require.True(t, restrictedUser.IsRestricted)
390+
assert.True(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser))
391+
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
392+
assert.False(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser))
385393
}
386394

387395
func TestHasOrgVisibleTypeLimited(t *testing.T) {

models/perm/access/access.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"code.gitea.io/gitea/models/perm"
1414
repo_model "code.gitea.io/gitea/models/repo"
1515
user_model "code.gitea.io/gitea/models/user"
16+
"code.gitea.io/gitea/modules/setting"
17+
"code.gitea.io/gitea/modules/structs"
1618

1719
"xorm.io/builder"
1820
)
@@ -41,7 +43,12 @@ func accessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Re
4143
restricted = user.IsRestricted
4244
}
4345

44-
if !restricted && !repo.IsPrivate {
46+
if err := repo.LoadOwner(ctx); err != nil {
47+
return mode, err
48+
}
49+
50+
repoIsFullyPublic := !setting.Service.RequireSignInViewStrict && repo.Owner.Visibility == structs.VisibleTypePublic && !repo.IsPrivate
51+
if (restricted && repoIsFullyPublic) || (!restricted && !repo.IsPrivate) {
4552
mode = perm.AccessModeRead
4653
}
4754

models/perm/access/access_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
repo_model "code.gitea.io/gitea/models/repo"
1313
"code.gitea.io/gitea/models/unittest"
1414
user_model "code.gitea.io/gitea/models/user"
15+
"code.gitea.io/gitea/modules/setting"
1516

1617
"github.com/stretchr/testify/assert"
1718
)
@@ -51,7 +52,14 @@ func TestAccessLevel(t *testing.T) {
5152
assert.NoError(t, err)
5253
assert.Equal(t, perm_model.AccessModeNone, level)
5354

54-
// restricted user has no access to a public repo
55+
// restricted user has default access to a public repo if no sign-in is required
56+
setting.Service.RequireSignInViewStrict = false
57+
level, err = access_model.AccessLevel(t.Context(), user29, repo1)
58+
assert.NoError(t, err)
59+
assert.Equal(t, perm_model.AccessModeRead, level)
60+
61+
// restricted user has no access to a public repo if sign-in is required
62+
setting.Service.RequireSignInViewStrict = true
5563
level, err = access_model.AccessLevel(t.Context(), user29, repo1)
5664
assert.NoError(t, err)
5765
assert.Equal(t, perm_model.AccessModeNone, level)

models/repo/repo_list.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,17 @@ func SearchRepositoryIDsByCondition(ctx context.Context, cond builder.Cond) ([]i
642642
Find(&repoIDs)
643643
}
644644

645+
func userAllPublicRepoCond(cond builder.Cond, orgVisibilityLimit []structs.VisibleType) builder.Cond {
646+
return cond.Or(builder.And(
647+
builder.Eq{"`repository`.is_private": false},
648+
// Aren't in a private organisation or limited organisation if we're not logged in
649+
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(
650+
builder.And(
651+
builder.Eq{"type": user_model.UserTypeOrganization},
652+
builder.In("visibility", orgVisibilityLimit)),
653+
))))
654+
}
655+
645656
// AccessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
646657
func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) builder.Cond {
647658
cond := builder.NewCond()
@@ -651,15 +662,8 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
651662
if user == nil || user.ID <= 0 {
652663
orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited)
653664
}
654-
// 1. Be able to see all non-private repositories that either:
655-
cond = cond.Or(builder.And(
656-
builder.Eq{"`repository`.is_private": false},
657-
// 2. Aren't in an private organisation or limited organisation if we're not logged in
658-
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(
659-
builder.And(
660-
builder.Eq{"type": user_model.UserTypeOrganization},
661-
builder.In("visibility", orgVisibilityLimit)),
662-
))))
665+
// 1. Be able to see all non-private repositories
666+
cond = userAllPublicRepoCond(cond, orgVisibilityLimit)
663667
}
664668

665669
if user != nil {
@@ -683,6 +687,9 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
683687
if !user.IsRestricted {
684688
// 5. Be able to see all public repos in private organizations that we are an org_user of
685689
cond = cond.Or(userOrgPublicRepoCond(user.ID))
690+
} else if !setting.Service.RequireSignInViewStrict {
691+
orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate, structs.VisibleTypeLimited}
692+
cond = userAllPublicRepoCond(cond, orgVisibilityLimit)
686693
}
687694
}
688695

0 commit comments

Comments
 (0)