noresponse 5.1.1 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/no_response/accounts.rb +663 -71
- data/lib/no_response/late_results.rb +10 -8
- data/lib/no_response/models/operations/listaccounts_request.rb +52 -0
- data/lib/no_response/models/operations/listaccounts_request.rbi +26 -0
- data/lib/no_response/models/operations/listaccounts_response.rb +32 -0
- data/lib/no_response/models/operations/listaccounts_response.rbi +20 -0
- data/lib/no_response/models/operations/recordenforcement_request.rb +33 -0
- data/lib/no_response/models/operations/recordenforcement_request.rbi +20 -0
- data/lib/no_response/models/operations/recordenforcement_response.rb +32 -0
- data/lib/no_response/models/operations/recordenforcement_response.rbi +20 -0
- data/lib/no_response/models/operations.rb +4 -0
- data/lib/no_response/models/shared/accountdeleted.rb +49 -0
- data/lib/no_response/models/shared/accountdeleted.rbi +26 -0
- data/lib/no_response/models/shared/accountdeletedevent.rb +70 -0
- data/lib/no_response/models/shared/accountdeletedevent.rbi +38 -0
- data/lib/no_response/models/shared/accountlist.rb +40 -0
- data/lib/no_response/models/shared/accountlist.rbi +20 -0
- data/lib/no_response/models/shared/accountriskchanged.rb +44 -0
- data/lib/no_response/models/shared/accountriskchanged.rbi +20 -0
- data/lib/no_response/models/shared/accountriskchangedevent.rb +70 -0
- data/lib/no_response/models/shared/accountriskchangedevent.rbi +38 -0
- data/lib/no_response/models/shared/assessmentexpired.rb +82 -0
- data/lib/no_response/models/shared/assessmentexpired.rbi +38 -0
- data/lib/no_response/models/shared/assessmentupdateddata.rb +23 -0
- data/lib/no_response/models/shared/assessmentupdateddata.rbi +9 -0
- data/lib/no_response/models/shared/assessmentupdatedevent.rb +79 -0
- data/lib/no_response/models/shared/assessmentupdatedevent.rbi +38 -0
- data/lib/no_response/models/shared/conflicterrorcode.rb +1 -0
- data/lib/no_response/models/shared/endpointverification.rb +41 -0
- data/lib/no_response/models/shared/endpointverification.rbi +20 -0
- data/lib/no_response/models/shared/endpointverificationevent.rb +70 -0
- data/lib/no_response/models/shared/endpointverificationevent.rbi +38 -0
- data/lib/no_response/models/shared/enforcement.rb +156 -0
- data/lib/no_response/models/shared/enforcement.rbi +68 -0
- data/lib/no_response/models/shared/enforcementaction.rb +23 -0
- data/lib/no_response/models/shared/enforcementaction.rbi +9 -0
- data/lib/no_response/models/shared/enforcementprovider.rb +21 -0
- data/lib/no_response/models/shared/enforcementprovider.rbi +9 -0
- data/lib/no_response/models/shared/enforcementrequest.rb +122 -0
- data/lib/no_response/models/shared/enforcementrequest.rbi +50 -0
- data/lib/no_response/models/shared/enforcementstate.rb +22 -0
- data/lib/no_response/models/shared/enforcementstate.rbi +9 -0
- data/lib/no_response/models/shared/enforcementstatus.rb +19 -0
- data/lib/no_response/models/shared/enforcementstatus.rbi +9 -0
- data/lib/no_response/models/shared/environment.rb +16 -0
- data/lib/no_response/models/shared/environment.rbi +9 -0
- data/lib/no_response/models/shared/webhookendpointdisabled.rb +49 -0
- data/lib/no_response/models/shared/webhookendpointdisabled.rbi +26 -0
- data/lib/no_response/models/shared/webhookendpointdisabledevent.rb +70 -0
- data/lib/no_response/models/shared/webhookendpointdisabledevent.rbi +38 -0
- data/lib/no_response/models/shared/webhookevent.rb +23 -0
- data/lib/no_response/models/shared/webhookevent.rbi +9 -0
- data/lib/no_response/models/shared/webhookverificationresponse.rb +33 -0
- data/lib/no_response/models/shared/webhookverificationresponse.rbi +14 -0
- data/lib/no_response/models/shared/workspacedeleted.rb +55 -0
- data/lib/no_response/models/shared/workspacedeleted.rbi +26 -0
- data/lib/no_response/models/shared/workspacedeletedevent.rb +70 -0
- data/lib/no_response/models/shared/workspacedeletedevent.rbi +38 -0
- data/lib/no_response/models/shared.rb +23 -0
- data/lib/no_response/sdk_hooks/registration.rb +0 -1
- data/lib/no_response/sdkconfiguration.rb +2 -2
- data/lib/no_response/webhook_verification.rb +170 -0
- data/lib/noresponse.rb +2 -0
- metadata +56 -2
- data/lib/no_response/webhooks.rb +0 -264
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module NoResponse
|
|
5
|
+
module Models
|
|
6
|
+
module Shared
|
|
7
|
+
# The webhook_endpoint.disabled event.
|
|
8
|
+
class WebhookEndpointDisabledEvent
|
|
9
|
+
extend T::Sig
|
|
10
|
+
include Crystalline::MetadataFields
|
|
11
|
+
|
|
12
|
+
# The event id, equal to the NR-Webhook-Id header and across redeliveries and every endpoint the event fans out to.
|
|
13
|
+
field :id, ::String, {'format_json': {'letter_case': ::NoResponse::Utils.field_name("id"), required: true}}
|
|
14
|
+
# When the event was created. Equal across redeliveries; the signing timestamp header is per delivery.
|
|
15
|
+
field(
|
|
16
|
+
:created_at,
|
|
17
|
+
::String,
|
|
18
|
+
{'format_json': {'letter_case': ::NoResponse::Utils.field_name("created_at"), required: true}}
|
|
19
|
+
)
|
|
20
|
+
# One of the environment's endpoints stopped receiving events: every delivery attempt to it failed from failing_since until disabled_at, 72 hours or more. Its queued deliveries are dropped. Fix the receiver and verify it again with POST /v1/webhooks/{endpoint_id}/test (type endpoint.verification) to resume delivery.
|
|
21
|
+
field(
|
|
22
|
+
:data,
|
|
23
|
+
Models::Shared::WebhookEndpointDisabled,
|
|
24
|
+
{'format_json': {'letter_case': ::NoResponse::Utils.field_name("data"), required: true}}
|
|
25
|
+
)
|
|
26
|
+
# Always webhook_endpoint.disabled.
|
|
27
|
+
field :type, ::String, {'format_json': {'letter_case': ::NoResponse::Utils.field_name("type"), required: true}}
|
|
28
|
+
# Present, and true, only on an event sent by POST /v1/webhooks/{endpoint_id}/test: its data is a fixed example assessment, not one of the workspace's, so a receiver acknowledges it and applies nothing. Real events have no test member.
|
|
29
|
+
field(
|
|
30
|
+
:test,
|
|
31
|
+
Crystalline::Nilable.new(Crystalline::Boolean.new),
|
|
32
|
+
{'format_json': {'letter_case': ::NoResponse::Utils.field_name("test")}}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
sig {
|
|
36
|
+
params(
|
|
37
|
+
id: ::String,
|
|
38
|
+
created_at: ::String,
|
|
39
|
+
data: Models::Shared::WebhookEndpointDisabled,
|
|
40
|
+
type: ::String,
|
|
41
|
+
test: T.nilable(T::Boolean)
|
|
42
|
+
)
|
|
43
|
+
.void
|
|
44
|
+
}
|
|
45
|
+
def initialize(id:, created_at:, data:, type: "webhook_endpoint.disabled", test: nil)
|
|
46
|
+
@id = id
|
|
47
|
+
@created_at = created_at
|
|
48
|
+
@data = data
|
|
49
|
+
unless type == "webhook_endpoint.disabled"
|
|
50
|
+
raise ArgumentError, "Invalid value for type"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@type = "webhook_endpoint.disabled"
|
|
54
|
+
@test = test
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
58
|
+
def ==(other)
|
|
59
|
+
return false unless other.is_a?(self.class)
|
|
60
|
+
return false unless @id == other.id
|
|
61
|
+
return false unless @created_at == other.created_at
|
|
62
|
+
return false unless @data == other.data
|
|
63
|
+
return false unless @type == other.type
|
|
64
|
+
return false unless @test == other.test
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
class NoResponse::Models::Shared::WebhookEndpointDisabledEvent
|
|
5
|
+
extend ::Crystalline::MetadataFields::ClassMethods
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class NoResponse::Models::Shared::WebhookEndpointDisabledEvent
|
|
9
|
+
def id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def id=(str_)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def created_at
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def created_at=(str_)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def data
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def data=(str_)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def type
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def type=(str_)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test=(str_)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module NoResponse
|
|
5
|
+
module Models
|
|
6
|
+
module Shared
|
|
7
|
+
# The body of every webhook delivery: {id, type, created_at, data}, where type chooses the shape of data. Deliveries are at least once and unordered: deduplicate on id, and keep an assessment only when its detail.assessment_version is newer than the one you hold. GET /v1/assessments/{assessment_id} stays authoritative.
|
|
8
|
+
class WebhookEvent
|
|
9
|
+
extend T::Sig
|
|
10
|
+
include Crystalline::MetadataFields
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
16
|
+
def ==(other)
|
|
17
|
+
return false unless other.is_a?(self.class)
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module NoResponse
|
|
5
|
+
module Models
|
|
6
|
+
module Shared
|
|
7
|
+
# The body an endpoint returns, with status 200 and Content-Type application/json within 5 seconds, to an endpoint.verification event. A 2xx without the echoed challenge does not verify the endpoint.
|
|
8
|
+
class WebhookVerificationResponse
|
|
9
|
+
extend T::Sig
|
|
10
|
+
include Crystalline::MetadataFields
|
|
11
|
+
|
|
12
|
+
# Endpoint verification nonce: 32 random bytes, unpadded base64url (43 characters). Fresh per verification.
|
|
13
|
+
field(
|
|
14
|
+
:challenge,
|
|
15
|
+
::String,
|
|
16
|
+
{'format_json': {'letter_case': ::NoResponse::Utils.field_name("challenge"), required: true}}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
sig { params(challenge: ::String).void }
|
|
20
|
+
def initialize(challenge:)
|
|
21
|
+
@challenge = challenge
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
25
|
+
def ==(other)
|
|
26
|
+
return false unless other.is_a?(self.class)
|
|
27
|
+
return false unless @challenge == other.challenge
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
class NoResponse::Models::Shared::WebhookVerificationResponse
|
|
5
|
+
extend ::Crystalline::MetadataFields::ClassMethods
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class NoResponse::Models::Shared::WebhookVerificationResponse
|
|
9
|
+
def challenge
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def challenge=(str_)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module NoResponse
|
|
5
|
+
module Models
|
|
6
|
+
module Shared
|
|
7
|
+
# Terminal for the receiving endpoint's environment: its workspace was deleted. Every key of the environment already answers 401, its assessments and accounts are gone, and no further event follows. The workspace id is not reused, so a later event naming it cannot arrive.
|
|
8
|
+
class WorkspaceDeleted
|
|
9
|
+
extend T::Sig
|
|
10
|
+
include Crystalline::MetadataFields
|
|
11
|
+
|
|
12
|
+
# The workspace id, which is also its live environment's workspace id; the test environment's is the same id with :test appended. A workspace created through the API is tnt_ and a 26-character ULID.
|
|
13
|
+
field(
|
|
14
|
+
:workspace_id,
|
|
15
|
+
::String,
|
|
16
|
+
{'format_json': {'letter_case': ::NoResponse::Utils.field_name("workspace_id"), required: true}}
|
|
17
|
+
)
|
|
18
|
+
# The environment whose endpoints receive this event: live, or test for the workspace's test environment.
|
|
19
|
+
field(
|
|
20
|
+
:environment,
|
|
21
|
+
Models::Shared::Environment,
|
|
22
|
+
{
|
|
23
|
+
'format_json': {
|
|
24
|
+
'letter_case': ::NoResponse::Utils.field_name("environment"),
|
|
25
|
+
required: true,
|
|
26
|
+
'decoder': ::NoResponse::Utils.enum_from_string(Models::Shared::Environment, false)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
# When the deletion took effect, equal to deleted_at in the DELETE answer.
|
|
31
|
+
field(
|
|
32
|
+
:deleted_at,
|
|
33
|
+
::String,
|
|
34
|
+
{'format_json': {'letter_case': ::NoResponse::Utils.field_name("deleted_at"), required: true}}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
sig { params(workspace_id: ::String, environment: Models::Shared::Environment, deleted_at: ::String).void }
|
|
38
|
+
def initialize(workspace_id:, environment:, deleted_at:)
|
|
39
|
+
@workspace_id = workspace_id
|
|
40
|
+
@environment = environment
|
|
41
|
+
@deleted_at = deleted_at
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
45
|
+
def ==(other)
|
|
46
|
+
return false unless other.is_a?(self.class)
|
|
47
|
+
return false unless @workspace_id == other.workspace_id
|
|
48
|
+
return false unless @environment == other.environment
|
|
49
|
+
return false unless @deleted_at == other.deleted_at
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
class NoResponse::Models::Shared::WorkspaceDeleted
|
|
5
|
+
extend ::Crystalline::MetadataFields::ClassMethods
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class NoResponse::Models::Shared::WorkspaceDeleted
|
|
9
|
+
def workspace_id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def workspace_id=(str_)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def environment
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def environment=(str_)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def deleted_at
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def deleted_at=(str_)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module NoResponse
|
|
5
|
+
module Models
|
|
6
|
+
module Shared
|
|
7
|
+
# The workspace.deleted event.
|
|
8
|
+
class WorkspaceDeletedEvent
|
|
9
|
+
extend T::Sig
|
|
10
|
+
include Crystalline::MetadataFields
|
|
11
|
+
|
|
12
|
+
# The event id, equal to the NR-Webhook-Id header and across redeliveries and every endpoint the event fans out to.
|
|
13
|
+
field :id, ::String, {'format_json': {'letter_case': ::NoResponse::Utils.field_name("id"), required: true}}
|
|
14
|
+
# When the event was created. Equal across redeliveries; the signing timestamp header is per delivery.
|
|
15
|
+
field(
|
|
16
|
+
:created_at,
|
|
17
|
+
::String,
|
|
18
|
+
{'format_json': {'letter_case': ::NoResponse::Utils.field_name("created_at"), required: true}}
|
|
19
|
+
)
|
|
20
|
+
# Terminal for the receiving endpoint's environment: its workspace was deleted. Every key of the environment already answers 401, its assessments and accounts are gone, and no further event follows. The workspace id is not reused, so a later event naming it cannot arrive.
|
|
21
|
+
field(
|
|
22
|
+
:data,
|
|
23
|
+
Models::Shared::WorkspaceDeleted,
|
|
24
|
+
{'format_json': {'letter_case': ::NoResponse::Utils.field_name("data"), required: true}}
|
|
25
|
+
)
|
|
26
|
+
# Always workspace.deleted.
|
|
27
|
+
field :type, ::String, {'format_json': {'letter_case': ::NoResponse::Utils.field_name("type"), required: true}}
|
|
28
|
+
# Present, and true, only on an event sent by POST /v1/webhooks/{endpoint_id}/test: its data is a fixed example assessment, not one of the workspace's, so a receiver acknowledges it and applies nothing. Real events have no test member.
|
|
29
|
+
field(
|
|
30
|
+
:test,
|
|
31
|
+
Crystalline::Nilable.new(Crystalline::Boolean.new),
|
|
32
|
+
{'format_json': {'letter_case': ::NoResponse::Utils.field_name("test")}}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
sig {
|
|
36
|
+
params(
|
|
37
|
+
id: ::String,
|
|
38
|
+
created_at: ::String,
|
|
39
|
+
data: Models::Shared::WorkspaceDeleted,
|
|
40
|
+
type: ::String,
|
|
41
|
+
test: T.nilable(T::Boolean)
|
|
42
|
+
)
|
|
43
|
+
.void
|
|
44
|
+
}
|
|
45
|
+
def initialize(id:, created_at:, data:, type: "workspace.deleted", test: nil)
|
|
46
|
+
@id = id
|
|
47
|
+
@created_at = created_at
|
|
48
|
+
@data = data
|
|
49
|
+
unless type == "workspace.deleted"
|
|
50
|
+
raise ArgumentError, "Invalid value for type"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@type = "workspace.deleted"
|
|
54
|
+
@test = test
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
58
|
+
def ==(other)
|
|
59
|
+
return false unless other.is_a?(self.class)
|
|
60
|
+
return false unless @id == other.id
|
|
61
|
+
return false unless @created_at == other.created_at
|
|
62
|
+
return false unless @data == other.data
|
|
63
|
+
return false unless @type == other.type
|
|
64
|
+
return false unless @test == other.test
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
class NoResponse::Models::Shared::WorkspaceDeletedEvent
|
|
5
|
+
extend ::Crystalline::MetadataFields::ClassMethods
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class NoResponse::Models::Shared::WorkspaceDeletedEvent
|
|
9
|
+
def id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def id=(str_)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def created_at
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def created_at=(str_)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def data
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def data=(str_)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def type
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def type=(str_)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test=(str_)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -7,8 +7,13 @@ module NoResponse
|
|
|
7
7
|
autoload :Account, "no_response/models/shared/account.rb"
|
|
8
8
|
autoload :AccountDecision, "no_response/models/shared/accountdecision.rb"
|
|
9
9
|
autoload :AccountDecisionsByFlow, "no_response/models/shared/accountdecisionsbyflow.rb"
|
|
10
|
+
autoload :AccountDeleted, "no_response/models/shared/accountdeleted.rb"
|
|
11
|
+
autoload :AccountDeletedEvent, "no_response/models/shared/accountdeletedevent.rb"
|
|
10
12
|
autoload :AccountDeletion, "no_response/models/shared/accountdeletion.rb"
|
|
13
|
+
autoload :AccountList, "no_response/models/shared/accountlist.rb"
|
|
11
14
|
autoload :AccountRisk, "no_response/models/shared/accountrisk.rb"
|
|
15
|
+
autoload :AccountRiskChanged, "no_response/models/shared/accountriskchanged.rb"
|
|
16
|
+
autoload :AccountRiskChangedEvent, "no_response/models/shared/accountriskchangedevent.rb"
|
|
12
17
|
autoload :AccountRiskReason, "no_response/models/shared/accountriskreason.rb"
|
|
13
18
|
autoload :Assessment, "no_response/models/shared/assessment.rb"
|
|
14
19
|
autoload :AssessmentDeletion, "no_response/models/shared/assessmentdeletion.rb"
|
|
@@ -16,6 +21,7 @@ module NoResponse
|
|
|
16
21
|
autoload :AssessmentDetail, "no_response/models/shared/assessmentdetail.rb"
|
|
17
22
|
autoload :AssessmentDetailAuthContext, "no_response/models/shared/assessmentdetailauthcontext.rb"
|
|
18
23
|
autoload :AssessmentDetailTiming, "no_response/models/shared/assessmentdetailtiming.rb"
|
|
24
|
+
autoload :AssessmentExpired, "no_response/models/shared/assessmentexpired.rb"
|
|
19
25
|
autoload :AssessmentList, "no_response/models/shared/assessmentlist.rb"
|
|
20
26
|
autoload :AssessmentOutcome, "no_response/models/shared/assessmentoutcome.rb"
|
|
21
27
|
autoload :AssessmentOutcomeRequest, "no_response/models/shared/assessmentoutcomerequest.rb"
|
|
@@ -25,6 +31,8 @@ module NoResponse
|
|
|
25
31
|
autoload :AssessmentRequestIdentifiers, "no_response/models/shared/assessmentrequestidentifiers.rb"
|
|
26
32
|
autoload :AssessmentRequestIssuedCredential, "no_response/models/shared/assessmentrequestissuedcredential.rb"
|
|
27
33
|
autoload :AssessmentRequestPolicyContext, "no_response/models/shared/assessmentrequestpolicycontext.rb"
|
|
34
|
+
autoload :AssessmentUpdatedData, "no_response/models/shared/assessmentupdateddata.rb"
|
|
35
|
+
autoload :AssessmentUpdatedEvent, "no_response/models/shared/assessmentupdatedevent.rb"
|
|
28
36
|
autoload :AssuranceLevel, "no_response/models/shared/assurancelevel.rb"
|
|
29
37
|
autoload :AuthContext, "no_response/models/shared/authcontext.rb"
|
|
30
38
|
autoload :AuthContextMfa, "no_response/models/shared/authcontextmfa.rb"
|
|
@@ -42,6 +50,15 @@ module NoResponse
|
|
|
42
50
|
autoload :ConsistencyValue, "no_response/models/shared/consistencyvalue.rb"
|
|
43
51
|
autoload :Country, "no_response/models/shared/country.rb"
|
|
44
52
|
autoload :Depth, "no_response/models/shared/depth.rb"
|
|
53
|
+
autoload :EndpointVerification, "no_response/models/shared/endpointverification.rb"
|
|
54
|
+
autoload :EndpointVerificationEvent, "no_response/models/shared/endpointverificationevent.rb"
|
|
55
|
+
autoload :Enforcement, "no_response/models/shared/enforcement.rb"
|
|
56
|
+
autoload :EnforcementAction, "no_response/models/shared/enforcementaction.rb"
|
|
57
|
+
autoload :EnforcementProvider, "no_response/models/shared/enforcementprovider.rb"
|
|
58
|
+
autoload :EnforcementRequest, "no_response/models/shared/enforcementrequest.rb"
|
|
59
|
+
autoload :EnforcementState, "no_response/models/shared/enforcementstate.rb"
|
|
60
|
+
autoload :EnforcementStatus, "no_response/models/shared/enforcementstatus.rb"
|
|
61
|
+
autoload :Environment, "no_response/models/shared/environment.rb"
|
|
45
62
|
autoload :Failure, "no_response/models/shared/failure.rb"
|
|
46
63
|
autoload :FailureReason, "no_response/models/shared/failurereason.rb"
|
|
47
64
|
autoload :Flow, "no_response/models/shared/flow.rb"
|
|
@@ -100,9 +117,12 @@ module NoResponse
|
|
|
100
117
|
autoload :WebhookDeliveryList, "no_response/models/shared/webhookdeliverylist.rb"
|
|
101
118
|
autoload :WebhookEndpoint, "no_response/models/shared/webhookendpoint.rb"
|
|
102
119
|
autoload :WebhookEndpointCreated, "no_response/models/shared/webhookendpointcreated.rb"
|
|
120
|
+
autoload :WebhookEndpointDisabled, "no_response/models/shared/webhookendpointdisabled.rb"
|
|
121
|
+
autoload :WebhookEndpointDisabledEvent, "no_response/models/shared/webhookendpointdisabledevent.rb"
|
|
103
122
|
autoload :WebhookEndpointList, "no_response/models/shared/webhookendpointlist.rb"
|
|
104
123
|
autoload :WebhookEndpointRequest, "no_response/models/shared/webhookendpointrequest.rb"
|
|
105
124
|
autoload :WebhookEndpointState, "no_response/models/shared/webhookendpointstate.rb"
|
|
125
|
+
autoload :WebhookEvent, "no_response/models/shared/webhookevent.rb"
|
|
106
126
|
autoload :WebhookKeyRotation, "no_response/models/shared/webhookkeyrotation.rb"
|
|
107
127
|
autoload :WebhookKeyRotationRequest, "no_response/models/shared/webhookkeyrotationrequest.rb"
|
|
108
128
|
autoload :WebhookReplay, "no_response/models/shared/webhookreplay.rb"
|
|
@@ -110,6 +130,9 @@ module NoResponse
|
|
|
110
130
|
autoload :WebhookTest, "no_response/models/shared/webhooktest.rb"
|
|
111
131
|
autoload :WebhookTestRequest, "no_response/models/shared/webhooktestrequest.rb"
|
|
112
132
|
autoload :WebhookTestRequestEvent, "no_response/models/shared/webhooktestrequestevent.rb"
|
|
133
|
+
autoload :WebhookVerificationResponse, "no_response/models/shared/webhookverificationresponse.rb"
|
|
134
|
+
autoload :WorkspaceDeleted, "no_response/models/shared/workspacedeleted.rb"
|
|
135
|
+
autoload :WorkspaceDeletedEvent, "no_response/models/shared/workspacedeletedevent.rb"
|
|
113
136
|
autoload :AlternateName, "no_response/models/shared/alternate_name.rb"
|
|
114
137
|
autoload :AlternateNameUnion, "no_response/models/shared/alternate_name_union.rb"
|
|
115
138
|
autoload :Email, "no_response/models/shared/email.rb"
|
|
@@ -95,9 +95,9 @@ module NoResponse
|
|
|
95
95
|
|
|
96
96
|
@language = "ruby"
|
|
97
97
|
@openapi_doc_version = "1"
|
|
98
|
-
@sdk_version = "
|
|
98
|
+
@sdk_version = "6.0.1"
|
|
99
99
|
@gen_version = "internal"
|
|
100
|
-
@user_agent = "noresponse-sdk/ruby
|
|
100
|
+
@user_agent = "noresponse-sdk/ruby 6.0.1 internal 1 noresponse"
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
sig { returns([String, T::Hash[Symbol, String]]) }
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require "openssl"
|
|
6
|
+
|
|
7
|
+
module NoResponse
|
|
8
|
+
# Verifies webhook deliveries and parses them into typed events.
|
|
9
|
+
module WebhookVerification
|
|
10
|
+
extend T::Sig
|
|
11
|
+
|
|
12
|
+
# How far the NR-Webhook-Timestamp header may be from the receiver's clock, in seconds.
|
|
13
|
+
DEFAULT_TOLERANCE_SECONDS = 300
|
|
14
|
+
|
|
15
|
+
# Raised by construct_event for a delivery that must not be processed:
|
|
16
|
+
# answer the delivery with a 400 and do not process it. code is one of
|
|
17
|
+
# "missing_header", "duplicate_header", "invalid_timestamp", "unknown_key",
|
|
18
|
+
# "invalid_signature" or "invalid_payload".
|
|
19
|
+
class VerificationError < StandardError
|
|
20
|
+
extend T::Sig
|
|
21
|
+
|
|
22
|
+
sig { returns(String) }
|
|
23
|
+
attr_reader :code
|
|
24
|
+
|
|
25
|
+
sig { params(code: String, message: String).void }
|
|
26
|
+
def initialize(code, message)
|
|
27
|
+
super("webhook verification failed: #{message} (#{code})")
|
|
28
|
+
@code = code
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
HEADER_NAMES = [
|
|
33
|
+
"NR-Webhook-Id",
|
|
34
|
+
"NR-Webhook-Timestamp",
|
|
35
|
+
"NR-Webhook-Key-Id",
|
|
36
|
+
"NR-Webhook-Signature"
|
|
37
|
+
].freeze
|
|
38
|
+
private_constant :HEADER_NAMES
|
|
39
|
+
EVENT_TYPE = Crystalline::DiscriminatedUnion.new(
|
|
40
|
+
"type",
|
|
41
|
+
{
|
|
42
|
+
"assessment.updated" => Models::Shared::AssessmentUpdatedEvent,
|
|
43
|
+
"endpoint.verification" => Models::Shared::EndpointVerificationEvent,
|
|
44
|
+
"account.deleted" => Models::Shared::AccountDeletedEvent,
|
|
45
|
+
"workspace.deleted" => Models::Shared::WorkspaceDeletedEvent,
|
|
46
|
+
"webhook_endpoint.disabled" => Models::Shared::WebhookEndpointDisabledEvent,
|
|
47
|
+
"account.risk_changed" => Models::Shared::AccountRiskChangedEvent
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
private_constant :EVENT_TYPE
|
|
51
|
+
|
|
52
|
+
class << self
|
|
53
|
+
extend T::Sig
|
|
54
|
+
|
|
55
|
+
# Verifies a webhook delivery and parses its body.
|
|
56
|
+
#
|
|
57
|
+
# payload is the raw request body exactly as received: re-encoding parsed JSON breaks the signature.
|
|
58
|
+
# headers is a Hash, or an Array of name/value pairs, whose values are Strings or Arrays of Strings.
|
|
59
|
+
# A header repeated in any letter case, or sent as a comma-joined list, is refused.
|
|
60
|
+
# secrets maps each key id, sent in NR-Webhook-Key-Id, to its secret, so a receiver can hold two during a rotation.
|
|
61
|
+
# now is the receiver's clock (a Time or Unix seconds) used for the tolerance check.
|
|
62
|
+
sig do
|
|
63
|
+
params(
|
|
64
|
+
payload: String,
|
|
65
|
+
headers: T::Enumerable[T.untyped],
|
|
66
|
+
secrets: T::Hash[String, String],
|
|
67
|
+
tolerance_seconds: Integer,
|
|
68
|
+
now: T.nilable(T.any(Time, Integer))
|
|
69
|
+
)
|
|
70
|
+
.returns(
|
|
71
|
+
T.any(
|
|
72
|
+
Models::Shared::AssessmentUpdatedEvent,
|
|
73
|
+
Models::Shared::EndpointVerificationEvent,
|
|
74
|
+
Models::Shared::AccountDeletedEvent,
|
|
75
|
+
Models::Shared::WorkspaceDeletedEvent,
|
|
76
|
+
Models::Shared::WebhookEndpointDisabledEvent,
|
|
77
|
+
Models::Shared::AccountRiskChangedEvent
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
def construct_event(payload, headers, secrets, tolerance_seconds: DEFAULT_TOLERANCE_SECONDS, now: nil)
|
|
82
|
+
raw = payload.b
|
|
83
|
+
values = header_values(headers)
|
|
84
|
+
id = values.fetch("NR-Webhook-Id")
|
|
85
|
+
timestamp = values.fetch("NR-Webhook-Timestamp")
|
|
86
|
+
key_id = values.fetch("NR-Webhook-Key-Id")
|
|
87
|
+
signature = values.fetch("NR-Webhook-Signature")
|
|
88
|
+
|
|
89
|
+
unless timestamp.match?(/\A[0-9]{1,12}\z/)
|
|
90
|
+
raise VerificationError.new("invalid_timestamp", "NR-Webhook-Timestamp is not a Unix time in seconds")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if ((now || Time.now).to_i - timestamp.to_i).abs > tolerance_seconds
|
|
94
|
+
raise VerificationError.new("invalid_timestamp", "NR-Webhook-Timestamp is outside the tolerance")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
unless secrets.key?(key_id)
|
|
98
|
+
raise VerificationError.new("unknown_key", "NR-Webhook-Key-Id names a key this receiver does not hold")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
secret = secrets.fetch(key_id)
|
|
102
|
+
raise VerificationError.new("invalid_signature", "the webhook secret is empty") if secret.empty?
|
|
103
|
+
|
|
104
|
+
received = decode_signature(signature)
|
|
105
|
+
substitutions = {"{timestamp}" => timestamp, "{id}" => id}
|
|
106
|
+
signed = "{timestamp}.".gsub(/\{(?:timestamp|id)\}/, substitutions).b + raw
|
|
107
|
+
expected = OpenSSL::HMAC.digest("SHA256", secret, signed)
|
|
108
|
+
unless received.bytesize == expected.bytesize && OpenSSL.fixed_length_secure_compare(expected, received)
|
|
109
|
+
raise VerificationError.new("invalid_signature", "NR-Webhook-Signature does not match")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
body = begin
|
|
113
|
+
JSON.parse(raw)
|
|
114
|
+
rescue JSON::ParserError, EncodingError
|
|
115
|
+
raise VerificationError.new("invalid_payload", "the body is not JSON")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
unless body.is_a?(::Hash) && body["id"].is_a?(::String) && body["id"] == id
|
|
119
|
+
raise VerificationError.new("invalid_payload", "the body's id does not equal NR-Webhook-Id")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
event = begin
|
|
123
|
+
Crystalline.unmarshal_json(body, EVENT_TYPE)
|
|
124
|
+
rescue StandardError
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if event.nil? || Crystalline.unknown?(event)
|
|
129
|
+
raise VerificationError.new("invalid_payload", "the body is not a known webhook event")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
event
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
sig { params(headers: T::Enumerable[T.untyped]).returns(T::Hash[String, String]) }
|
|
138
|
+
def header_values(headers)
|
|
139
|
+
HEADER_NAMES.to_h do |name|
|
|
140
|
+
found = T.let([], T::Array[String])
|
|
141
|
+
headers.each do |key, value|
|
|
142
|
+
found.concat(Array(value).map(&:to_s)) if key.to_s.casecmp?(name)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if found.length > 1 || found.first&.include?(",")
|
|
146
|
+
raise VerificationError.new("duplicate_header", "#{name} is repeated")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
raise VerificationError.new("missing_header", "#{name} is missing") if found.first.to_s.empty?
|
|
150
|
+
|
|
151
|
+
[name, found.first.to_s]
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
sig { params(signature: String).returns(String) }
|
|
156
|
+
def decode_signature(signature)
|
|
157
|
+
encoded = signature.delete_prefix("v1=")
|
|
158
|
+
if encoded.length == signature.length
|
|
159
|
+
raise VerificationError.new("invalid_signature", "NR-Webhook-Signature is malformed")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
unless encoded.match?(/\A(?:[0-9a-fA-F]{2})*\z/)
|
|
163
|
+
raise VerificationError.new("invalid_signature", "NR-Webhook-Signature is malformed")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
[encoded].pack("H*")
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
data/lib/noresponse.rb
CHANGED