/
omnigollum.rb
317 lines (266 loc) · 10.1 KB
/
omnigollum.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
require 'cgi'
require 'omniauth'
require 'mustache/sinatra'
require 'sinatra/base'
module Omnigollum
module Views; class Layout < Mustache; end; end
module Models
class OmniauthUserInitError < StandardError; end
class User
attr_reader :uid, :name, :email, :nickname, :provider
end
class OmniauthUser < User
def initialize (hash, options)
# Validity checks, don't trust providers
@uid = hash['uid'].to_s.strip
raise OmniauthUserInitError, "Insufficient data from authentication provider, uid not provided or empty" if @uid.empty?
@name = hash['info']['name'].to_s.strip if hash['info'].has_key?('name')
@name = options[:default_name] if !@name || @name.empty?
raise OmniauthUserInitError, "Insufficient data from authentication provider, name not provided or empty" if !@name || @name.empty?
@email = hash['info']['email'].to_s.strip if hash['info'].has_key?('email')
@email = options[:default_email] if !@email || @email.empty?
raise OmniauthUserInitError, "Insufficient data from authentication provider, email not provided or empty" if !@email || @email.empty?
@nickname = hash['info']['nickname'].to_s.strip if hash['info'].has_key?('nickname')
@provider = hash['provider']
self
end
end
end
module Helpers
def user_authed?
session.has_key? :omniauth_user
end
def user_auth
@title = 'Authentication is required'
@subtext = 'Please choose a login service'
show_login
end
def kick_back
redirect !request.referrer.nil? && request.referrer !~ /#{Regexp.escape(settings.send(:omnigollum)[:route_prefix])}\/.*/ ?
request.referrer:
'/'
halt
end
def get_user
session[:omniauth_user]
end
def user_deauth
session.delete :omniauth_user
end
def auth_config
options = settings.send(:omnigollum)
@auth = {
:route_prefix => options[:route_prefix],
:providers => options[:provider_names],
:path_images => options[:path_images],
:logo_suffix => options[:logo_suffix],
:logo_missing => options[:logo_missing]
}
end
def show_login
options = settings.send(:omnigollum)
# Don't bother showing the login screen, just redirect
if options[:provider_names].count == 1
if !request.params['origin'].nil?
origin = request.params['origin']
elsif !request.path.nil?
origin = request.path
else
origin = '/'
end
redirect (request.script_name || '') + options[:route_prefix] + '/auth/' + options[:provider_names].first.to_s + "?origin=" +
CGI.escape(origin)
else
auth_config
require options[:path_views] + '/login'
halt mustache Omnigollum::Views::Login
end
end
def show_error
options = settings.send(:omnigollum)
auth_config
require options[:path_views] + '/error'
halt mustache Omnigollum::Views::Error
end
def commit_message
if user_authed?
user = get_user
return { :message => params[:message], :name => user.name, :email => user.email}
else
return { :message => params[:message]}
end
end
end
# Config class provides default values for omnigollum configuration, and an array
# of all providers which have been enabled if a omniauth config block is passed to
# eval_omniauth_config.
class Config
attr_accessor :default_options
class << self; attr_accessor :default_options; end
@default_options = {
:protected_routes => [
'/revert/*',
'/revert',
'/create/*',
'/create',
'/edit/*',
'/edit',
'/rename/*',
'/rename/',
'/upload/*',
'/upload/',
'/delete/*',
'/delete'],
:route_prefix => '/__omnigollum__',
:dummy_auth => true,
:providers => Proc.new { provider :github, '', '' },
:path_base => dir = File.expand_path(File.dirname(__FILE__) + '/..'),
:logo_suffix => "_logo.png",
:logo_missing => "omniauth", # Set to false to disable missing logos
:path_images => "#{dir}/public/images",
:path_views => "#{dir}/views",
:path_templates => "#{dir}/templates",
:default_name => nil,
:default_email => nil,
:provider_names => [],
:authorized_users => [],
:author_format => Proc.new { |user| user.nickname ? user.name + ' (' + user.nickname + ')' : user.name },
:author_email => Proc.new { |user| user.email }
}
def initialize
@default_options = self.class.default_options
end
# Register provider name
#
# name - Provider symbol
# args - Arbitrary arguments
def provider(name, *args)
@default_options[:provider_names].push name
end
# Evaluate procedure calls in an omniauth config block/proc in the context
# of this class.
#
# This allows us to learn about omniauth config items that would otherwise be inaccessible.
#
# block - Omniauth proc or block
def eval_omniauth_config(&block)
self.instance_eval(&block)
end
# Catches missing methods we haven't implemented, but which omniauth accepts
# in its config block.
#
# args - Arbitrary list of arguments
def method_missing(*args); end
end
module Sinatra
def self.registered(app)
# As options determine which routes are created, they must be set before registering omniauth
config = Omnigollum::Config.new
options = app.settings.respond_to?(:omnigollum) ?
config.default_options.merge(app.settings.send(:omnigollum)) :
config.default_options
# Set omniauth path prefix based on options
OmniAuth.config.path_prefix = options[:route_prefix] + OmniAuth.config.path_prefix
# Setup test_mode options
if options[:dummy_auth]
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:default] = {
'uid' => '12345',
"info" => {
"email" => "user@example.com",
"name" => "example user"
},
'provider' => 'local'
}
end
# Register helpers
app.helpers Helpers
# Enable sinatra session support
app.set :sessions, true
# Setup omniauth providers
if !options[:providers].nil?
app.use OmniAuth::Builder, &options[:providers]
# You told omniauth, now tell us!
config.eval_omniauth_config &options[:providers] if options[:provider_names].count == 0
end
# Populates instance variables used to display currently logged in user
app.before '/*' do
@user_authed = user_authed?
@user = get_user
end
# Stop browsers from screwing up our referrer information
# FIXME: This is hacky...
app.before '/favicon.ico' do
halt 403 unless user_authed?
end
# Explicit login (user followed login link) clears previous redirect info
app.before options[:route_prefix] + '/login' do
kick_back if user_authed?
@auth_params = "?origin=#{CGI.escape(request.referrer)}" unless request.referrer.nil?
user_auth
end
app.before options[:route_prefix] + '/logout' do
user_deauth
kick_back
end
app.before options[:route_prefix] + '/auth/failure' do
user_deauth
@title = 'Authentication failed'
@subtext = "Provider did not validate your credentials (#{params[:message]}) - please retry or choose another login service"
@auth_params = "?origin=#{CGI.escape(request.env['omniauth.origin'])}" unless request.env['omniauth.origin'].nil?
show_error
end
app.before options[:route_prefix] + '/auth/:name/callback' do
begin
if !request.env['omniauth.auth'].nil?
user = Omnigollum::Models::OmniauthUser.new(request.env['omniauth.auth'], options)
case (authorized_users = options[:authorized_users])
when Regexp
user_authorized = (user.email =~ authorized_users)
when Array
user_authorized = authorized_users.include?(user.email) || authorized_users.include?(user.nickname)
else
user_authorized = true
end
# Check authorized users
if !user_authorized
@title = 'Authorization failed'
@subtext = 'User was not found in the authorized users list'
@auth_params = "?origin=#{CGI.escape(request.env['omniauth.origin'])}" unless request.env['omniauth.origin'].nil?
show_error
end
session[:omniauth_user] = user
# Update gollum's author hash, so commits are recorded correctly
session['gollum.author'] = {
:name => options[:author_format].call(user),
:email => options[:author_email].call(user)
}
redirect request.env['omniauth.origin']
elsif !user_authed?
@title = 'Authentication failed'
@subtext = 'Omniauth experienced an error processing your request'
@auth_params = "?origin=#{CGI.escape(request.env['omniauth.origin'])}" unless request.env['omniauth.origin'].nil?
show_error
end
rescue StandardError => fail_reason
@title = 'Authentication failed'
@subtext = fail_reason
@auth_params = "?origin=#{CGI.escape(request.env['omniauth.origin'])}" unless request.env['omniauth.origin'].nil?
show_error
end
end
app.before options[:route_prefix] + '/images/:image.png' do
content_type :png
send_file options[:path_images] + '/' + params[:image] + '.png'
end
# Stop sinatra processing and hand off to omniauth
app.before options[:route_prefix] + '/auth/:provider' do
halt 404
end
# Pre-empt protected routes
options[:protected_routes].each {|route| app.before(route) {user_auth unless user_authed?}}
# Write the actual config back to the app instance
app.set(:omnigollum, options)
end
end
end