Protocol: split client session authority
This commit is contained in:
+127
-1
@@ -14,7 +14,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const SchemaSHA256 = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
|
||||
const SchemaSHA256 = "762d009c3d25d80c3850d975e45f7a6b3fd8adf5c93c8fa7dd11dfa993f8bbb1"
|
||||
const ProtocolVersion = "1.0.0"
|
||||
const CurrentWireVersion = "2"
|
||||
const NMinus1WireVersion = "1"
|
||||
@@ -97,6 +97,16 @@ type ChannelFrame struct {
|
||||
Payload string `json:"payload"`
|
||||
}
|
||||
|
||||
type ClientSessionAuthority struct {
|
||||
Version string `json:"version"`
|
||||
SessionID string `json:"session_id"`
|
||||
GatewayID string `json:"gateway_id"`
|
||||
Audience string `json:"audience"`
|
||||
ReconnectSequence int64 `json:"reconnect_sequence"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
Capabilities CapabilityProfile `json:"capabilities"`
|
||||
}
|
||||
|
||||
type ClipboardPolicy struct {
|
||||
ClientToProviderEnabled bool `json:"client_to_provider_enabled"`
|
||||
ProviderToClientEnabled bool `json:"provider_to_client_enabled"`
|
||||
@@ -1233,6 +1243,122 @@ func EncodeChannelFrame(value ChannelFrame) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (v ClientSessionAuthority) Validate() error {
|
||||
var violations []FieldViolation
|
||||
if v.Version == "" {
|
||||
violations = append(violations, FieldViolation{Field: "version", Code: "required"})
|
||||
}
|
||||
if v.Version != "1" && v.Version != "" {
|
||||
violations = append(violations, FieldViolation{Field: "version", Code: "invalid_value"})
|
||||
}
|
||||
if v.SessionID == "" {
|
||||
violations = append(violations, FieldViolation{Field: "session_id", Code: "required"})
|
||||
}
|
||||
if len(v.SessionID) < 1 && v.SessionID != "" {
|
||||
violations = append(violations, FieldViolation{Field: "session_id", Code: "min_length"})
|
||||
}
|
||||
if len(v.SessionID) > 128 {
|
||||
violations = append(violations, FieldViolation{Field: "session_id", Code: "max_length"})
|
||||
}
|
||||
if v.GatewayID == "" {
|
||||
violations = append(violations, FieldViolation{Field: "gateway_id", Code: "required"})
|
||||
}
|
||||
if len(v.GatewayID) < 1 && v.GatewayID != "" {
|
||||
violations = append(violations, FieldViolation{Field: "gateway_id", Code: "min_length"})
|
||||
}
|
||||
if len(v.GatewayID) > 128 {
|
||||
violations = append(violations, FieldViolation{Field: "gateway_id", Code: "max_length"})
|
||||
}
|
||||
if v.Audience == "" {
|
||||
violations = append(violations, FieldViolation{Field: "audience", Code: "required"})
|
||||
}
|
||||
if len(v.Audience) < 1 && v.Audience != "" {
|
||||
violations = append(violations, FieldViolation{Field: "audience", Code: "min_length"})
|
||||
}
|
||||
if len(v.Audience) > 256 {
|
||||
violations = append(violations, FieldViolation{Field: "audience", Code: "max_length"})
|
||||
}
|
||||
if v.ReconnectSequence != 0 && v.ReconnectSequence < 0 {
|
||||
violations = append(violations, FieldViolation{Field: "reconnect_sequence", Code: "minimum"})
|
||||
}
|
||||
if v.ExpiresAt == "" {
|
||||
violations = append(violations, FieldViolation{Field: "expires_at", Code: "required"})
|
||||
}
|
||||
if len(v.ExpiresAt) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "expires_at", Code: "max_length"})
|
||||
}
|
||||
if v.ExpiresAt != "" {
|
||||
if parsed, err := time.Parse(time.RFC3339Nano, v.ExpiresAt); err != nil || parsed.UTC().Format(time.RFC3339Nano) != v.ExpiresAt {
|
||||
violations = append(violations, FieldViolation{Field: "expires_at", Code: "invalid_time"})
|
||||
}
|
||||
}
|
||||
if reflect.DeepEqual(v.Capabilities, CapabilityProfile{}) {
|
||||
violations = append(violations, FieldViolation{Field: "capabilities", Code: "required"})
|
||||
}
|
||||
if err := v.Capabilities.Validate(); err != nil {
|
||||
violations = append(violations, FieldViolation{Field: "capabilities", Code: "invalid_object"})
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return ValidationError{Violations: violations}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecodeClientSessionAuthority(data []byte) (ClientSessionAuthority, error) {
|
||||
var value ClientSessionAuthority
|
||||
if len(data) > 1024*1024 {
|
||||
return value, errors.New("protocol payload exceeds limit")
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return value, err
|
||||
}
|
||||
if raw, ok := fields["audience"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "audience", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["capabilities"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "capabilities", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["expires_at"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "expires_at", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["reconnect_sequence"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "reconnect_sequence", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["session_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "session_id", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["version"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "version", Code: "required"}}}
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&value); err != nil {
|
||||
return value, err
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); err != io.EOF {
|
||||
if err == nil {
|
||||
return value, errors.New("trailing JSON value")
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
if err := value.Validate(); err != nil {
|
||||
return value, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func EncodeClientSessionAuthority(value ClientSessionAuthority) ([]byte, error) {
|
||||
if err := value.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (v ClipboardPolicy) Validate() error {
|
||||
var violations []FieldViolation
|
||||
if v.MaxTextBytes == 0 {
|
||||
|
||||
+1
-1
@@ -14,5 +14,5 @@
|
||||
},
|
||||
"generator_sha256": "00c1905fc611ca9e226cd90da761b48b8e203734b10542befea397a30082d360",
|
||||
"protocol_version": "1.0.0",
|
||||
"schema_sha256": "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
|
||||
"schema_sha256": "762d009c3d25d80c3850d975e45f7a6b3fd8adf5c93c8fa7dd11dfa993f8bbb1"
|
||||
}
|
||||
|
||||
Binary file not shown.
+44
-1
@@ -1,6 +1,6 @@
|
||||
// Code generated by tools/generate.py; DO NOT EDIT.
|
||||
#![allow(non_snake_case)]
|
||||
pub const SCHEMA_SHA256: &str = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f";
|
||||
pub const SCHEMA_SHA256: &str = "762d009c3d25d80c3850d975e45f7a6b3fd8adf5c93c8fa7dd11dfa993f8bbb1";
|
||||
pub const CURRENT_WIRE_VERSION: &str = "2";
|
||||
pub const N_MINUS_1_WIRE_VERSION: &str = "1";
|
||||
pub const N_MINUS_2_WIRE_VERSION: &str = "0";
|
||||
@@ -353,6 +353,49 @@ impl ChannelFrame {
|
||||
pub fn payload(&self) -> &String { &self.payload }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientSessionAuthority {
|
||||
version: String,
|
||||
sessionId: String,
|
||||
gatewayId: String,
|
||||
audience: String,
|
||||
reconnectSequence: i64,
|
||||
expiresAt: String,
|
||||
capabilities: CapabilityProfile,
|
||||
}
|
||||
|
||||
impl ClientSessionAuthority {
|
||||
pub fn new(version: String, sessionId: String, gatewayId: String, audience: String, reconnectSequence: i64, expiresAt: String, capabilities: CapabilityProfile) -> Result<Self, ValidationError> {
|
||||
let value = Self { version, sessionId, gatewayId, audience, reconnectSequence, expiresAt, capabilities };
|
||||
value.validate()?;
|
||||
Ok(value)
|
||||
}
|
||||
pub fn validate(&self) -> Result<(), ValidationError> {
|
||||
if self.version != "1" { return Err(ValidationError::new("version", "invalid_value")); }
|
||||
if self.sessionId.is_empty() { return Err(ValidationError::new("session_id", "required")); }
|
||||
if !self.sessionId.is_empty() && self.sessionId.len() < 1 { return Err(ValidationError::new("session_id", "min_length")); }
|
||||
if self.sessionId.len() > 128 { return Err(ValidationError::new("session_id", "max_length")); }
|
||||
if self.gatewayId.is_empty() { return Err(ValidationError::new("gateway_id", "required")); }
|
||||
if !self.gatewayId.is_empty() && self.gatewayId.len() < 1 { return Err(ValidationError::new("gateway_id", "min_length")); }
|
||||
if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); }
|
||||
if self.audience.is_empty() { return Err(ValidationError::new("audience", "required")); }
|
||||
if !self.audience.is_empty() && self.audience.len() < 1 { return Err(ValidationError::new("audience", "min_length")); }
|
||||
if self.audience.len() > 256 { return Err(ValidationError::new("audience", "max_length")); }
|
||||
if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); }
|
||||
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
|
||||
self.capabilities.validate().map_err(|_| ValidationError::new("capabilities", "invalid_object"))?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn version(&self) -> &String { &self.version }
|
||||
pub fn sessionId(&self) -> &String { &self.sessionId }
|
||||
pub fn gatewayId(&self) -> &String { &self.gatewayId }
|
||||
pub fn audience(&self) -> &String { &self.audience }
|
||||
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
|
||||
pub fn expiresAt(&self) -> &String { &self.expiresAt }
|
||||
pub fn capabilities(&self) -> &CapabilityProfile { &self.capabilities }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClipboardPolicy {
|
||||
clientToProviderEnabled: bool,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by tools/generate.py; DO NOT EDIT.
|
||||
import Foundation
|
||||
public typealias JSONObject = [String: String]
|
||||
public let schemaSHA256 = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
|
||||
public let schemaSHA256 = "762d009c3d25d80c3850d975e45f7a6b3fd8adf5c93c8fa7dd11dfa993f8bbb1"
|
||||
public let currentWireVersion = "2"
|
||||
public let nMinus1WireVersion = "1"
|
||||
public let nMinus2WireVersion = "0"
|
||||
@@ -433,6 +433,63 @@ public struct ChannelFrame: Codable, Equatable {
|
||||
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
|
||||
}
|
||||
|
||||
public struct ClientSessionAuthority: Codable, Equatable {
|
||||
public let version: String
|
||||
public let sessionId: String
|
||||
public let gatewayId: String
|
||||
public let audience: String
|
||||
public let reconnectSequence: Int64
|
||||
public let expiresAt: String
|
||||
public let capabilities: CapabilityProfile
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case version = "version"
|
||||
case sessionId = "session_id"
|
||||
case gatewayId = "gateway_id"
|
||||
case audience = "audience"
|
||||
case reconnectSequence = "reconnect_sequence"
|
||||
case expiresAt = "expires_at"
|
||||
case capabilities = "capabilities"
|
||||
}
|
||||
|
||||
public init(version: String, sessionId: String, gatewayId: String, audience: String, reconnectSequence: Int64, expiresAt: String, capabilities: CapabilityProfile) throws {
|
||||
self.version = version
|
||||
self.sessionId = sessionId
|
||||
self.gatewayId = gatewayId
|
||||
self.audience = audience
|
||||
self.reconnectSequence = reconnectSequence
|
||||
self.expiresAt = expiresAt
|
||||
self.capabilities = capabilities
|
||||
try validate()
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let all = try decoder.container(keyedBy: AnyCodingKey.self)
|
||||
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), audience: try c.decode(String.self, forKey: .audience), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), expiresAt: try c.decode(String.self, forKey: .expiresAt), capabilities: try c.decode(CapabilityProfile.self, forKey: .capabilities))
|
||||
}
|
||||
|
||||
public func validate() throws {
|
||||
if self.version != "1" { throw ContractValidationError(field: "version", code: "invalid_value") }
|
||||
if self.sessionId.isEmpty { throw ContractValidationError(field: "session_id", code: "required") }
|
||||
if !self.sessionId.isEmpty && self.sessionId.utf8.count < 1 { throw ContractValidationError(field: "session_id", code: "min_length") }
|
||||
if self.sessionId.utf8.count > 128 { throw ContractValidationError(field: "session_id", code: "max_length") }
|
||||
if self.gatewayId.isEmpty { throw ContractValidationError(field: "gateway_id", code: "required") }
|
||||
if !self.gatewayId.isEmpty && self.gatewayId.utf8.count < 1 { throw ContractValidationError(field: "gateway_id", code: "min_length") }
|
||||
if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") }
|
||||
if self.audience.isEmpty { throw ContractValidationError(field: "audience", code: "required") }
|
||||
if !self.audience.isEmpty && self.audience.utf8.count < 1 { throw ContractValidationError(field: "audience", code: "min_length") }
|
||||
if self.audience.utf8.count > 256 { throw ContractValidationError(field: "audience", code: "max_length") }
|
||||
if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") }
|
||||
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
|
||||
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
try self.capabilities.validate()
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
|
||||
}
|
||||
|
||||
public struct ClipboardPolicy: Codable, Equatable {
|
||||
public let clientToProviderEnabled: Bool
|
||||
public let providerToClientEnabled: Bool
|
||||
|
||||
Reference in New Issue
Block a user