v3.0.0-alpha.1 6fdddb8

http01_test.go RawHistoryFolder

1package publictls
2
3import (
4	"context"
5	"fmt"
6	"sync"
7	"testing"
8
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11)
12
13func TestHTTP01Challenges(t *testing.T) {
14	ctx := context.Background()
15
16	t.Run("full lifecycle", func(t *testing.T) {
17		ch := NewHTTP01Challenges()
18		require.NotNil(t, ch)
19
20		// Present a valid token
21		ch.Present("token", "key-auth")
22		got, ok := ch.Get(ctx, "token")
23		assert.True(t, ok)
24		assert.Equal(t, "key-auth", got)
25
26		// Clean up
27		ch.CleanUp("token")
28		_, ok = ch.Get(ctx, "token")
29		assert.False(t, ok)
30	})
31
32	t.Run("concurrent access is correct", func(t *testing.T) {
33		ch := NewHTTP01Challenges()
34
35		// Pre-populate some tokens.
36		ch.Present("existing-token", "existing-keyauth")
37
38		const numGoroutines = 20
39		var wg sync.WaitGroup
40		errCh := make(chan error, numGoroutines)
41
42		// Spawn concurrent readers and writers.
43		for i := range numGoroutines {
44			wg.Add(1)
45			go func(id int) {
46				defer wg.Done()
47				token := fmt.Sprintf("token-%d", id)
48				keyAuth := fmt.Sprintf("ka-%d", id)
49
50				// Every goroutine presents its own token.
51				ch.Present(token, keyAuth)
52
53				// Every goroutine reads back its own token.
54				got, ok := ch.Get(ctx, token)
55				if !ok {
56					errCh <- fmt.Errorf("token %q not found after Present", token)
57					return
58				}
59				if got != keyAuth {
60					errCh <- fmt.Errorf("token %q: got %q, want %q", token, got, keyAuth)
61					return
62				}
63
64				// Every goroutine reads the pre-populated existing token.
65				existing, ok := ch.Get(ctx, "existing-token")
66				if !ok {
67					errCh <- fmt.Errorf("existing-token not found by goroutine %d", id)
68					return
69				}
70				if existing != "existing-keyauth" {
71					errCh <- fmt.Errorf("existing-token: got %q, want existing-keyauth", existing)
72					return
73				}
74
75				// Every goroutine cleans up its own token.
76				ch.CleanUp(token)
77				_, ok = ch.Get(ctx, token)
78				if ok {
79					errCh <- fmt.Errorf("token %q should be gone after CleanUp", token)
80					return
81				}
82			}(i)
83		}
84
85		wg.Wait()
86		close(errCh)
87
88		// Collect any errors from goroutines.
89		for err := range errCh {
90			t.Error(err)
91		}
92
93		// Verify that pre-populated token is still present after all concurrent access.
94		existing, ok := ch.Get(ctx, "existing-token")
95		require.True(t, ok, "existing-token should survive concurrent access")
96		assert.Equal(t, "existing-keyauth", existing)
97	})
98}
99
100func TestHTTP01ChallengesRejectUnsafeToken(t *testing.T) {
101	ctx := context.Background()
102
103	t.Run("path traversal token ../secret is rejected", func(t *testing.T) {
104		ch := NewHTTP01Challenges()
105
106		ch.Present("../secret", "key-auth")
107		_, ok := ch.Get(ctx, "../secret")
108		assert.False(t, ok, "Present should silently ignore ../secret")
109	})
110
111	t.Run("token with slash is rejected", func(t *testing.T) {
112		ch := NewHTTP01Challenges()
113
114		ch.Present("a/b", "key-auth")
115		_, ok := ch.Get(ctx, "a/b")
116		assert.False(t, ok, "token containing / should be rejected")
117
118		// Get also rejects the unsafe token
119		_, ok = ch.Get(ctx, "safe/token")
120		assert.False(t, ok)
121	})
122
123	t.Run("token with backslash is rejected", func(t *testing.T) {
124		ch := NewHTTP01Challenges()
125
126		ch.Present("a\\b", "key-auth")
127		_, ok := ch.Get(ctx, "a\\b")
128		assert.False(t, ok, "token containing backslash should be rejected")
129
130		_, ok = ch.Get(ctx, "safe\\token")
131		assert.False(t, ok)
132	})
133
134	t.Run("empty token is rejected", func(t *testing.T) {
135		ch := NewHTTP01Challenges()
136
137		ch.Present("", "key-auth")
138		_, ok := ch.Get(ctx, "")
139		assert.False(t, ok, "empty token should be rejected")
140	})
141
142	t.Run("'..' token is rejected", func(t *testing.T) {
143		ch := NewHTTP01Challenges()
144
145		ch.Present("..", "key-auth")
146		_, ok := ch.Get(ctx, "..")
147		assert.False(t, ok, "token '..' should be rejected")
148	})
149
150	t.Run("empty keyAuth is silently ignored", func(t *testing.T) {
151		ch := NewHTTP01Challenges()
152		ch.Present("valid-token", "")
153		_, ok := ch.Get(ctx, "valid-token")
154		assert.False(t, ok, "Present with empty keyAuth should not store anything")
155	})
156
157	t.Run("valid tokens are accepted and retrievable", func(t *testing.T) {
158		ch := NewHTTP01Challenges()
159		ch.Present("valid-token", "key-auth-value")
160		got, ok := ch.Get(ctx, "valid-token")
161		assert.True(t, ok)
162		assert.Equal(t, "key-auth-value", got)
163	})
164}

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 7

Tags 106

↑↓ moveenter openesc close