refactor/kvconf 3421f16

worker_test.go RawHistoryFolder

1package auth
2
3import (
4	"bytes"
5	"context"
6	"errors"
7	"io"
8	"testing"
9	"time"
10
11	"github.com/bnema/neferafk/internal/ports"
12	pam "github.com/bnema/purego-pam"
13	"github.com/stretchr/testify/mock"
14)
15
16// Unlock runs only the PAM auth stack (like swaylock); account management is
17// never called, so a service file with only `auth` lines works.
18func TestPAMRequiresAuthenticationAndEnd(t *testing.T) {
19	for _, stage := range []string{"success", "start", "authenticate", "end"} {
20		t.Run(stage, func(t *testing.T) {
21			b := newMockpamBackend(t)
22			tx := newMockpamTransaction(t)
23			boom := errors.New("sensitive native message")
24			if stage == "start" {
25				b.EXPECT().start("neferafk", "real-user", mock.Anything).Return(nil, boom).Once()
26			} else {
27				b.EXPECT().start("neferafk", "real-user", mock.Anything).Return(tx, nil).Once()
28				var authErr, endErr error
29				if stage == "authenticate" {
30					authErr = boom
31				}
32				if stage == "end" {
33					endErr = boom
34				}
35				tx.EXPECT().Authenticate(pam.DisallowNullAuthtok).Return(authErr).Once()
36				tx.EXPECT().End().Return(endErr).Once()
37			}
38			code := authenticate(b, "real-user", &worker{})
39			if (code == ports.AuthSuccess) != (stage == "success") {
40				t.Fatalf("stage %s code %d", stage, code)
41			}
42		})
43	}
44}
45
46func TestPINWrongNeverFallsBackAndAttemptsScoped(t *testing.T) {
47	var input, output bytes.Buffer
48	for i, secret := range []string{"000000", "123456"} {
49		if err := WriteFrame(&input, ports.AuthFrame{Kind: ports.AuthAttempt, Generation: 8, Attempt: uint64(i + 1), Payload: []byte(secret)}); err != nil {
50			t.Fatal(err)
51		}
52	}
53	b := newMockpamBackend(t) // no PAM call allowed for valid resolved PIN
54	if err := run(&input, &output, 8, []byte("123456"), b, "user"); err != nil {
55		t.Fatal(err)
56	}
57	for i, want := range []ports.AuthCode{ports.AuthPIN, ports.AuthDenied, ports.AuthSuccess} {
58		f, err := ReadFrame(&output)
59		if err != nil || f.Code != want || f.Generation != 8 || f.Attempt != uint64(i) || len(f.Payload) != 0 {
60			t.Fatalf("result %+v err=%v", f, err)
61		}
62	}
63}
64
65func TestRunConsumesBootstrapOnceAndWipes(t *testing.T) {
66	var input, output bytes.Buffer
67	if err := WriteFrame(&input, ports.AuthFrame{Kind: ports.AuthAttempt, Generation: 8, Attempt: 1, Payload: []byte("123456")}); err != nil {
68		t.Fatal(err)
69	}
70	bootstrap := ports.AuthBootstrap{Generation: 8, PINSource: "env", EnvPIN: []byte("123456")}
71	if err := Run(context.Background(), &input, &output, bootstrap); err != nil {
72		t.Fatal(err)
73	}
74	if !bytes.Equal(bootstrap.EnvPIN, make([]byte, 6)) {
75		t.Fatal("bootstrap retained secret")
76	}
77	for _, kind := range []ports.AuthKind{ports.AuthReady, ports.AuthResult} {
78		f, err := ReadFrame(&output)
79		if err != nil || f.Kind != kind {
80			t.Fatalf("frame %+v %v", f, err)
81		}
82	}
83}
84
85func TestUnavailablePAMAndReplayFailClosed(t *testing.T) {
86	var input, output bytes.Buffer
87	for range 2 {
88		if err := WriteFrame(&input, ports.AuthFrame{Kind: ports.AuthAttempt, Generation: 2, Attempt: 1}); err != nil {
89			t.Fatal(err)
90		}
91	}
92	if err := run(&input, &output, 2, nil, nil, ""); !errors.Is(err, ErrProtocol) {
93		t.Fatal(err)
94	}
95	for range 2 {
96		f, err := ReadFrame(&output)
97		if err != nil || f.Code != ports.AuthUnavailable {
98			t.Fatalf("result %+v %v", f, err)
99		}
100	}
101	if _, err := ReadFrame(&output); !errors.Is(err, io.EOF) {
102		t.Fatal("replay produced result")
103	}
104}
105
106func TestConversationGenericMultiplePromptsAndWipes(t *testing.T) {
107	var input, output bytes.Buffer
108	if err := WriteFrame(&input, ports.AuthFrame{Kind: ports.AuthSecret, Generation: 3, Attempt: 9, Payload: []byte("second")}); err != nil {
109		t.Fatal(err)
110	}
111	w := &worker{r: &input, w: &output, generation: 3, attempt: 9, deadline: time.Now().Add(time.Second), first: true, secretLen: 5, secret: make([]byte, 512)}
112	copy(w.secret[:], "first")
113	answer := make([]byte, 512)
114	if n, err := w.Respond(pam.Message{Style: pam.PromptEchoOff, Text: []byte("do not forward")}, answer); err != nil || string(answer[:n]) != "first" {
115		t.Fatalf("first %d %v", n, err)
116	}
117	if !bytes.Equal(w.secret[:], make([]byte, 512)) {
118		t.Fatal("first secret retained")
119	}
120	clear(answer)
121	if n, err := w.Respond(pam.Message{Style: pam.PromptEchoOn, Text: []byte("raw native prompt")}, answer); err != nil || string(answer[:n]) != "second" {
122		t.Fatalf("second %d %v", n, err)
123	}
124	if _, err := w.Respond(pam.Message{Style: pam.ErrorMsg, Text: []byte("native secret error")}, answer); err != nil {
125		t.Fatal(err)
126	}
127	for _, kind := range []ports.AuthKind{ports.AuthPrompt, ports.AuthStatus} {
128		f, err := ReadFrame(&output)
129		if err != nil || f.Kind != kind || len(f.Payload) != 0 || f.Attempt != 9 {
130			t.Fatalf("generic %+v %v", f, err)
131		}
132	}
133}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 9

↑↓ moveenter openesc close