package dehub import ( "errors" "regexp" "testing" "dehub.dev/src/dehub.git/accessctl" "dehub.dev/src/dehub.git/sigcred" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" ) func TestConfigChange(t *testing.T) { h := newHarness(t) rootSig := h.stageNewAccount("root", false) var commits []Commit // commit the initial staged changes, which merely include the config and // public key commit := h.assertCommitChange(verifyShouldSucceed, "commit configuration", rootSig) commits = append(commits, commit) // create a new account and add it to the configuration. That commit should // not be verifiable, though tootSig := h.stageNewAccount("toot", false) h.stageCfg() h.assertCommitChange(verifyShouldFail, "add toot user", tootSig) // now add with the root user, this should work. h.stageCfg() commit = h.assertCommitChange(verifyShouldSucceed, "add toot user", rootSig) commits = append(commits, commit) // _now_ the toot user should be able to do things. h.stage(map[string]string{"foo/bar": "what a cool file"}) commit = h.assertCommitChange(verifyShouldSucceed, "add a cool file", tootSig) commits = append(commits, commit) if err := h.proj.VerifyCommits(MainRefName, commits); err != nil { t.Fatal(err) } } func TestMainAncestryRequirement(t *testing.T) { otherBranch := plumbing.NewBranchReferenceName("other") t.Run("empty repo", func(t *testing.T) { h := newHarness(t) rootSig := h.stageNewAccount("root", false) h.checkout(otherBranch) // stage and try to add to the "other" branch, it shouldn't work though h.stageCfg() h.assertCommitChange(verifyShouldFail, "starting new branch at other", rootSig) }) t.Run("new branch, single commit", func(t *testing.T) { h := newHarness(t) rootSig := h.stageNewAccount("root", false) h.assertCommitChange(verifyShouldSucceed, "add cfg", rootSig) // set HEAD to this other branch which doesn't really exist ref := plumbing.NewSymbolicReference(plumbing.HEAD, otherBranch) if err := h.proj.GitRepo.Storer.SetReference(ref); err != nil { h.t.Fatal(err) } h.stageCfg() h.assertCommitChange(verifyShouldFail, "starting new branch at other", rootSig) }) } func TestAnonymousCommits(t *testing.T) { h := newHarness(t) anonSig := h.stageNewAccount("anon", true) h.stageAccessControls(` - action: allow filters: - type: signature any: true `) h.assertCommitChange(verifyShouldSucceed, "this will work", anonSig) } func TestNonFastForwardCommits(t *testing.T) { h := newHarness(t) rootSig := h.stageNewAccount("root", false) initCommit := h.assertCommitChange(verifyShouldSucceed, "init", rootSig) // add another commit h.stage(map[string]string{"foo": "foo"}) fooCommit := h.assertCommitChange(verifyShouldSucceed, "foo", rootSig) commitOn := func(hash plumbing.Hash, msg string) Commit { ref := plumbing.NewHashReference(plumbing.HEAD, hash) if err := h.proj.GitRepo.Storer.SetReference(ref); err != nil { h.t.Fatal(err) } else if commitChange, err := h.proj.NewPayloadChange("bar"); err != nil { h.t.Fatal(err) } else if commitChange, err = h.proj.AccreditPayload(commitChange, rootSig); err != nil { h.t.Fatal(err) } else if gitCommit, err := h.proj.Commit(commitChange); err != nil { h.t.Fatal(err) } else { return gitCommit } panic("can't get here") } // checkout initCommit directly, make a new commit on top of it, and try to // verify that (this is too fancy for the harness, must be done manually). h.stage(map[string]string{"bar": "bar"}) barCommit := commitOn(initCommit.Hash, "bar") err := h.proj.VerifyCommits(MainRefName, []Commit{barCommit}) if !errors.As(err, new(accessctl.ErrCommitRequestDenied)) { h.t.Fatalf("expected ErrCommitRequestDenied, got: %v", err) } // check main back out (fooCommit should be checked out), and modify the // config to allow nonFF commits, and add another bogus commit on top. h.checkout(MainRefName) h.stageAccessControls(` - action: allow filters: - type: commit_attributes non_fast_forward: true`) h.stageCfg() allowNonFFCommit := h.assertCommitChange(verifyShouldSucceed, "allow non-ff", rootSig) h.stage(map[string]string{"foo": "foo foo"}) h.assertCommitChange(verifyShouldSucceed, "foo foo", rootSig) // checking out allowNonFFCommit directly and performing a nonFF commit // should work now. h.stage(map[string]string{"baz": "baz"}) bazCommit := commitOn(allowNonFFCommit.Hash, "baz") if err = h.proj.VerifyCommits(MainRefName, []Commit{bazCommit}); err != nil { h.t.Fatal(err) } // verifying the full history should also work gitCommits := []Commit{initCommit, fooCommit, allowNonFFCommit, bazCommit} if err = h.proj.VerifyCommits(MainRefName, gitCommits); err != nil { h.t.Fatal(err) } } func TestVerifyCanSetBranchHEADTo(t *testing.T) { type toTest struct { // branchName and hash are the arguments passed into // VerifyCanSetBranchHEADTo. branchName plumbing.ReferenceName hash plumbing.Hash // if set then the branch will have its HEAD reset to this hash prior to // calling VerifyCanSetBranchHEADTo. resetTo plumbing.Hash } type test struct { descr string init func(h *harness, rootSig sigcred.Signifier) toTest // If true then the verify call is expected to fail. The string is a // regex which should match the unwrapped error returned. expErr string } tests := []test{ { descr: "creation of main", init: func(h *harness, rootSig sigcred.Signifier) toTest { // checkout other and build on top of that, so that when // VerifyCanSetBranchHEADTo is called main won't exist. other := plumbing.NewBranchReferenceName("other") h.checkout(other) initCommit := h.assertCommitChange(verifySkip, "init", rootSig) return toTest{ branchName: MainRefName, hash: initCommit.Hash, } }, }, { descr: "main ff", init: func(h *harness, rootSig sigcred.Signifier) toTest { initCommit := h.assertCommitChange(verifySkip, "init", rootSig) h.stage(map[string]string{"foo": "foo"}) nextCommit := h.assertCommitChange(verifySkip, "next", rootSig) return toTest{ branchName: MainRefName, hash: nextCommit.Hash, resetTo: initCommit.Hash, } }, }, { descr: "new branch, no main", init: func(h *harness, rootSig sigcred.Signifier) toTest { // checkout other and build on top of that, so that when // VerifyCanSetBranchHEADTo is called main won't exist. other := plumbing.NewBranchReferenceName("other") h.checkout(other) initCommit := h.assertCommitChange(verifySkip, "init", rootSig) return toTest{ branchName: plumbing.NewBranchReferenceName("other2"), hash: initCommit.Hash, } }, expErr: `^cannot verify commits in branch "refs/heads/other2" when no main branch exists$`, }, { // this case isn't generally possible, unless someone manually // creates a branch in an empty repo on the remote descr: "existing branch, no main", init: func(h *harness, rootSig sigcred.Signifier) toTest { // checkout other and build on top of that, so that when // VerifyCanSetBranchHEADTo is called main won't exist. other := plumbing.NewBranchReferenceName("other") h.checkout(other) initCommit := h.assertCommitChange(verifySkip, "init", rootSig) h.stage(map[string]string{"foo": "foo"}) fooCommit := h.assertCommitChange(verifySkip, "foo", rootSig) return toTest{ branchName: other, hash: fooCommit.Hash, resetTo: initCommit.Hash, } }, expErr: `^cannot verify commits in branch "refs/heads/other" when no main branch exists$`, }, { descr: "new branch, not ancestor of main", init: func(h *harness, rootSig sigcred.Signifier) toTest { h.assertCommitChange(verifySkip, "init", rootSig) // create new branch with no HEAD, and commit on that. other := plumbing.NewBranchReferenceName("other") ref := plumbing.NewSymbolicReference(plumbing.HEAD, other) if err := h.proj.GitRepo.Storer.SetReference(ref); err != nil { t.Fatal(err) } h.stageCfg() h.stage(map[string]string{"foo": "foo"}) badInitCommit := h.assertCommitChange(verifySkip, "a different init", rootSig) return toTest{ branchName: plumbing.NewBranchReferenceName("other2"), hash: badInitCommit.Hash, } }, expErr: `^commit "[0-9a-f]+" must be direct descendant of root commit of "main" \("[0-9a-f]+"\)$`, }, { // this case isn't generally possible, unless someone manually // creates a branch in an empty repo on the remote descr: "existing branch, not ancestor of main", init: func(h *harness, rootSig sigcred.Signifier) toTest { h.assertCommitChange(verifySkip, "init", rootSig) // create new branch with no HEAD, and commit on that. other := plumbing.NewBranchReferenceName("other") ref := plumbing.NewSymbolicReference(plumbing.HEAD, other) if err := h.proj.GitRepo.Storer.SetReference(ref); err != nil { t.Fatal(err) } h.stageCfg() h.stage(map[string]string{"foo": "foo"}) badInitCommit := h.assertCommitChange(verifySkip, "a different init", rootSig) h.stage(map[string]string{"bar": "bar"}) barCommit := h.assertCommitChange(verifySkip, "bar", rootSig) return toTest{ branchName: other, hash: barCommit.Hash, resetTo: badInitCommit.Hash, } }, expErr: `^commit "[0-9a-f]+" must be direct descendant of root commit of "main" \("[0-9a-f]+"\)$`, }, { descr: "new branch off of main", init: func(h *harness, rootSig sigcred.Signifier) toTest { initCommit := h.assertCommitChange(verifySkip, "init", rootSig) other := plumbing.NewBranchReferenceName("other") h.checkout(other) h.stage(map[string]string{"foo": "foo"}) fooCommit := h.assertCommitChange(verifySkip, "foo", rootSig) return toTest{ branchName: other, hash: fooCommit.Hash, resetTo: initCommit.Hash, } }, }, { descr: "new branch off of older main commit", init: func(h *harness, rootSig sigcred.Signifier) toTest { initCommit := h.assertCommitChange(verifySkip, "init", rootSig) h.stage(map[string]string{"foo": "foo"}) h.assertCommitChange(verifySkip, "foo", rootSig) other := plumbing.NewBranchReferenceName("other") h.checkout(other) h.reset(initCommit.Hash, git.HardReset) h.stage(map[string]string{"bar": "bar"}) barCommit := h.assertCommitChange(verifySkip, "bar", rootSig) return toTest{ branchName: other, hash: barCommit.Hash, resetTo: initCommit.Hash, } }, }, { descr: "branch ff", init: func(h *harness, rootSig sigcred.Signifier) toTest { h.assertCommitChange(verifySkip, "init", rootSig) other := plumbing.NewBranchReferenceName("other") h.checkout(other) var commits []Commit for _, str := range []string{"foo", "bar", "baz", "biz", "buz"} { h.stage(map[string]string{str: str}) commit := h.assertCommitChange(verifySkip, str, rootSig) commits = append(commits, commit) } return toTest{ branchName: other, hash: commits[len(commits)-1].Hash, resetTo: commits[0].Hash, } }, }, { descr: "main nonff", init: func(h *harness, rootSig sigcred.Signifier) toTest { initCommit := h.assertCommitChange(verifySkip, "init", rootSig) h.stage(map[string]string{"foo": "foo"}) h.assertCommitChange(verifySkip, "foo", rootSig) // start another branch back at init and make a new commit on it other := plumbing.NewBranchReferenceName("other") h.checkout(other) h.reset(initCommit.Hash, git.HardReset) h.stage(map[string]string{"bar": "bar"}) barCommit := h.assertCommitChange(verifySkip, "bar", rootSig) return toTest{ branchName: MainRefName, hash: barCommit.Hash, } }, expErr: `^commit matched and denied by this access control:`, }, { descr: "branch nonff", init: func(h *harness, rootSig sigcred.Signifier) toTest { h.assertCommitChange(verifySkip, "init", rootSig) other := plumbing.NewBranchReferenceName("other") h.checkout(other) h.stage(map[string]string{"foo": "foo"}) fooCommit := h.assertCommitChange(verifySkip, "foo", rootSig) h.stage(map[string]string{"bar": "bar"}) h.assertCommitChange(verifySkip, "bar", rootSig) other2 := plumbing.NewBranchReferenceName("other2") h.checkout(other2) h.reset(fooCommit.Hash, git.HardReset) h.stage(map[string]string{"baz": "baz"}) bazCommit := h.assertCommitChange(verifySkip, "baz", rootSig) return toTest{ branchName: other, hash: bazCommit.Hash, } }, }, { descr: "branch nonff to previous commit", init: func(h *harness, rootSig sigcred.Signifier) toTest { h.assertCommitChange(verifySkip, "init", rootSig) other := plumbing.NewBranchReferenceName("other") h.checkout(other) h.stage(map[string]string{"foo": "foo"}) fooCommit := h.assertCommitChange(verifySkip, "foo", rootSig) h.stage(map[string]string{"bar": "bar"}) h.assertCommitChange(verifySkip, "bar", rootSig) return toTest{ branchName: other, hash: fooCommit.Hash, } }, }, } for _, test := range tests { t.Run(test.descr, func(t *testing.T) { h := newHarness(t) rootSig := h.stageNewAccount("root", false) toTest := test.init(h, rootSig) if toTest.resetTo != plumbing.ZeroHash { ref := plumbing.NewHashReference(toTest.branchName, toTest.resetTo) if err := h.proj.GitRepo.Storer.SetReference(ref); err != nil { t.Fatal(err) } } err := h.proj.VerifyCanSetBranchHEADTo(toTest.branchName, toTest.hash) if test.expErr == "" { if err != nil { t.Fatalf("unexpected error: %v", err) } return } else if err == nil { t.Fatal("expected verification to fail") } ogErr := err for { if unwrappedErr := errors.Unwrap(err); unwrappedErr != nil { err = unwrappedErr } else { break } } errRegex := regexp.MustCompile(test.expErr) if !errRegex.MatchString(err.Error()) { t.Fatalf("\nexpected error of form %q\nbut got: %v", test.expErr, ogErr) } }) } }