feat(protocol): define native session credentials
This commit is contained in:
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestManifestRejectsForbiddenAndUnknownFields(t *testing.T) {
|
||||
valid := `{"version":"1","purpose":"launch","session_id":"session-1","reconnect_sequence":0,"gateway":{"id":"gateway-1","addresses":["gateway.control.test:443"],"public_identity":"gateway-1"},"tunnel":{"versions":["verse-gateway-v1/1"],"features":["control.v1"]},"profile":{"id":"standard","bounds":{"minimum_kbps":1,"target_kbps":2,"maximum_kbps":3}},"grant":{"opaque_value":"opaque-one-time-grant-value-with-at-least-43-bytes","expires_at":"2099-01-01T00:00:00Z","audience":"versevdi-gateway"},"correlation_id":"correlation-1"}`
|
||||
valid := `{"version":"1","purpose":"launch","session_id":"session-1","reconnect_sequence":0,"gateway":{"id":"gateway-1","addresses":["gateway.control.test:443"],"public_identity":"gateway.control.test"},"tunnel":{"versions":["verse-gateway-v1/1"],"features":["control.v1"]},"profile":{"id":"standard","bounds":{"minimum_kbps":1,"target_kbps":2,"maximum_kbps":3}},"grant":{"opaque_value":"opaque-one-time-grant-value-with-at-least-43-bytes","expires_at":"2099-01-01T00:00:00Z","audience":"versevdi-gateway"},"correlation_id":"correlation-1"}`
|
||||
manifest, err := protocol.DecodeConnectionManifest([]byte(valid))
|
||||
if err != nil || manifest.Gateway.ID != "gateway-1" {
|
||||
t.Fatalf("valid manifest = %+v, err = %v", manifest, err)
|
||||
@@ -42,11 +42,6 @@ func TestDisplayModeIsOptionalStrictAndBounded(t *testing.T) {
|
||||
|
||||
request := protocol.SessionRequest{
|
||||
ClientDeviceID: "device-1", DeviceKeyID: "key-1", PoolID: "pool-1", IdempotencyKey: "request-1",
|
||||
PolicySnapshot: protocol.AllocationPolicy{
|
||||
MinimumKbps: 1000, TargetKbps: 2000, MaximumKbps: 3000, Tier: "standard",
|
||||
Audience: "versevdi-gateway", Protocol: "verse", ProtocolVersion: 1,
|
||||
GrantTTLSeconds: 60, ReservationLeaseSeconds: 300,
|
||||
},
|
||||
}
|
||||
legacy, err := protocol.EncodeSessionRequest(request)
|
||||
if err != nil {
|
||||
@@ -100,6 +95,79 @@ func TestDisplayModeIsOptionalStrictAndBounded(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionRequestRejectsClientPolicySnapshot(t *testing.T) {
|
||||
if protocol.CurrentWireVersion != "2" || protocol.NMinus1WireVersion != "1" || protocol.NMinus2WireVersion != "0" {
|
||||
t.Fatalf("wire compatibility = current %q, n-1 %q, n-2 %q", protocol.CurrentWireVersion, protocol.NMinus1WireVersion, protocol.NMinus2WireVersion)
|
||||
}
|
||||
request := `{"client_device_id":"device-1","device_key_id":"key-1","pool_id":"pool-1","idempotency_key":"request-1"}`
|
||||
if _, err := protocol.DecodeSessionRequest([]byte(request)); err != nil {
|
||||
t.Fatalf("policy-free SessionRequest rejected: %v", err)
|
||||
}
|
||||
withPolicy := strings.Replace(request, `"idempotency_key":"request-1"`, `"idempotency_key":"request-1","policy_snapshot":{}`, 1)
|
||||
if _, err := protocol.DecodeSessionRequest([]byte(withPolicy)); err == nil {
|
||||
t.Fatal("SessionRequest accepted client-supplied policy_snapshot")
|
||||
}
|
||||
if _, ok := reflect.TypeOf(protocol.SessionRequest{}).FieldByName("PolicySnapshot"); ok {
|
||||
t.Fatal("SessionRequest generated a client-owned PolicySnapshot field")
|
||||
}
|
||||
if _, ok := reflect.TypeOf(protocol.BrokerSession{}).FieldByName("PolicySnapshot"); !ok {
|
||||
t.Fatal("BrokerSession lost its server-owned PolicySnapshot disclosure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticatedSessionShapesAreRouteSpecific(t *testing.T) {
|
||||
browser := `{"username":"alice","provider":"local","roles":["user"],"role":"user"}`
|
||||
decodedBrowser, err := protocol.DecodeBrowserAuthenticatedSession([]byte(browser))
|
||||
if err != nil || decodedBrowser.Username != "alice" {
|
||||
t.Fatalf("browser session = %+v, err = %v", decodedBrowser, err)
|
||||
}
|
||||
native := `{"username":"alice","provider":"local","roles":["user"],"role":"user","native_identity":{"client_device_id":"device-1","device_key_id":"key-1"}}`
|
||||
decodedNative, err := protocol.DecodeNativeAuthenticatedSession([]byte(native))
|
||||
if err != nil || decodedNative.NativeIdentity.DeviceKeyID != "key-1" {
|
||||
t.Fatalf("native session = %+v, err = %v", decodedNative, err)
|
||||
}
|
||||
if _, err := protocol.DecodeBrowserAuthenticatedSession([]byte(native)); err == nil {
|
||||
t.Fatal("browser response accepted native identity")
|
||||
}
|
||||
if _, err := protocol.DecodeNativeAuthenticatedSession([]byte(browser)); err == nil {
|
||||
t.Fatal("native response accepted missing native identity")
|
||||
}
|
||||
for _, invalid := range []string{
|
||||
strings.Replace(native, `,"device_key_id":"key-1"`, "", 1),
|
||||
strings.Replace(native, `"native_identity":{"client_device_id":"device-1","device_key_id":"key-1"}`, `"native_identity":null`, 1),
|
||||
strings.Replace(native, `"device_key_id":"key-1"`, `"device_key_id":"key-1","unknown":true`, 1),
|
||||
} {
|
||||
if _, err := protocol.DecodeNativeAuthenticatedSession([]byte(invalid)); err == nil {
|
||||
t.Fatalf("NativeAuthenticatedSession accepted non-atomic or unknown native identity: %s", invalid)
|
||||
}
|
||||
}
|
||||
for _, invalidRoles := range []string{`[""]`, `["` + strings.Repeat("r", 65) + `"]`} {
|
||||
invalid := strings.Replace(browser, `["user"]`, invalidRoles, 1)
|
||||
if _, err := protocol.DecodeBrowserAuthenticatedSession([]byte(invalid)); err == nil {
|
||||
t.Fatalf("BrowserAuthenticatedSession accepted invalid roles: %s", invalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeTunnelCredentialIsStrictAndBounded(t *testing.T) {
|
||||
valid := `{"client_device_id":"device-1","device_key_id":"key-1","certificate_chain_pem":"certificate","trust_bundle_pem":"trust","expires_at":"2099-01-01T00:00:00Z"}`
|
||||
credential, err := protocol.DecodeNativeTunnelCredential([]byte(valid))
|
||||
if err != nil || credential.ClientDeviceID != "device-1" {
|
||||
t.Fatalf("native tunnel credential = %+v, err = %v", credential, err)
|
||||
}
|
||||
for _, invalid := range []string{
|
||||
strings.Replace(valid, `"device_key_id":"key-1",`, "", 1),
|
||||
strings.Replace(valid, `"expires_at":"2099-01-01T00:00:00Z"`, `"expires_at":"not-a-time"`, 1),
|
||||
strings.Replace(valid, `"expires_at":"2099-01-01T00:00:00Z"`, `"expires_at":"2099-01-01T00:00:00+00:00"`, 1),
|
||||
strings.Replace(valid, `"expires_at":"2099-01-01T00:00:00Z"`, `"expires_at":"2099-01-01T00:00:00.100Z"`, 1),
|
||||
strings.Replace(valid, `"expires_at":"2099-01-01T00:00:00Z"`, `"expires_at":"2099-01-01T00:00:00Z","unknown":true`, 1),
|
||||
} {
|
||||
if _, err := protocol.DecodeNativeTunnelCredential([]byte(invalid)); err == nil {
|
||||
t.Fatalf("NativeTunnelCredential accepted invalid payload: %s", invalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPageInfoRejectsOutOfBoundsLimit(t *testing.T) {
|
||||
if _, err := protocol.DecodePageInfo([]byte(`{"limit":101,"next_cursor":""}`)); err == nil {
|
||||
t.Fatal("DecodePageInfo accepted limit above the contract maximum")
|
||||
|
||||
Reference in New Issue
Block a user