Skip to content

Commit 1df1249

Browse files
authored
Add Sentry Oauth Installation Hook (#112)
1 parent 400ef61 commit 1df1249

File tree

4 files changed

+149
-22
lines changed

4 files changed

+149
-22
lines changed

app/models/pager_tree/integrations/sentry/v3.rb

+141-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
module PagerTree::Integrations
22
class Sentry::V3 < Integration
33
OPTIONS = [
4-
{key: :client_secret, type: :string, default: nil}
4+
{key: :client_secret, type: :string, default: nil},
5+
{key: :authorization_token, type: :string, default: nil},
6+
{key: :authorization_token_expires_at, type: :string, default: nil},
7+
{key: :authorization_refresh_token, type: :string, default: nil},
8+
{key: :code, type: :string, default: nil}
59
]
610
store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option"
711

812
after_initialize do
913
end
1014

15+
after_create_commit do
16+
verify_installation if initialize_authorization_token!
17+
end
18+
1119
def adapter_should_block_incoming?(request)
1220
should_block = false
1321
# https://docs.sentry.io/product/integrations/integration-platform/webhooks/#sentry-hook-signature
@@ -53,8 +61,134 @@ def adapter_process_create
5361
)
5462
end
5563

64+
def adapter_process_other
65+
_installation_process_other if installation?
66+
end
67+
68+
def verify_installation
69+
if thirdparty_id.present?
70+
HTTParty.put("https://sentry.io/api/0/sentry-app-installations/#{thirdparty_id}/",
71+
headers: {
72+
"Content-Type" => "application/json",
73+
"Authorization" => "Bearer #{option_authorization_token}"
74+
}, body: {
75+
status: "installed"
76+
}.to_json)
77+
78+
return true
79+
end
80+
81+
false
82+
rescue => e
83+
Rails.logger.error("Error sending Sentry App Installation Confirmation: #{e.message}")
84+
false
85+
end
86+
87+
def initialize_authorization_token!
88+
if thirdparty_id.present? && option_code.present?
89+
response = HTTParty.post("https://sentry.io/api/0/sentry-app-installations/#{thirdparty_id}/authorizations/",
90+
headers: {
91+
"Content-Type" => "application/json"
92+
}, body: {
93+
grant_type: "authorization_code",
94+
code: option_code,
95+
client_id: PagerTree::Integrations.integration_sentry_v3_client_id,
96+
client_secret: PagerTree::Integrations.integration_sentry_v3_client_secret
97+
}.to_json)
98+
99+
if response.code == 201
100+
json = JSON.parse(response.body)
101+
self.option_authorization_token = json.dig("token")
102+
self.option_authorization_refresh_token = json.dig("refreshToken")
103+
self.option_authorization_token_expires_at = json.dig("expiresAt")
104+
save!
105+
106+
return true
107+
end
108+
end
109+
110+
false
111+
rescue => e
112+
Rails.logger.error("Error initializing Sentry App Authorization Token: #{e.message}")
113+
false
114+
end
115+
116+
def refresh_authorization_token!
117+
if thirdparty_id.present? && option_authorization_refresh_token.present?
118+
response = HTTParty.post("https://sentry.io/api/0/sentry-app-installations/#{thirdparty_id}/authorizations/",
119+
headers: {
120+
"Content-Type" => "application/json"
121+
}, body: {
122+
grant_type: "refresh_token",
123+
refresh_token: option_authorization_refresh_token,
124+
client_id: PagerTree::Integrations.integration_sentry_v3_client_id,
125+
client_secret: PagerTree::Integrations.integration_sentry_v3_client_secret
126+
}.to_json)
127+
128+
if response.code == 201
129+
json = JSON.parse(response.body)
130+
self.option_authorization_token = json.dig("token")
131+
self.option_authorization_refresh_token = json.dig("refreshToken")
132+
self.option_authorization_token_expires_at = json.dig("expiresAt")
133+
save!
134+
135+
return true
136+
end
137+
end
138+
139+
false
140+
rescue => e
141+
Rails.logger.error("Error refreshing Sentry App Authorization Token: #{e.message}")
142+
false
143+
end
144+
56145
private
57146

147+
############################
148+
# START INSTALLATION
149+
# https://docs.sentry.io/product/integrations/integration-platform/webhooks/installation/
150+
############################
151+
152+
def _installation_adapter_thirdparty_id
153+
incoming_json.dig("id")
154+
end
155+
156+
def _installation_adapter_action
157+
:other
158+
end
159+
160+
def _installation_title
161+
""
162+
end
163+
164+
def _installation_description
165+
""
166+
end
167+
168+
def _installation_additional_datums
169+
[]
170+
end
171+
172+
def _installation_dedup_keys
173+
[]
174+
end
175+
176+
def _installation_process_other
177+
action = incoming_json.dig("action")
178+
if action == "created"
179+
# intentionally left blank
180+
elsif action == "deleted"
181+
# clear the thirdparty id off this integration and save
182+
self.thirdparty_id = nil
183+
self.discarded_at = Time.current
184+
save!
185+
end
186+
end
187+
188+
############################
189+
# END Installation
190+
############################
191+
58192
############################
59193
# START WEBHOOK
60194
# Undocumented webhook format (this is what you get when you just signup for a trial)
@@ -89,7 +223,7 @@ def _webhook_dedup_keys
89223
end
90224

91225
############################
92-
# END ISSUE
226+
# END WEBHOOK
93227
############################
94228

95229
############################
@@ -320,12 +454,16 @@ def error?
320454
hook_resource == "error"
321455
end
322456

457+
def installation?
458+
hook_resource == "installation"
459+
end
460+
323461
def webhook?
324462
incoming_headers["HTTP_SENTRY_HOOK_RESOURCE"].blank? && (hook_resource == "webhook")
325463
end
326464

327465
def should_process?
328-
issue? || event_alert? || metric_alert? || error? || webhook?
466+
issue? || event_alert? || metric_alert? || error? || installation? || webhook?
329467
end
330468

331469
def action
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
2-
<div class="form-group">
3-
<%= form.label :option_client_secret %>
4-
<%= form.text_field :option_client_secret, class: "form-control" %>
5-
<p class="form-hint"><%== t(".option_client_secret_hint_html") %></p>
6-
</div>
7-
</div>
1+
<%= form.hidden_field :option_code, class: "form-control" %>
Original file line numberDiff line numberDiff line change
@@ -1,12 +1 @@
1-
<div class="sm:col-span-2">
2-
<dt class="text-sm font-medium text-gray-500">
3-
<%= t("activerecord.attributes.pager_tree/integrations/sentry/v3.option_client_secret") %>
4-
</dt>
5-
<dd class="mt-1 text-sm text-gray-900">
6-
<div class="flex items-center gap-2">
7-
<p class="text-sm truncate">
8-
<%= mask integration.option_client_secret %>
9-
</p>
10-
</div>
11-
</dd>
12-
</div>
1+

lib/pager_tree/integrations.rb

+6
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,11 @@ module Integrations
2525

2626
mattr_accessor :integration_email_v3_inbox
2727
@@integration_email_v3_inbox = "a"
28+
29+
mattr_accessor :integration_sentry_v3_client_id
30+
@@integration_sentry_v3_client_id = ""
31+
32+
mattr_accessor :integration_sentry_v3_client_secret
33+
@@integration_sentry_v3_client_secret = ""
2834
end
2935
end

0 commit comments

Comments
 (0)