-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathoauth2_test.go
More file actions
129 lines (119 loc) · 5.32 KB
/
Copy pathoauth2_test.go
File metadata and controls
129 lines (119 loc) · 5.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package codersdk_test
import (
"net/url"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/codersdk"
)
// TestOAuth2ClientRegistrationRequest_DetermineClientType verifies that the
// client type is derived from the requested token_endpoint_auth_method
// (RFC 7591 §2, OAuth 2.1 §2.1), not hardcoded to "confidential".
func TestOAuth2ClientRegistrationRequest_DetermineClientType(t *testing.T) {
t.Parallel()
tests := []struct {
name string
authMethod codersdk.OAuth2TokenEndpointAuthMethod
// applyDefaults runs ApplyDefaults() before DetermineClientType(),
// matching the real request path where an omitted auth method is
// defaulted to "client_secret_basic" before this check ever runs.
applyDefaults bool
// wantAuthMethodAfterDefaults pins what ApplyDefaults() does to
// authMethod, so a case that runs applyDefaults also verifies
// ApplyDefaults left (or changed) the field as expected before
// DetermineClientType() reads it. Only checked when applyDefaults
// is true.
wantAuthMethodAfterDefaults codersdk.OAuth2TokenEndpointAuthMethod
expectedType string
}{
{
name: "NoneIsPublic",
authMethod: codersdk.OAuth2TokenEndpointAuthMethodNone,
expectedType: "public",
},
{
name: "ClientSecretBasicIsConfidential",
authMethod: codersdk.OAuth2TokenEndpointAuthMethodClientSecretBasic,
expectedType: "confidential",
},
{
name: "ClientSecretPostIsConfidential",
authMethod: codersdk.OAuth2TokenEndpointAuthMethodClientSecretPost,
expectedType: "confidential",
},
{
// ApplyDefaults only fills an empty auth method; it must not
// touch an explicit "none". If it ever grew a rule that did,
// the pre-defaults Validate() call and the post-defaults
// storage call would disagree about this client's type.
name: "NoneStaysPublicAfterApplyDefaults",
authMethod: codersdk.OAuth2TokenEndpointAuthMethodNone,
applyDefaults: true,
wantAuthMethodAfterDefaults: codersdk.OAuth2TokenEndpointAuthMethodNone,
expectedType: "public",
},
{
// An omitted auth method must not be read as public. Without
// ApplyDefaults the empty string also falls through to
// confidential, so this is safe in either order, but the real
// path always defaults first.
name: "OmittedDefaultsToConfidentialAfterApplyDefaults",
applyDefaults: true,
wantAuthMethodAfterDefaults: codersdk.OAuth2TokenEndpointAuthMethodClientSecretBasic,
expectedType: "confidential",
},
{
name: "OmittedIsConfidentialWithoutApplyDefaults",
expectedType: "confidential",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
req := codersdk.OAuth2ClientRegistrationRequest{
TokenEndpointAuthMethod: tt.authMethod,
}
if tt.applyDefaults {
req = req.ApplyDefaults()
require.Equal(t, tt.wantAuthMethodAfterDefaults, req.TokenEndpointAuthMethod)
}
require.Equal(t, tt.expectedType, string(req.DetermineClientType()))
})
}
}
func TestRedirectURIMatches(t *testing.T) {
t.Parallel()
tests := []struct {
name string
registered string
presented string
want bool
}{
{"ExactMatch", "https://app.example.com/callback", "https://app.example.com/callback", true},
{"CustomSchemeExact", "cursor://anysphere.cursor-mcp/oauth/callback", "cursor://anysphere.cursor-mcp/oauth/callback", true},
{"LoopbackIPv4PortDiffers", "http://127.0.0.1/callback", "http://127.0.0.1:53219/callback", true},
{"LoopbackIPv6PortDiffers", "http://[::1]/callback", "http://[::1]:53219/callback", true},
{"LocalhostPortDiffers", "http://localhost/callback", "http://localhost:53219/callback", true},
{"RegisteredPortDoesNotPin", "http://localhost:9876/callback", "http://localhost:53219/callback", true},
{"PresentedWithoutPort", "http://127.0.0.1:53219/callback", "http://127.0.0.1/callback", true},
{"LoopbackPathDiffers", "http://127.0.0.1/callback", "http://127.0.0.1:53219/other", false},
{"LoopbackSchemeDiffers", "http://127.0.0.1/callback", "https://127.0.0.1:53219/callback", false},
{"LoopbackHostSubstitution", "http://127.0.0.1/callback", "http://localhost:53219/callback", false},
{"LoopbackQueryDiffers", "http://127.0.0.1/callback", "http://127.0.0.1:53219/callback?next=x", false},
{"LoopbackUserinfoDiffers", "http://127.0.0.1/callback", "http://[email protected]:53219/callback", false},
{"LocalhostSubdomain", "http://app.localhost/callback", "http://app.localhost:53219/callback", false},
{"OtherLoopbackIP", "http://127.0.0.2/callback", "http://127.0.0.2:53219/callback", false},
{"HTTPSLoopbackPortDiffers", "https://127.0.0.1/callback", "https://127.0.0.1:53219/callback", false},
{"NonLoopbackPortDiffers", "https://app.example.com/callback", "https://app.example.com:8443/callback", false},
{"LoopbackPresentedAgainstNonLoopback", "https://app.example.com/callback", "http://127.0.0.1:53219/callback", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
registered, err := url.Parse(tt.registered)
require.NoError(t, err)
presented, err := url.Parse(tt.presented)
require.NoError(t, err)
require.Equal(t, tt.want, codersdk.RedirectURIMatches(presented, registered))
})
}
}