Compare commits
3 commits
eb3feaad45
...
b86aefc038
Author | SHA1 | Date | |
---|---|---|---|
|
b86aefc038 | ||
|
b55c72828e | ||
|
eb85681b41 |
3
Makefile
|
@ -1017,8 +1017,7 @@ generate-gomock:
|
|||
|
||||
.PHONY: generate-images
|
||||
generate-images: | node_modules
|
||||
npm install --no-save fabric@6 imagemin-zopfli@7
|
||||
node tools/generate-images.js $(TAGS)
|
||||
node tools/generate-images.js
|
||||
|
||||
.PHONY: generate-manpage
|
||||
generate-manpage:
|
||||
|
|
|
@ -1163,9 +1163,13 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; Signing format that Forgejo should use, openpgp uses GPG and ssh uses OpenSSH.
|
||||
;FORMAT = openpgp
|
||||
;;
|
||||
;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
|
||||
;; run in the context of the RUN_USER
|
||||
;; Switch to none to stop signing completely
|
||||
;; Switch to none to stop signing completely.
|
||||
;; If `FORMAT` is set to **ssh** this should be set to an absolute path to an public OpenSSH key.
|
||||
;SIGNING_KEY = default
|
||||
;;
|
||||
;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer.
|
||||
|
|
|
@ -201,7 +201,7 @@ func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerifica
|
|||
}
|
||||
}
|
||||
|
||||
if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
|
||||
if setting.Repository.Signing.Format == "openpgp" && setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
|
||||
// OK we should try the default key
|
||||
gpgSettings := git.GPGSettings{
|
||||
Sign: true,
|
||||
|
|
|
@ -12,8 +12,10 @@ import (
|
|||
"forgejo.org/models/db"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
"github.com/42wim/sshsig"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// ParseObjectWithSSHSignature check if signature is good against keystore.
|
||||
|
@ -62,6 +64,22 @@ func ParseObjectWithSSHSignature(ctx context.Context, c *GitObject, committer *u
|
|||
}
|
||||
}
|
||||
|
||||
// If the SSH instance key is set, try to verify it with that key.
|
||||
if setting.SSHInstanceKey != nil {
|
||||
instanceSSHKey := &PublicKey{
|
||||
Content: string(ssh.MarshalAuthorizedKey(setting.SSHInstanceKey)),
|
||||
Fingerprint: ssh.FingerprintSHA256(setting.SSHInstanceKey),
|
||||
}
|
||||
instanceUser := &user_model.User{
|
||||
Name: setting.Repository.Signing.SigningName,
|
||||
Email: setting.Repository.Signing.SigningEmail,
|
||||
}
|
||||
commitVerification := verifySSHObjectVerification(c.Signature.Signature, c.Signature.Payload, instanceSSHKey, committer, instanceUser, setting.Repository.Signing.SigningEmail)
|
||||
if commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package asymkey
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
|
@ -15,6 +16,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestParseCommitWithSSHSignature(t *testing.T) {
|
||||
|
@ -150,4 +152,43 @@ muPLbvEduU+Ze/1Ol1pgk=
|
|||
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
|
||||
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
|
||||
})
|
||||
|
||||
t.Run("Instance key", func(t *testing.T) {
|
||||
pubKeyContent, err := os.ReadFile("../../tests/integration/ssh-signing-key.pub")
|
||||
require.NoError(t, err)
|
||||
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(pubKeyContent)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "UwU")()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "fox@example.com")()
|
||||
defer test.MockVariableValue(&setting.SSHInstanceKey, pubKey)()
|
||||
|
||||
gitCommit := &git.Commit{
|
||||
Committer: &git.Signature{
|
||||
Email: "fox@example.com",
|
||||
},
|
||||
Signature: &git.ObjectSignature{
|
||||
Payload: `tree f96f1a4f1a51dc42e2983592f503980b60b8849c
|
||||
parent 93f84db542dd8c6e952c8130bc2fcbe2e299b8b4
|
||||
author OwO <instance@example.com> 1738961379 +0100
|
||||
committer UwU <fox@example.com> 1738961379 +0100
|
||||
|
||||
Fox
|
||||
`,
|
||||
Signature: `-----BEGIN SSH SIGNATURE-----
|
||||
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgV5ELwZ8XJe2LLR/UTuEu/vsFdb
|
||||
t7ry0W8hyzz/b1iocAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
|
||||
AAAAQCnyMRkWVVNoZxZkvi/ZoknUhs4LNBmEwZs9e9214WIt+mhKfc6BiHoE2qeluR2McD
|
||||
Y5RzHnA8Ke9wXddEePCQE=
|
||||
-----END SSH SIGNATURE-----
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
o := commitToGitObject(gitCommit)
|
||||
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||
assert.True(t, commitVerification.Verified)
|
||||
assert.Equal(t, "UwU / SHA256:QttK41r/zMUeAW71b5UgVSb8xGFF/DlZJ6TyADW+uoI", commitVerification.Reason)
|
||||
assert.Equal(t, "SHA256:QttK41r/zMUeAW71b5UgVSb8xGFF/DlZJ6TyADW+uoI", commitVerification.SigningSSHKey.Fingerprint)
|
||||
})
|
||||
}
|
||||
|
|
17
models/fixtures/TestPackagesGetOrInsertBlob/package_blob.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
-
|
||||
id: 1
|
||||
size: 10
|
||||
hash_md5: HASHMD5_1
|
||||
hash_sha1: HASHSHA1_1
|
||||
hash_sha256: HASHSHA256_1
|
||||
hash_sha512: HASHSHA512_1
|
||||
hash_blake2b: HASHBLAKE2B_1
|
||||
created_unix: 946687980
|
||||
-
|
||||
id: 2
|
||||
size: 20
|
||||
hash_md5: HASHMD5_2
|
||||
hash_sha1: HASHSHA1_2
|
||||
hash_sha256: HASHSHA256_2
|
||||
hash_sha512: HASHSHA512_2
|
||||
created_unix: 946687980
|
31
models/packages/main_test.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
)
|
||||
|
||||
func AddFixtures(dirs ...string) func() {
|
||||
return unittest.OverrideFixtures(
|
||||
unittest.FixturesOptions{
|
||||
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
|
||||
Base: setting.AppWorkPath,
|
||||
Dirs: dirs,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m)
|
||||
}
|
|
@ -44,14 +44,19 @@ func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool,
|
|||
|
||||
existing := &PackageBlob{}
|
||||
|
||||
has, err := e.Where(builder.Eq{
|
||||
"size": pb.Size,
|
||||
"hash_md5": pb.HashMD5,
|
||||
"hash_sha1": pb.HashSHA1,
|
||||
"hash_sha256": pb.HashSHA256,
|
||||
"hash_sha512": pb.HashSHA512,
|
||||
"hash_blake2b": pb.HashBlake2b,
|
||||
}).Get(existing)
|
||||
has, err := e.Where(builder.And(
|
||||
builder.Eq{
|
||||
"size": pb.Size,
|
||||
"hash_md5": pb.HashMD5,
|
||||
"hash_sha1": pb.HashSHA1,
|
||||
"hash_sha256": pb.HashSHA256,
|
||||
"hash_sha512": pb.HashSHA512,
|
||||
},
|
||||
builder.Or(
|
||||
builder.Eq{"hash_blake2b": pb.HashBlake2b},
|
||||
builder.IsNull{"hash_blake2b"},
|
||||
),
|
||||
)).Get(existing)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
|
65
models/packages/package_blob_test.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2025 The Forgejo Authors.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPackagesGetOrInsertBlob(t *testing.T) {
|
||||
defer AddFixtures("models/fixtures/TestPackagesGetOrInsertBlob/")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
blake2bIsSet := unittest.AssertExistsAndLoadBean(t, &PackageBlob{ID: 1})
|
||||
blake2bNotSet := unittest.AssertExistsAndLoadBean(t, &PackageBlob{ID: 2})
|
||||
|
||||
var blake2bSetToRandom PackageBlob
|
||||
blake2bSetToRandom = *blake2bNotSet
|
||||
blake2bSetToRandom.HashBlake2b = "SOMETHING RANDOM"
|
||||
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
exists bool
|
||||
packageBlob *PackageBlob
|
||||
}{
|
||||
{
|
||||
name: "exists and blake2b is not null in the database",
|
||||
exists: true,
|
||||
packageBlob: blake2bIsSet,
|
||||
},
|
||||
{
|
||||
name: "exists and blake2b is null in the database",
|
||||
exists: true,
|
||||
packageBlob: &blake2bSetToRandom,
|
||||
},
|
||||
{
|
||||
name: "does not exists",
|
||||
exists: false,
|
||||
packageBlob: &PackageBlob{
|
||||
Size: 30,
|
||||
HashMD5: "HASHMD5_3",
|
||||
HashSHA1: "HASHSHA1_3",
|
||||
HashSHA256: "HASHSHA256_3",
|
||||
HashSHA512: "HASHSHA512_3",
|
||||
HashBlake2b: "HASHBLAKE2B_3",
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
found, has, _ := GetOrInsertBlob(t.Context(), testCase.packageBlob)
|
||||
assert.Equal(t, testCase.exists, has)
|
||||
require.NotNil(t, found)
|
||||
if testCase.exists {
|
||||
assert.Equal(t, found.ID, testCase.packageBlob.ID)
|
||||
} else {
|
||||
unittest.BeanExists(t, &PackageBlob{ID: found.ID})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -13,18 +13,9 @@ import (
|
|||
"forgejo.org/models/unittest"
|
||||
user_model "forgejo.org/models/user"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m)
|
||||
}
|
||||
|
||||
func prepareExamplePackage(t *testing.T) *packages_model.Package {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
|
|
@ -278,6 +278,49 @@ func syncGitConfig() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
switch setting.Repository.Signing.Format {
|
||||
case "ssh":
|
||||
// First do a git version check.
|
||||
if CheckGitVersionAtLeast("2.34.0") != nil {
|
||||
return errors.New("ssh signing requires Git >= 2.34.0")
|
||||
}
|
||||
|
||||
// Get the ssh-keygen binary that Git will use.
|
||||
// This can be overriden in app.ini in [git.config] section, so we must
|
||||
// query this information.
|
||||
sshKeygenPath, err := configGet("gpg.ssh.program")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// git is very stubborn and does not give a default value, so we must do
|
||||
// this ourselves.
|
||||
if len(sshKeygenPath) == 0 {
|
||||
// Default value of git, very unlikely to change.
|
||||
// https://github.com/git/git/blob/5b97a56fa0e7d580dc8865b73107407c9b3f0eff/gpg-interface.c#L116
|
||||
sshKeygenPath = "ssh-keygen"
|
||||
}
|
||||
|
||||
// Although there's a version requirement of 8.2p1, there's no cross-version
|
||||
// method to get the version of ssh-keygen. Therefore we do a simple binary
|
||||
// presence check and hope for the best.
|
||||
if _, err := exec.LookPath(sshKeygenPath); err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
return errors.New("git signing requires a ssh-keygen binary")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := configSet("gpg.format", "ssh"); err != nil {
|
||||
return err
|
||||
}
|
||||
// openpgp is already the default value, so in the case of a non SSH format
|
||||
// set the value to openpgp.
|
||||
default:
|
||||
if err := configSet("gpg.format", "openpgp"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
||||
|
@ -324,6 +367,15 @@ func CheckGitVersionEqual(equal string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func configGet(key string) (string, error) {
|
||||
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
if err != nil && !IsErrorExitCode(err, 1) {
|
||||
return "", fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout), nil
|
||||
}
|
||||
|
||||
func configSet(key, value string) error {
|
||||
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
if err != nil && !IsErrorExitCode(err, 1) {
|
||||
|
|
|
@ -11,8 +11,10 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/modules/util"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -94,3 +96,57 @@ func TestSyncConfig(t *testing.T) {
|
|||
assert.True(t, gitConfigContains("[sync-test]"))
|
||||
assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
|
||||
}
|
||||
|
||||
func TestSyncConfigGPGFormat(t *testing.T) {
|
||||
defer test.MockProtect(&setting.GitConfig)()
|
||||
|
||||
t.Run("No format", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.Format, "")()
|
||||
require.NoError(t, syncGitConfig())
|
||||
assert.True(t, gitConfigContains("[gpg]"))
|
||||
assert.True(t, gitConfigContains("format = openpgp"))
|
||||
})
|
||||
|
||||
t.Run("SSH format", func(t *testing.T) {
|
||||
r, err := os.OpenRoot(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
f, err := r.OpenFile("ssh-keygen", os.O_CREATE|os.O_TRUNC, 0o700)
|
||||
require.NoError(t, f.Close())
|
||||
require.NoError(t, err)
|
||||
t.Setenv("PATH", r.Name())
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.Format, "ssh")()
|
||||
|
||||
require.NoError(t, syncGitConfig())
|
||||
assert.True(t, gitConfigContains("[gpg]"))
|
||||
assert.True(t, gitConfigContains("format = ssh"))
|
||||
|
||||
t.Run("Old version", func(t *testing.T) {
|
||||
oldVersion, err := version.NewVersion("2.33.0")
|
||||
require.NoError(t, err)
|
||||
defer test.MockVariableValue(&gitVersion, oldVersion)()
|
||||
require.ErrorContains(t, syncGitConfig(), "ssh signing requires Git >= 2.34.0")
|
||||
})
|
||||
|
||||
t.Run("No ssh-keygen binary", func(t *testing.T) {
|
||||
require.NoError(t, r.Remove("ssh-keygen"))
|
||||
require.ErrorContains(t, syncGitConfig(), "git signing requires a ssh-keygen binary")
|
||||
})
|
||||
|
||||
t.Run("Dynamic ssh-keygen binary location", func(t *testing.T) {
|
||||
f, err := r.OpenFile("ssh-keygen-2", os.O_CREATE|os.O_TRUNC, 0o700)
|
||||
require.NoError(t, f.Close())
|
||||
require.NoError(t, err)
|
||||
defer test.MockVariableValue(&setting.GitConfig.Options, map[string]string{
|
||||
"gpg.ssh.program": "ssh-keygen-2",
|
||||
})()
|
||||
require.NoError(t, syncGitConfig())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("OpenPGP format", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.Format, "openpgp")()
|
||||
require.NoError(t, syncGitConfig())
|
||||
assert.True(t, gitConfigContains("[gpg]"))
|
||||
assert.True(t, gitConfigContains("format = openpgp"))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/modules/log"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// enumerates all the policy repository creating
|
||||
|
@ -26,6 +29,8 @@ var MaxUserCardsPerPage = 36
|
|||
// MaxForksPerPage sets maximum amount of forks shown per page
|
||||
var MaxForksPerPage = 40
|
||||
|
||||
var SSHInstanceKey ssh.PublicKey
|
||||
|
||||
// Repository settings
|
||||
var (
|
||||
Repository = struct {
|
||||
|
@ -109,6 +114,7 @@ var (
|
|||
SigningKey string
|
||||
SigningName string
|
||||
SigningEmail string
|
||||
Format string
|
||||
InitialCommit []string
|
||||
CRUDActions []string `ini:"CRUD_ACTIONS"`
|
||||
Merges []string
|
||||
|
@ -262,6 +268,7 @@ var (
|
|||
SigningKey string
|
||||
SigningName string
|
||||
SigningEmail string
|
||||
Format string
|
||||
InitialCommit []string
|
||||
CRUDActions []string `ini:"CRUD_ACTIONS"`
|
||||
Merges []string
|
||||
|
@ -271,6 +278,7 @@ var (
|
|||
SigningKey: "default",
|
||||
SigningName: "",
|
||||
SigningEmail: "",
|
||||
Format: "openpgp",
|
||||
InitialCommit: []string{"always"},
|
||||
CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
|
||||
Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
|
||||
|
@ -376,4 +384,15 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
|
|||
log.Fatal("loadRepoArchiveFrom: %v", err)
|
||||
}
|
||||
Repository.EnableFlags = sec.Key("ENABLE_FLAGS").MustBool()
|
||||
|
||||
if Repository.Signing.Format == "ssh" && Repository.Signing.SigningKey != "none" && Repository.Signing.SigningKey != "" {
|
||||
sshPublicKey, err := os.ReadFile(Repository.Signing.SigningKey)
|
||||
if err != nil {
|
||||
log.Fatal("Could not read repository signing key in %q: %v", Repository.Signing.SigningKey, err)
|
||||
}
|
||||
SSHInstanceKey, _, _, _, err = ssh.ParseAuthorizedKey(sshPublicKey)
|
||||
if err != nil {
|
||||
log.Fatal("Could not parse the SSH signing key %q: %v", sshPublicKey, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
59
modules/setting/repository_test.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestSSHInstanceKey(t *testing.T) {
|
||||
sshSigningKeyPath, err := filepath.Abs("../../tests/integration/ssh-signing-key.pub")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("None value", func(t *testing.T) {
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[repository.signing]
|
||||
FORMAT = ssh
|
||||
SIGNING_KEY = none
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
loadRepositoryFrom(cfg)
|
||||
|
||||
assert.Nil(t, SSHInstanceKey)
|
||||
})
|
||||
|
||||
t.Run("No value", func(t *testing.T) {
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[repository.signing]
|
||||
FORMAT = ssh
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
loadRepositoryFrom(cfg)
|
||||
|
||||
assert.Nil(t, SSHInstanceKey)
|
||||
})
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
iniStr := fmt.Sprintf(`
|
||||
[repository.signing]
|
||||
FORMAT = ssh
|
||||
SIGNING_KEY = %s
|
||||
`, sshSigningKeyPath)
|
||||
cfg, err := NewConfigProviderFromData(iniStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
loadRepositoryFrom(cfg)
|
||||
|
||||
assert.NotNil(t, SSHInstanceKey)
|
||||
assert.Equal(t, "ssh-ed25519", SSHInstanceKey.Type())
|
||||
assert.EqualValues(t, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeRC8GfFyXtiy0f1E7hLv77BXW7e68tFvIcs8/29YqH\n", ssh.MarshalAuthorizedKey(SSHInstanceKey))
|
||||
})
|
||||
}
|
491
package-lock.json
generated
|
@ -92,6 +92,7 @@
|
|||
"license-checker-rseidelsohn": "4.4.2",
|
||||
"markdownlint-cli": "0.44.0",
|
||||
"postcss-html": "1.8.0",
|
||||
"sharp": "0.34.1",
|
||||
"stylelint": "16.17.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.11",
|
||||
|
@ -1338,6 +1339,403 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz",
|
||||
"integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz",
|
||||
"integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz",
|
||||
"integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz",
|
||||
"integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz",
|
||||
"integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz",
|
||||
"integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz",
|
||||
"integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz",
|
||||
"integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz",
|
||||
"integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz",
|
||||
"integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz",
|
||||
"integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz",
|
||||
"integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz",
|
||||
"integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz",
|
||||
"integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
|
@ -5016,6 +5414,20 @@
|
|||
"typo-js": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
@ -5034,6 +5446,17 @@
|
|||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/colord": {
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
|
||||
|
@ -6018,6 +6441,16 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/devlop": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
||||
|
@ -12476,6 +12909,47 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz",
|
||||
"integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.34.1",
|
||||
"@img/sharp-darwin-x64": "0.34.1",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.1.0",
|
||||
"@img/sharp-libvips-darwin-x64": "1.1.0",
|
||||
"@img/sharp-libvips-linux-arm": "1.1.0",
|
||||
"@img/sharp-libvips-linux-arm64": "1.1.0",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.1.0",
|
||||
"@img/sharp-libvips-linux-s390x": "1.1.0",
|
||||
"@img/sharp-libvips-linux-x64": "1.1.0",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.1.0",
|
||||
"@img/sharp-linux-arm": "0.34.1",
|
||||
"@img/sharp-linux-arm64": "0.34.1",
|
||||
"@img/sharp-linux-s390x": "0.34.1",
|
||||
"@img/sharp-linux-x64": "0.34.1",
|
||||
"@img/sharp-linuxmusl-arm64": "0.34.1",
|
||||
"@img/sharp-linuxmusl-x64": "0.34.1",
|
||||
"@img/sharp-wasm32": "0.34.1",
|
||||
"@img/sharp-win32-ia32": "0.34.1",
|
||||
"@img/sharp-win32-x64": "0.34.1"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
@ -12605,6 +13079,23 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
|
|
|
@ -91,6 +91,7 @@
|
|||
"license-checker-rseidelsohn": "4.4.2",
|
||||
"markdownlint-cli": "0.44.0",
|
||||
"postcss-html": "1.8.0",
|
||||
"sharp": "0.34.1",
|
||||
"stylelint": "16.17.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.11",
|
||||
|
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 212 212" width="32" height="32"><style>circle,path{fill:none;stroke:#000;stroke-width:15}path{stroke-width:25}.orange{stroke:#f60}.red{stroke:#d40000}</style><g transform="translate(6 6)"><path d="M58 168V70a50 50 0 0 1 50-50h20" class="orange"/><path d="M58 168v-30a50 50 0 0 1 50-50h20" class="red"/><circle cx="142" cy="20" r="18" class="orange"/><circle cx="142" cy="88" r="18" class="red"/><circle cx="58" cy="180" r="18" class="red"/></g></svg>
|
Before Width: | Height: | Size: 503 B |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 3.7 KiB |
|
@ -865,6 +865,7 @@ func Routes() *web.Route {
|
|||
m.Group("", func() {
|
||||
m.Get("/version", misc.Version)
|
||||
m.Get("/signing-key.gpg", misc.SigningKey)
|
||||
m.Get("/signing-key.ssh", misc.SSHSigningKey)
|
||||
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
|
||||
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
|
||||
m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
|
||||
|
|
|
@ -7,8 +7,11 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
asymkey_service "forgejo.org/services/asymkey"
|
||||
"forgejo.org/services/context"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// SigningKey returns the public key of the default signing key if it exists
|
||||
|
@ -61,3 +64,29 @@ func SigningKey(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusInternalServerError, "gpg export", fmt.Errorf("Error writing key content %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// SSHSigningKey returns the public SSH key of the default signing key if it exists
|
||||
func SSHSigningKey(ctx *context.APIContext) {
|
||||
// swagger:operation GET /signing-key.ssh miscellaneous getSSHSigningKey
|
||||
// ---
|
||||
// summary: Get default signing-key.ssh
|
||||
// produces:
|
||||
// - text/plain
|
||||
// responses:
|
||||
// "200":
|
||||
// description: "SSH public key in OpenSSH authorized key format"
|
||||
// schema:
|
||||
// type: string
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if setting.SSHInstanceKey == nil {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
_, err := ctx.Write(ssh.MarshalAuthorizedKey(setting.SSHInstanceKey))
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ssh export", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func TestRoutes(t *testing.T) {
|
|||
assert.Equal(t, 404, w.Code)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req = httptest.NewRequest("GET", "/assets/img/gitea.svg", nil)
|
||||
req = httptest.NewRequest("GET", "/assets/img/forgejo.svg", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
}
|
||||
|
|
|
@ -90,6 +90,13 @@ func SigningKey(ctx context.Context, repoPath string) (string, *git.Signature) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
if setting.Repository.Signing.Format == "ssh" {
|
||||
return setting.Repository.Signing.SigningKey, &git.Signature{
|
||||
Name: setting.Repository.Signing.SigningName,
|
||||
Email: setting.Repository.Signing.SigningEmail,
|
||||
}
|
||||
}
|
||||
|
||||
if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" {
|
||||
// Can ignore the error here as it means that commit.gpgsign is not set
|
||||
value, _, _ := git.NewCommand(ctx, "config", "--get", "commit.gpgsign").RunStdString(&git.RunOpts{Dir: repoPath})
|
||||
|
|
23
templates/swagger/v1_json.tmpl
generated
|
@ -17315,6 +17315,29 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/signing-key.ssh": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"miscellaneous"
|
||||
],
|
||||
"summary": "Get default signing-key.ssh",
|
||||
"operationId": "getSSHSigningKey",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "SSH public key in OpenSSH authorized key format",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/teams/{id}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
|
38
tests/integration/api_misc_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestAPISSHSigningKey(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("No signing key", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.SSHInstanceKey, nil)()
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/signing-key.ssh"), http.StatusNotFound)
|
||||
})
|
||||
t.Run("With signing key", func(t *testing.T) {
|
||||
publicKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeRC8GfFyXtiy0f1E7hLv77BXW7e68tFvIcs8/29YqH\n"
|
||||
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKey))
|
||||
require.NoError(t, err)
|
||||
defer test.MockVariableValue(&setting.SSHInstanceKey, pubKey)()
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/signing-key.ssh"), http.StatusOK)
|
||||
assert.Equal(t, publicKey, resp.Body.String())
|
||||
})
|
||||
}
|
|
@ -1,304 +0,0 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/unittest"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/git"
|
||||
"forgejo.org/modules/process"
|
||||
"forgejo.org/modules/setting"
|
||||
api "forgejo.org/modules/structs"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/tests"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGPGGit(t *testing.T) {
|
||||
tmpDir := t.TempDir() // use a temp dir to avoid messing with the user's GPG keyring
|
||||
err := os.Chmod(tmpDir, 0o700)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Setenv("GNUPGHOME", tmpDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Need to create a root key
|
||||
rootKeyPair, err := importTestingKey()
|
||||
require.NoError(t, err, "importTestingKey")
|
||||
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "gitea")()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "gitea@fake.local")()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})()
|
||||
|
||||
username := "user2"
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
|
||||
baseAPITestContext := NewAPITestContext(t, username, "repo1")
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
u.Path = baseAPITestContext.GitPath()
|
||||
|
||||
t.Run("Unsigned-Initial", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat
|
||||
t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
assert.NotNil(t, branch.Commit)
|
||||
assert.NotNil(t, branch.Commit.Verification)
|
||||
assert.False(t, branch.Commit.Verification.Verified)
|
||||
assert.Empty(t, branch.Commit.Verification.Signature)
|
||||
}))
|
||||
t.Run("CreateCRUDFile-Never", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
t.Run("CreateCRUDFile-Never", crudActionCreateFile(
|
||||
t, testCtx, user, "never", "never2", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
|
||||
t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
|
||||
t, testCtx, user, "parentsigned", "parentsigned2", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.CRUDActions = []string{"never"}
|
||||
t.Run("Unsigned-Initial-CRUD-Never", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateCRUDFile-Never", crudActionCreateFile(
|
||||
t, testCtx, user, "parentsigned", "parentsigned-never", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.CRUDActions = []string{"always"}
|
||||
t.Run("Unsigned-Initial-CRUD-Always", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateCRUDFile-Always", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.NotNil(t, response.Verification)
|
||||
if response.Verification == nil {
|
||||
assert.FailNow(t, "no verification provided with response", "response: %v", response)
|
||||
}
|
||||
assert.True(t, response.Verification.Verified)
|
||||
if !response.Verification.Verified {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
|
||||
}))
|
||||
t.Run("CreateCRUDFile-ParentSigned-always", crudActionCreateFile(
|
||||
t, testCtx, user, "parentsigned", "parentsigned-always", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.NotNil(t, response.Verification)
|
||||
if response.Verification == nil {
|
||||
assert.FailNow(t, "no verification provided with response", "response: %v", response)
|
||||
}
|
||||
assert.True(t, response.Verification.Verified)
|
||||
if !response.Verification.Verified {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
|
||||
t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile(
|
||||
t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.NotNil(t, response.Verification)
|
||||
if response.Verification == nil {
|
||||
assert.FailNow(t, "no verification provided with response", "response: %v", response)
|
||||
}
|
||||
assert.True(t, response.Verification.Verified)
|
||||
if !response.Verification.Verified {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.InitialCommit = []string{"always"}
|
||||
t.Run("AlwaysSign-Initial", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat
|
||||
t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
assert.NotNil(t, branch.Commit)
|
||||
if branch.Commit == nil {
|
||||
assert.FailNow(t, "no commit provided with branch", "branch: %v", branch)
|
||||
}
|
||||
assert.NotNil(t, branch.Commit.Verification)
|
||||
if branch.Commit.Verification == nil {
|
||||
assert.FailNow(t, "no verification provided with branch commit", "commit: %v", branch.Commit)
|
||||
}
|
||||
assert.True(t, branch.Commit.Verification.Verified)
|
||||
if !branch.Commit.Verification.Verified {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "gitea@fake.local", branch.Commit.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.CRUDActions = []string{"never"}
|
||||
t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-always-never", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat
|
||||
t.Run("CreateCRUDFile-Never", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
|
||||
t.Run("AlwaysSign-Initial-CRUD-ParentSigned-On-Always", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-always-parent", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat
|
||||
t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.True(t, response.Verification.Verified)
|
||||
if !response.Verification.Verified {
|
||||
t.FailNow()
|
||||
return
|
||||
}
|
||||
assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.CRUDActions = []string{"always"}
|
||||
t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-always-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat
|
||||
t.Run("CreateCRUDFile-Always", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.True(t, response.Verification.Verified)
|
||||
if !response.Verification.Verified {
|
||||
t.FailNow()
|
||||
return
|
||||
}
|
||||
assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.Merges = []string{"commitssigned"}
|
||||
t.Run("UnsignedMerging", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreatePullRequest", func(t *testing.T) {
|
||||
pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "never2")(t)
|
||||
require.NoError(t, err)
|
||||
t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
|
||||
})
|
||||
t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
assert.NotNil(t, branch.Commit)
|
||||
assert.NotNil(t, branch.Commit.Verification)
|
||||
assert.False(t, branch.Commit.Verification.Verified)
|
||||
assert.Empty(t, branch.Commit.Verification.Signature)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.Merges = []string{"basesigned"}
|
||||
t.Run("BaseSignedMerging", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreatePullRequest", func(t *testing.T) {
|
||||
pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "parentsigned2")(t)
|
||||
require.NoError(t, err)
|
||||
t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
|
||||
})
|
||||
t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
assert.NotNil(t, branch.Commit)
|
||||
assert.NotNil(t, branch.Commit.Verification)
|
||||
assert.False(t, branch.Commit.Verification.Verified)
|
||||
assert.Empty(t, branch.Commit.Verification.Signature)
|
||||
}))
|
||||
})
|
||||
|
||||
setting.Repository.Signing.Merges = []string{"commitssigned"}
|
||||
t.Run("CommitsSignedMerging", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreatePullRequest", func(t *testing.T) {
|
||||
pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "always-parentsigned")(t)
|
||||
require.NoError(t, err)
|
||||
t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
|
||||
})
|
||||
t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
assert.NotNil(t, branch.Commit)
|
||||
assert.NotNil(t, branch.Commit.Verification)
|
||||
assert.True(t, branch.Commit.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func crudActionCreateFile(_ *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) {
|
||||
return doAPICreateFile(ctx, path, &api.CreateFileOptions{
|
||||
FileOptions: api.FileOptions{
|
||||
BranchName: from,
|
||||
NewBranchName: to,
|
||||
Message: fmt.Sprintf("from:%s to:%s path:%s", from, to, path),
|
||||
Author: api.Identity{
|
||||
Name: user.FullName,
|
||||
Email: user.Email,
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: user.FullName,
|
||||
Email: user.Email,
|
||||
},
|
||||
},
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("This is new text for %s", path))),
|
||||
}, callback...)
|
||||
}
|
||||
|
||||
func importTestingKey() (*openpgp.Entity, error) {
|
||||
if _, _, err := process.GetManager().Exec("gpg --import tests/integration/private-testing.key", "gpg", "--import", "tests/integration/private-testing.key"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyringFile, err := os.Open("tests/integration/private-testing.key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer keyringFile.Close()
|
||||
|
||||
block, err := armor.Decode(keyringFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyring, err := openpgp.ReadKeyRing(block.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Keyring access failed: '%w'", err)
|
||||
}
|
||||
|
||||
// There should only be one entity in this file.
|
||||
return keyring[0], nil
|
||||
}
|
330
tests/integration/signing_git_test.go
Normal file
|
@ -0,0 +1,330 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/unittest"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/git"
|
||||
"forgejo.org/modules/process"
|
||||
"forgejo.org/modules/setting"
|
||||
api "forgejo.org/modules/structs"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/tests"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestInstanceSigning(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
// Cannot use t.Context(), it is in the done state.
|
||||
require.NoError(t, git.InitFull(context.Background())) //nolint:usetesting
|
||||
})
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "UwU")()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "fox@example.com")()
|
||||
defer test.MockProtect(&setting.Repository.Signing.InitialCommit)()
|
||||
defer test.MockProtect(&setting.Repository.Signing.CRUDActions)()
|
||||
|
||||
t.Run("SSH", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
pubKeyContent, err := os.ReadFile("tests/integration/ssh-signing-key.pub")
|
||||
require.NoError(t, err)
|
||||
|
||||
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(pubKeyContent)
|
||||
require.NoError(t, err)
|
||||
signingKeyPath, err := filepath.Abs("tests/integration/ssh-signing-key")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.Chmod(signingKeyPath, 0o600))
|
||||
defer test.MockVariableValue(&setting.SSHInstanceKey, pubKey)()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.Format, "ssh")()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, signingKeyPath)()
|
||||
|
||||
// Ensure the git config is updated with the new signing format.
|
||||
require.NoError(t, git.InitFull(t.Context()))
|
||||
|
||||
forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) {
|
||||
u2 := *u
|
||||
testCRUD(t, &u2, "ssh", objectFormat)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("PGP", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
// Use a new GNUPGPHOME to avoid messing with the existing GPG keyring.
|
||||
tmpDir := t.TempDir()
|
||||
require.NoError(t, os.Chmod(tmpDir, 0o700))
|
||||
t.Setenv("GNUPGHOME", tmpDir)
|
||||
|
||||
rootKeyPair, err := importTestingKey()
|
||||
require.NoError(t, err)
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.Format, "openpgp")()
|
||||
|
||||
// Ensure the git config is updated with the new signing format.
|
||||
require.NoError(t, git.InitFull(t.Context()))
|
||||
|
||||
forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) {
|
||||
u2 := *u
|
||||
testCRUD(t, &u2, "pgp", objectFormat)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.ObjectFormat) {
|
||||
t.Helper()
|
||||
setting.Repository.Signing.CRUDActions = []string{"never"}
|
||||
setting.Repository.Signing.InitialCommit = []string{"never"}
|
||||
|
||||
username := "user2"
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
|
||||
baseAPITestContext := NewAPITestContext(t, username, "repo1")
|
||||
u.Path = baseAPITestContext.GitPath()
|
||||
|
||||
suffix := "-" + signingFormat + "-" + objectFormat.Name()
|
||||
|
||||
t.Run("Unsigned-Initial", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat))
|
||||
t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
assert.NotNil(t, branch.Commit)
|
||||
assert.NotNil(t, branch.Commit.Verification)
|
||||
assert.False(t, branch.Commit.Verification.Verified)
|
||||
assert.Empty(t, branch.Commit.Verification.Signature)
|
||||
}))
|
||||
t.Run("CreateCRUDFile-Never", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
t.Run("CreateCRUDFile-Never", crudActionCreateFile(
|
||||
t, testCtx, user, "never", "never2", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
|
||||
t, testCtx, user, "parentsigned", "parentsigned2", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("Unsigned-Initial-CRUD-Never", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.InitialCommit = []string{"never"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateCRUDFile-Never", crudActionCreateFile(
|
||||
t, testCtx, user, "parentsigned", "parentsigned-never", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("Unsigned-Initial-CRUD-Always", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.CRUDActions = []string{"always"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateCRUDFile-Always", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
|
||||
require.NotNil(t, response.Verification)
|
||||
assert.True(t, response.Verification.Verified)
|
||||
assert.Equal(t, "fox@example.com", response.Verification.Signer.Email)
|
||||
}))
|
||||
t.Run("CreateCRUDFile-ParentSigned-always", crudActionCreateFile(
|
||||
t, testCtx, user, "parentsigned", "parentsigned-always", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
|
||||
require.NotNil(t, response.Verification)
|
||||
assert.True(t, response.Verification.Verified)
|
||||
assert.Equal(t, "fox@example.com", response.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile(
|
||||
t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) {
|
||||
require.NotNil(t, response.Verification)
|
||||
assert.True(t, response.Verification.Verified)
|
||||
assert.Equal(t, "fox@example.com", response.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("AlwaysSign-Initial", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.InitialCommit = []string{"always"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-always"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat))
|
||||
t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
require.NotNil(t, branch.Commit)
|
||||
require.NotNil(t, branch.Commit.Verification)
|
||||
assert.True(t, branch.Commit.Verification.Verified)
|
||||
assert.Equal(t, "fox@example.com", branch.Commit.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.CRUDActions = []string{"never"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-always-never"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat))
|
||||
t.Run("CreateCRUDFile-Never", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.False(t, response.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("AlwaysSign-Initial-CRUD-ParentSigned-On-Always", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-always-parent"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat))
|
||||
t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.True(t, response.Verification.Verified)
|
||||
assert.Equal(t, "fox@example.com", response.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.CRUDActions = []string{"always"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-always-always"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat))
|
||||
t.Run("CreateCRUDFile-Always", crudActionCreateFile(
|
||||
t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
|
||||
assert.True(t, response.Verification.Verified)
|
||||
assert.Equal(t, "fox@example.com", response.Verification.Signer.Email)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("UnsignedMerging", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.Merges = []string{"commitssigned"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreatePullRequest", func(t *testing.T) {
|
||||
pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "never2")(t)
|
||||
require.NoError(t, err)
|
||||
t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
|
||||
})
|
||||
t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
require.NotNil(t, branch.Commit)
|
||||
require.NotNil(t, branch.Commit.Verification)
|
||||
assert.False(t, branch.Commit.Verification.Verified)
|
||||
assert.Empty(t, branch.Commit.Verification.Signature)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("BaseSignedMerging", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.Merges = []string{"basesigned"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreatePullRequest", func(t *testing.T) {
|
||||
pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "parentsigned2")(t)
|
||||
require.NoError(t, err)
|
||||
t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
|
||||
})
|
||||
t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
require.NotNil(t, branch.Commit)
|
||||
require.NotNil(t, branch.Commit.Verification)
|
||||
assert.False(t, branch.Commit.Verification.Verified)
|
||||
assert.Empty(t, branch.Commit.Verification.Signature)
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("CommitsSignedMerging", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
setting.Repository.Signing.Merges = []string{"commitssigned"}
|
||||
|
||||
testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
t.Run("CreatePullRequest", func(t *testing.T) {
|
||||
pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "always-parentsigned")(t)
|
||||
require.NoError(t, err)
|
||||
t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
|
||||
})
|
||||
t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
|
||||
require.NotNil(t, branch.Commit)
|
||||
require.NotNil(t, branch.Commit.Verification)
|
||||
assert.True(t, branch.Commit.Verification.Verified)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
func crudActionCreateFile(_ *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) {
|
||||
return doAPICreateFile(ctx, path, &api.CreateFileOptions{
|
||||
FileOptions: api.FileOptions{
|
||||
BranchName: from,
|
||||
NewBranchName: to,
|
||||
Message: fmt.Sprintf("from:%s to:%s path:%s", from, to, path),
|
||||
Author: api.Identity{
|
||||
Name: user.FullName,
|
||||
Email: user.Email,
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: user.FullName,
|
||||
Email: user.Email,
|
||||
},
|
||||
},
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("This is new text for %s", path))),
|
||||
}, callback...)
|
||||
}
|
||||
|
||||
func importTestingKey() (*openpgp.Entity, error) {
|
||||
if _, _, err := process.GetManager().Exec("gpg --import tests/integration/private-testing.key", "gpg", "--import", "tests/integration/private-testing.key"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyringFile, err := os.Open("tests/integration/private-testing.key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer keyringFile.Close()
|
||||
|
||||
block, err := armor.Decode(keyringFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyring, err := openpgp.ReadKeyRing(block.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Keyring access failed: '%w'", err)
|
||||
}
|
||||
|
||||
// There should only be one entity in this file.
|
||||
return keyring[0], nil
|
||||
}
|
7
tests/integration/ssh-signing-key
Normal file
|
@ -0,0 +1,7 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBXkQvBnxcl7YstH9RO4S7++wV1u3uvLRbyHLPP9vWKhwAAAJhlmhmkZZoZ
|
||||
pAAAAAtzc2gtZWQyNTUxOQAAACBXkQvBnxcl7YstH9RO4S7++wV1u3uvLRbyHLPP9vWKhw
|
||||
AAAEDnOTuE2rDECN+2OsuUbQgGrMSY22tn+IF5JG5nuyJinVeRC8GfFyXtiy0f1E7hLv77
|
||||
BXW7e68tFvIcs8/29YqHAAAAE2d1c3RlZEBndXN0ZWQtYmVhc3QBAg==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
1
tests/integration/ssh-signing-key.pub
Normal file
|
@ -0,0 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeRC8GfFyXtiy0f1E7hLv77BXW7e68tFvIcs8/29YqH
|
|
@ -1,9 +1,8 @@
|
|||
#!/usr/bin/env node
|
||||
import imageminZopfli from 'imagemin-zopfli'; // eslint-disable-line import-x/no-unresolved
|
||||
import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node'; // eslint-disable-line import-x/no-unresolved
|
||||
import {optimize} from 'svgo';
|
||||
import {readFile, writeFile} from 'node:fs/promises';
|
||||
import {argv, exit} from 'node:process';
|
||||
import {exit} from 'node:process';
|
||||
import SharpConstructor from 'sharp';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
function doExit(err) {
|
||||
if (err) console.error(err);
|
||||
|
@ -28,36 +27,14 @@ async function generate(svg, path, {size, bg}) {
|
|||
return;
|
||||
}
|
||||
|
||||
const {objects, options} = await loadSVGFromString(svg);
|
||||
const canvas = new Canvas();
|
||||
canvas.setDimensions({width: size, height: size});
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
|
||||
|
||||
let sharp = (new SharpConstructor(Buffer.from(svg))).resize(size, size).png({compressionLevel: 9, palette: true, effort: 10, quality: 80});
|
||||
if (bg) {
|
||||
canvas.add(new Rect({
|
||||
left: 0,
|
||||
top: 0,
|
||||
height: size * (1 / (size / options.height)),
|
||||
width: size * (1 / (size / options.width)),
|
||||
fill: 'white',
|
||||
}));
|
||||
sharp = sharp.flatten({background: 'white'});
|
||||
}
|
||||
|
||||
canvas.add(util.groupSVGElements(objects, options));
|
||||
canvas.renderAll();
|
||||
|
||||
let png = Buffer.from([]);
|
||||
for await (const chunk of canvas.createPNGStream()) {
|
||||
png = Buffer.concat([png, chunk]);
|
||||
}
|
||||
|
||||
png = await imageminZopfli({more: true})(png);
|
||||
await writeFile(outputFile, png);
|
||||
sharp.toFile(fileURLToPath(outputFile), (err) => err !== null && console.error(err) && exit(1));
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const gitea = argv.slice(2).includes('gitea');
|
||||
const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8');
|
||||
const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
|
||||
|
||||
|
@ -68,7 +45,6 @@ async function main() {
|
|||
generate(faviconSvg, '../public/assets/img/favicon.png', {size: 180}),
|
||||
generate(logoSvg, '../public/assets/img/avatar_default.png', {size: 200}),
|
||||
generate(logoSvg, '../public/assets/img/apple-touch-icon.png', {size: 180, bg: true}),
|
||||
gitea && generate(logoSvg, '../public/assets/img/gitea.svg', {size: 32}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|