feat: backend DB model for fine-grained repo access tokens

This commit is contained in:
Mathieu Fenniak 2026-02-15 10:57:25 -07:00 committed by Mathieu Fenniak
parent 2d4a3e5658
commit a1eff6f0dc
13 changed files with 166 additions and 1 deletions

View file

@ -0,0 +1,9 @@
- token_id: 3
repo_id: 1
created_unix: 1772158384
- token_id: 3
repo_id: 2
created_unix: 1772158384
- token_id: 3
repo_id: 3
created_unix: 1772158384

View file

@ -73,6 +73,8 @@ type AccessToken struct {
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
HasRecentActivity bool `xorm:"-"`
HasUsed bool `xorm:"-"`
ResourceAllRepos bool `xorm:"NOT NULL DEFAULT TRUE"` // flag for whether AccessTokenResourceRepo instances will limit the resources this access token can access (false) or won't limit them (true).
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.

View file

@ -0,0 +1,36 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package auth
import (
"context"
"forgejo.org/models/db"
"forgejo.org/modules/timeutil"
)
// Represents a many-to-many join table which indicates specific repositories (RepoID) that can be accessed by an access
// token (TokenID). An access token's ResourceAllRepos field must be false for records in this table to become active.
type AccessTokenResourceRepo struct {
ID int64 `xorm:"pk autoincr"`
TokenID int64 `xorm:"NOT NULL REFERENCES(access_token, id)"` // needs to be shortened from "AccessTokenID" for the index to fit MySQL table identifier length restrictions
RepoID int64 `xorm:"NOT NULL REFERENCES(repository, id)"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
}
func init() {
db.RegisterModel(new(AccessTokenResourceRepo))
}
func GetRepositoriesAccessibleWithToken(ctx context.Context, accessTokenID int64) ([]*AccessTokenResourceRepo, error) {
var resources []*AccessTokenResourceRepo
err := db.GetEngine(ctx).
Where("token_id = ?", accessTokenID).
Find(&resources)
if err != nil {
return nil, err
}
return resources, nil
}

View file

@ -0,0 +1,41 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package auth_test
import (
"testing"
auth_model "forgejo.org/models/auth"
"forgejo.org/models/db"
"forgejo.org/models/unittest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetRepositoriesAccessibleWithToken(t *testing.T) {
defer unittest.OverrideFixtures("models/auth/TestGetRepositoriesAccessibleWithToken")()
require.NoError(t, unittest.PrepareTestDatabase())
t.Run("No Resources", func(t *testing.T) {
resources, err := auth_model.GetRepositoriesAccessibleWithToken(db.DefaultContext, 999)
require.NoError(t, err)
assert.Empty(t, resources)
})
t.Run("Has Resources", func(t *testing.T) {
resources, err := auth_model.GetRepositoriesAccessibleWithToken(db.DefaultContext, 3)
require.NoError(t, err)
require.Len(t, resources, 3)
// Verify all expected repo IDs are present
repoIDs := make([]int64, len(resources))
for i, res := range resources {
repoIDs[i] = res.RepoID
}
assert.Contains(t, repoIDs, int64(1))
assert.Contains(t, repoIDs, int64(2))
assert.Contains(t, repoIDs, int64(3))
})
}

View file

@ -0,0 +1,23 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package forgejo_migrations
import (
"xorm.io/xorm"
)
func init() {
registerMigration(&Migration{
Description: "add resource_all_owned_repositories to table access_token",
Upgrade: addAllOwnedRepositoriesToAccessToken,
})
}
func addAllOwnedRepositoriesToAccessToken(x *xorm.Engine) error {
type AccessToken struct {
ResourceAllRepos bool `xorm:"NOT NULL DEFAULT TRUE"`
}
_, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(AccessToken))
return err
}

View file

@ -0,0 +1,29 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package forgejo_migrations
import (
"forgejo.org/modules/timeutil"
"xorm.io/xorm"
)
func init() {
registerMigration(&Migration{
Description: "add access_token_resource table",
Upgrade: addAccessTokenResource,
})
}
func addAccessTokenResource(x *xorm.Engine) error {
type AccessTokenResourceRepo struct {
ID int64 `xorm:"pk autoincr"`
TokenID int64 `xorm:"NOT NULL REFERENCES(access_token, id)"` // needs to be shortened from "AccessTokenID" for the index to fit MySQL table identifier length restrictions
RepoID int64 `xorm:"NOT NULL REFERENCES(repository, id)"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
}
_, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(AccessTokenResourceRepo))
return err
}