forked from datastax/cla-enforcer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.rb
355 lines (293 loc) · 10 KB
/
app.rb
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
require 'rubygems'
require 'bundler/setup'
require 'openssl'
require 'json'
require 'sinatra'
require 'sinatra/flash'
require 'sinatra/auth/github'
require 'sinatra/param'
require 'rack/parser'
require 'nori'
require 'nori/parser/nokogiri'
$: << File.expand_path('../', __FILE__)
$: << File.expand_path('../lib', __FILE__)
require 'cla'
post '/github/?' do
content_type 'text/plain'
request.body.rewind
content = request.body.read
halt(403, "Invalid hub signature. Expected #{('sha1=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), ENV['GITHUB_VERIFIER_SECRET'], content))}, found #{request.env['HTTP_X_HUB_SIGNATURE']}...") unless request.env['HTTP_X_HUB_SIGNATURE'] == ('sha1=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), ENV['GITHUB_VERIFIER_SECRET'], content))
# halt(403, 'No payload found, halting...') unless params.include?('payload')
puts "MREMOND: #{content}"
begin
payload = JSON.load(content)
rescue => e
CLA.logger.error("#{e.class.name}: #{e.message}\n " + e.backtrace.join("\n "))
halt(406, 'Invalid JSON in payload')
end
if pull_request_opened?(payload)
enqueue_command('github:pull_request', {
user: payload['repository']['owner']['login'],
repo: payload['repository']['name'],
sender: payload['sender']['login'],
number: payload['number']
})
status 202
body "Checking CLA status for #{payload['sender']['login']}..."
elsif pull_request_commented?(payload)
# TODO ? CLA comment: Nothing is implemented to handle comments.
comment = payload['comment']['body']
if /\[cla (\w+)\]/ =~ comment
command = $1
enqueue_command('github:command', {
user: payload['repository']['owner']['login'],
repo: payload['repository']['name'],
sender: payload['sender']['login'],
number: payload['issue']['number'],
owner: payload['issue']['user']['login'],
command: command
})
status 202
body "Handling CLA command: #{command.inspect} for #{payload['sender']['login']}..."
else
status 200
body "Pull request comment skipped, no CLA action required"
end
else
status 200
body "Unexpected event notification, no CLA action required"
end
end
post '/eversign/?' do
request.body.rewind
content = request.body.read
begin
payload = JSON.load(content)
rescue => e
CLA.logger.error("#{e.class.name}: #{e.message}\n " + e.backtrace.join("\n "))
halt(406, 'Invalid JSON in payload')
end
if payload.has_key?('event_time') &&
payload.has_key?('meta') &&
payload['meta'].has_key?('related_document_hash') &&
'document_signed' == payload['event_type']
CLA.logger.info "#{payload['meta']['related_document_hash']} #{payload['event_type']} at #{Time.at(payload['event_time'])}"
enqueue_command('docusign:update', {
envelope_id: payload['meta']['related_document_hash'],
status: 'Completed',
updated_at: Time.at(payload['event_time'])
})
else
CLA.logger.info "#{payload['event_type']} at #{Time.now}"
end
status 200
end
post '/docusign/?' do
if envelope_status_update?(params)
status = params['DocuSignEnvelopeInformation']['EnvelopeStatus']['RecipientStatuses']['RecipientStatus']['Status']
enqueue_command('docusign:update', {
envelope_id: params['DocuSignEnvelopeInformation']['EnvelopeStatus']['EnvelopeID'],
status: status,
updated_at: params['DocuSignEnvelopeInformation']['EnvelopeStatus']['RecipientStatuses']['RecipientStatus'][status] || Time.now
})
status 202
body "Processing envelope status update..."
else
status 200
body "Unexpected docusign connect notification, no action required"
end
end
get '/' do
erb :index, :layout => :layout
end
get '/form/?' do
authenticate!
redirect to('/status') if CLA.contributors.include?(github_user.login)
@user = github_user
@param = flash[:param]
@message = flash[:message]
erb :form, :layout => :layout
end
get '/status/?' do
authenticate!
@status = CLA.contributors.find(github_user.login)
@param = flash[:param]
@message = flash[:message]
redirect to('/form') unless @status
erb :status, :layout => :layout
end
post '/reset/?' do
authenticate!
envelope_id = CLA.contributors.delete(github_user.login)
if envelope_id
enqueue_command('docusign:void', envelope_id: envelope_id)
redirect to('/form')
else
redirect to('/status'), param: 'form', message: 'Cannot reset completed CLA'
end
end
post '/submit/?' do
redirect to('/') unless github_user
redirect to('/status') if CLA.contributors.include?(github_user.login)
param :name, String, max_length: 128, required: true
param :email, String, max_length: 254, required: true, format: EmailAddress
param :company, String, max_length: 128, required: true
CLA.contributors.insert(github_user.login, params['name'], params['email'], params['company'])
enqueue_command('docusign:send', {
login: github_user.login,
name: params['name'],
email: params['email'],
company: params['company']
})
@name = params['name']
@email = params['email']
status 201
erb :accepted
end
get '/authorize/?' do
if params["error"]
redirect to('/unauthenticated'), error: params["error"]
else
authenticate!
redirect to(session.delete('return_to', '/'))
end
end
get '/unauthenticated/?' do
status 403
content_type 'text/plain'
if flash[:error]
body "Unauthenticated: #{flash[:error]}"
else
body 'Unauthenticated'
end
end
get '/ping/?' do
status 200
content_type 'text/plain'
body 'Success!'
end
set :views, 'app/views'
set :public_folder, 'app/static'
set :sessions, key: 'cla.session', expire_after: 3600
set :session_secret, ENV['SINATRA_SECRET']
disable :show_exceptions, :raise_errors, :dump_errors
enable :raise_sinatra_param_exceptions
set :github_options, {
:secret => ENV['GITHUB_SECRET'],
:client_id => ENV['GITHUB_CLIENT_ID'],
:callback_url => '/authorize'
}
register Sinatra::Auth::Github
helpers Sinatra::Param
use Rack::Parser, :parsers => {
'text/xml' => proc { |data| Nori::Parser::Nokogiri.parse(data, {
:strip_namespaces => false,
:delete_namespace_attributes => false,
:convert_tags_to => nil,
:convert_attributes_to => nil,
:advanced_typecasting => true,
:convert_dashes_to_underscores => true
})
},
}
helpers do
def pull_request_opened?(payload)
payload.has_key?('pull_request') && payload['action'] == 'opened'
end
def pull_request_commented?(payload)
payload.has_key?('comment') &&
payload.has_key?('issue') &&
payload['issue'].has_key?('pull_request') &&
payload['action'] == 'created'
end
def enqueue_command(command, data)
CLA.queue.publish(command, JSON.dump(data))
end
def envelope_status_update?(params)
params.has_key?('DocuSignEnvelopeInformation') &&
params['DocuSignEnvelopeInformation'].has_key?('EnvelopeStatus') &&
params['DocuSignEnvelopeInformation']['EnvelopeStatus'].has_key?('EnvelopeID') &&
params['DocuSignEnvelopeInformation']['EnvelopeStatus'].has_key?('RecipientStatuses') &&
params['DocuSignEnvelopeInformation']['EnvelopeStatus']['RecipientStatuses'].has_key?('RecipientStatus')
end
def redirect(uri, *args)
if args.last.is_a?(::Hash)
args.pop.each do |n, v|
flash[n] = v
end
end
super(uri, *args)
end
def label_color(status)
case status
when 'Sent'
'warning'
when 'Delivered'
'info'
when 'Completed'
'success'
when 'Declined'
'danger'
else
'default'
end
end
def agreement_name
ENV['AGREEMENT_NAME'] || 'Contribution License Agreement'
end
end
not_found do
redirect to('/')
end
error Sinatra::Param::InvalidParameterError do |e|
CLA.logger.info("Invalid parameter #{e.param} - #{e.message}")
redirect to('/form'), param: e.param, message: e.message
end
error do |e|
CLA.logger.error("#{e.class.name}: #{e.message}\n " + e.backtrace.join("\n "))
status 500
content_type 'text/plain'
body "#{e.class.name}: #{e.message}"
end
# Almost RFC2822 (No attribution reference available).
#
# This differs in that it does not allow local domains (test@localhost).
# 99% of the time you do not want to allow these email addresses
# in a public web application.
EmailAddress = begin
if (RUBY_VERSION == '1.9.2' && RUBY_ENGINE == 'jruby' && JRUBY_VERSION <= '1.6.3') || RUBY_VERSION == '1.9.3'
# There is an obscure bug in jruby 1.6 that prevents matching
# on unicode properties here. Remove this logic branch once
# a stable jruby release fixes this.
#
# http://jira.codehaus.org/browse/JRUBY-5622
#
# There is a similar bug in preview releases of 1.9.3
#
# http://redmine.ruby-lang.org/issues/5126
letter = 'a-zA-Z'
else
letter = 'a-zA-Z\p{L}' # Changed from RFC2822 to include unicode chars
end
digit = '0-9'
atext = "[#{letter}#{digit}\!\#\$\%\&\'\*+\/\=\?\^\_\`\{\|\}\~\-]"
dot_atom_text = "#{atext}+([.]#{atext}*)+"
dot_atom = dot_atom_text
no_ws_ctl = '\x01-\x08\x11\x12\x14-\x1f\x7f'
qtext = "[^#{no_ws_ctl}\\x0d\\x22\\x5c]" # Non-whitespace, non-control character except for \ and "
text = '[\x01-\x09\x11\x12\x14-\x7f]'
quoted_pair = "(\\x5c#{text})"
qcontent = "(?:#{qtext}|#{quoted_pair})"
quoted_string = "[\"]#{qcontent}+[\"]"
atom = "#{atext}+"
word = "(?:#{atom}|#{quoted_string})"
obs_local_part = "#{word}([.]#{word})*"
local_part = "(?:#{dot_atom}|#{quoted_string}|#{obs_local_part})"
dtext = "[#{no_ws_ctl}\\x21-\\x5a\\x5e-\\x7e]"
dcontent = "(?:#{dtext}|#{quoted_pair})"
domain_literal = "\\[#{dcontent}+\\]"
obs_domain = "#{atom}([.]#{atom})+"
domain = "(?:#{dot_atom}|#{domain_literal}|#{obs_domain})"
addr_spec = "#{local_part}\@#{domain}"
pattern = /\A#{addr_spec}\z/u
end