desviar 0.0.16 → 0.0.17
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +5 -1
- data/config/.htdigest +2 -0
- data/config/config.rb.example +2 -5
- data/examples/ruby-client.rb +2 -1
- data/lib/auth.rb +42 -91
- data/lib/desviar.rb +151 -140
- data/lib/model.rb +1 -0
- data/lib/version.rb +3 -3
- data/lib/views/captcha.erb +5 -1
- data/lib/views/config.erb +1 -0
- data/lib/views/create.erb +1 -0
- data/lib/views/list.erb +39 -5
- data/lib/views/show.erb +2 -1
- metadata +4 -4
- data/lib/authorization.rb +0 -55
data/README.md
CHANGED
@@ -72,10 +72,14 @@ For scripting, the list, link and config commands can be modified with a _/json_
|
|
72
72
|
Security notes:
|
73
73
|
Consider moving the default database location from /dev/shm/desviar, and set its permissions to 0600. You can modify config.ru to direct log output to a different file.
|
74
74
|
|
75
|
+
User credentials:
|
76
|
+
A file called .htdigest is part of this package (see https://raw.github.com/instantlinux/desviar/master/config/.htdigest); you can customize the list using the _htdigest_ utility (get it from [htdigest-ruby](https://rubygems.org/gems/htdigest-ruby) and define environment variable DESVIAR_HTDIGEST with the pathname of your customized file. The configuration parameter _adminuser_ defines a "super-user" which can view/set configuration at run-time, and can access records other than its own via the list and link commands.
|
77
|
+
|
75
78
|
#### Features implemented ####
|
76
79
|
|
77
80
|
- [x] HTTP digest authentication for client API and user interface
|
78
|
-
- [
|
81
|
+
- [x] Multiple credentials, with designated _adminuser_
|
82
|
+
- [x] Parse htdigest (password) file
|
79
83
|
- [x] Bypass authentication for generated URIs
|
80
84
|
- [x] Basic HTTP authentication for remote URIs
|
81
85
|
- [ ] HTTP digest authentication for remote URIs
|
data/config/.htdigest
ADDED
data/config/config.rb.example
CHANGED
@@ -28,15 +28,12 @@ $config = {
|
|
28
28
|
:msg_redir_retain => "Retention policy for URI - discard before saving db entry to be more secure",
|
29
29
|
:redir_retain => "keep",
|
30
30
|
|
31
|
-
:msg_authprompt => "AuthPrompt appears in the browser authentication dialog",
|
31
|
+
:msg_authprompt => "AuthPrompt appears as the realm in the browser authentication dialog",
|
32
32
|
:authprompt => "Please Authenticate",
|
33
33
|
|
34
34
|
:msg_adminuser => "Administrative username - has access to all records",
|
35
35
|
:adminuser => "desviar",
|
36
36
|
|
37
|
-
:msg_adminpw => "Admin PW secures the UI",
|
38
|
-
:adminpw => "password",
|
39
|
-
|
40
37
|
:msg_authsalt => "AuthSalt is used to randomize http-digest hash",
|
41
38
|
:authsalt => "notveryrandom",
|
42
39
|
|
@@ -70,7 +67,7 @@ $config = {
|
|
70
67
|
:hidden => [ "hidden", "hashed" ],
|
71
68
|
|
72
69
|
# Hashed config items - secure passwords
|
73
|
-
:hashed => [ "authsalt", "
|
70
|
+
:hashed => [ "authsalt", "cryptkey", "captchapriv" ],
|
74
71
|
|
75
72
|
:msg_debug => "Debug mode",
|
76
73
|
:debug => false
|
data/examples/ruby-client.rb
CHANGED
@@ -51,7 +51,8 @@ puts obj.fetch link['temp_uri']
|
|
51
51
|
|
52
52
|
# Example 3: valid for 10 minutes
|
53
53
|
pp obj.create "https://rubygems.org/gems/desviar", 600, {
|
54
|
-
|
54
|
+
:num_uses => 10,
|
55
|
+
:notes => "example 3" }
|
55
56
|
|
56
57
|
puts "========= Desviar::Client list ============"
|
57
58
|
pp obj.list
|
data/lib/auth.rb
CHANGED
@@ -1,102 +1,53 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
granted = htpasswd.authenticated? username, password
|
22
|
-
if checked and validated and granted
|
23
|
-
request.env["desviar.authenticated"] = true
|
24
|
-
request.env["REMOTE_USER"] = authentication.username
|
25
|
-
else
|
26
|
-
nil
|
1
|
+
# Authorization module for Desviar - RFC 2317 htdigest support
|
2
|
+
#
|
3
|
+
# Copyright 2013 Richard Braun
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
module Desviar
|
12
|
+
class Auth
|
13
|
+
def initialize(htdigest_file, adminuser, realm, authsalt)
|
14
|
+
@users = Hash.new
|
15
|
+
File.open(htdigest_file) do |f|
|
16
|
+
f.each_line do |line|
|
17
|
+
if line.split(':')[1] == realm
|
18
|
+
@users[line.split(':')[0]] = line.split(':')[2].strip
|
19
|
+
end
|
20
|
+
end
|
27
21
|
end
|
22
|
+
@realm = realm
|
23
|
+
@adminuser = adminuser
|
24
|
+
@authsalt = authsalt
|
28
25
|
end
|
29
26
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
27
|
+
def authenticate!(app)
|
28
|
+
auth = Rack::Auth::Digest::MD5.new(app) do |username|
|
29
|
+
@users[username]
|
30
|
+
end
|
31
|
+
auth.realm = @realm
|
32
|
+
auth.opaque = @authsalt
|
33
|
+
auth.passwords_hashed = true
|
34
|
+
auth
|
34
35
|
end
|
35
36
|
|
36
|
-
|
37
|
-
|
37
|
+
=begin
|
38
|
+
# TODO: figure out how to access Rack::Request environment
|
39
|
+
def authorized?(owner)
|
40
|
+
request.env['REMOTE_USER'] == @adminuser ||
|
41
|
+
request.env['REMOTE_USER'] == owner
|
38
42
|
end
|
39
43
|
|
40
|
-
def
|
41
|
-
|
42
|
-
unauthorized! unless authentication.provided?
|
43
|
-
bad_request! unless authentication.basic?
|
44
|
-
unauthorized! unless authenticate(*authentication.credentials)
|
45
|
-
request.env["REMOTE_USER"] = authentication.username
|
44
|
+
def authsuper?
|
45
|
+
request.env['REMOTE_USER'] == @adminuser
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
49
|
-
|
48
|
+
def user_name
|
49
|
+
request.env['REMOTE_USER']
|
50
50
|
end
|
51
|
-
|
51
|
+
=end
|
52
|
+
end
|
52
53
|
end
|
53
|
-
|
54
|
-
class Desviar::Auth2
|
55
|
-
|
56
|
-
def initialize(file)
|
57
|
-
@handler = WEBrick::HTTPAuth::Htpasswd.new(file)
|
58
|
-
yield self if block_given?
|
59
|
-
end
|
60
|
-
|
61
|
-
def find(username)
|
62
|
-
password = @handler.get_passwd(nil, username, false)
|
63
|
-
if block_given?
|
64
|
-
yield password ? [password, password[0,2]] : [nil, nil]
|
65
|
-
else
|
66
|
-
password
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def authenticated?(username, password)
|
71
|
-
self.find username do |crypted, salt|
|
72
|
-
crypted && salt && crypted == password.crypt(salt)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def create(username, password)
|
77
|
-
@handler.set_passwd(nil, username, password)
|
78
|
-
end
|
79
|
-
alias update create
|
80
|
-
|
81
|
-
def destroy(username)
|
82
|
-
@handler.delete_passwd(nil, username)
|
83
|
-
end
|
84
|
-
|
85
|
-
def include?(username)
|
86
|
-
users.include? username
|
87
|
-
end
|
88
|
-
|
89
|
-
def size
|
90
|
-
users.size
|
91
|
-
end
|
92
|
-
|
93
|
-
def write!
|
94
|
-
@handler.flush
|
95
|
-
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
def users
|
100
|
-
@handler.each{|username, password| username }
|
101
|
-
end
|
102
|
-
end
|
data/lib/desviar.rb
CHANGED
@@ -20,7 +20,6 @@ require 'dm-timestamps'
|
|
20
20
|
require 'syntaxi'
|
21
21
|
require 'syslog'
|
22
22
|
require 'net/http'
|
23
|
-
#require 'test/unit'
|
24
23
|
require 'rack/test'
|
25
24
|
require 'rack/recaptcha'
|
26
25
|
require 'multi_json'
|
@@ -33,10 +32,76 @@ end
|
|
33
32
|
require File.expand_path '../version', __FILE__
|
34
33
|
require File.expand_path '../encrypt', __FILE__
|
35
34
|
require File.expand_path '../model', __FILE__
|
36
|
-
|
37
|
-
|
35
|
+
require File.expand_path '../auth', __FILE__
|
36
|
+
$digest_file = ENV['DESVIAR_HTDIGEST'] || (File.expand_path '../../config/.htdigest', __FILE__)
|
38
37
|
|
39
38
|
module Desviar
|
39
|
+
|
40
|
+
#############################################
|
41
|
+
# Class Desviar::Public - routes without auth
|
42
|
+
|
43
|
+
class Public < Sinatra::Base
|
44
|
+
|
45
|
+
configure do
|
46
|
+
use Rack::Recaptcha, :public_key => $config[:captchapub], :private_key => $config[:captchapriv]
|
47
|
+
helpers Rack::Recaptcha::Helpers
|
48
|
+
end
|
49
|
+
|
50
|
+
# display content
|
51
|
+
get '/:temp_uri' do
|
52
|
+
@desviar = Desviar::Model::Main.first(:temp_uri => params[:temp_uri])
|
53
|
+
cache_control :public, :max_age => 30
|
54
|
+
if @desviar && DateTime.now < @desviar[:expires_at] && @desviar[:num_uses] !=0
|
55
|
+
if @desviar[:captcha] && !@desviar[:captcha_validated]
|
56
|
+
@button = @desviar[:captcha_button]
|
57
|
+
erb :captcha
|
58
|
+
else
|
59
|
+
@desviar.update(:captcha_validated => false) if @desviar[:captcha_validated]
|
60
|
+
if !$config[:dbencrypt]
|
61
|
+
@content = @desviar[:content]
|
62
|
+
else
|
63
|
+
obj = Desviar::EncryptedItem::Decryptor::for({
|
64
|
+
'cipher' => $config[:dbencrypt],
|
65
|
+
'version' => 2,
|
66
|
+
'encrypted_data' => @desviar[:content],
|
67
|
+
'iv' => Base64.encode64(@desviar[:cipher_iv]),
|
68
|
+
'hmac' => @desviar[:hmac]}, $config[:cryptkey])
|
69
|
+
@content = obj.for_decrypted_item
|
70
|
+
end
|
71
|
+
if @desviar[:num_uses] > 0
|
72
|
+
@desviar.update(:num_uses => @desviar[:num_uses] - 1)
|
73
|
+
end
|
74
|
+
Desviar::Public::log "Fetched #{@desviar.id} #{@desviar.redir_uri} #{@desviar.content.bytesize} #{@desviar.notes[0,50]}"
|
75
|
+
erb :content, :layout => false
|
76
|
+
end
|
77
|
+
else
|
78
|
+
error 404
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# handle reCAPTCHA
|
83
|
+
post '/:temp_uri' do
|
84
|
+
if recaptcha_valid?
|
85
|
+
@desviar = Desviar::Model::Main.first(
|
86
|
+
:temp_uri => params[:temp_uri],
|
87
|
+
:fields => [ :id, :temp_uri, :captcha_validated ])
|
88
|
+
@desviar.update(:captcha_validated => true)
|
89
|
+
end
|
90
|
+
redirect "/desviar/#{params[:temp_uri]}"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Syslog utility
|
94
|
+
def self.log(message, priority = Syslog::LOG_INFO)
|
95
|
+
if $config[:log_facility]
|
96
|
+
Syslog.open($0, Syslog::LOG_PID | Syslog::LOG_CONS | $config[:log_facility]) { |obj| obj.info message }
|
97
|
+
end
|
98
|
+
puts "#{Time.now} #{message}" if $config[:debug]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
#############################################
|
103
|
+
# Class Desviar::Authorized - routes (commands) which require authorization
|
104
|
+
|
40
105
|
class Authorized < Sinatra::Base
|
41
106
|
|
42
107
|
configure do
|
@@ -45,19 +110,21 @@ module Desviar
|
|
45
110
|
DataMapper.auto_upgrade! if DataMapper.respond_to?(:auto_upgrade!)
|
46
111
|
$config[:cryptkey] = SecureRandom.base64(32) if $config[:cryptkey].nil?
|
47
112
|
helpers Sinatra::JSON
|
113
|
+
@auth = Desviar::Auth.new($digest_file, $config[:adminuser], $config[:authprompt], $config[:authsalt])
|
114
|
+
Desviar::Public::log "Starting #{Desviar.info}"
|
48
115
|
end
|
49
116
|
|
50
117
|
get '/' do
|
51
118
|
redirect '/create'
|
52
119
|
end
|
53
120
|
|
54
|
-
#
|
121
|
+
# form: new temporary URI
|
55
122
|
get '/create' do
|
56
123
|
puts request.env.inspect if $config[:debug]
|
57
124
|
erb :create
|
58
125
|
end
|
59
126
|
|
60
|
-
#
|
127
|
+
# create new temporary URI
|
61
128
|
post '/create' do
|
62
129
|
error 400 if params[:redir_uri].strip == ""
|
63
130
|
|
@@ -66,7 +133,8 @@ module Desviar
|
|
66
133
|
@desviar = Desviar::Model::Main.new(params.merge({
|
67
134
|
:temp_uri => "#{$config[:uriprefix]}#{SecureRandom.urlsafe_base64($config[:hashlength])[0,$config[:hashlength]]}#{$config[:urisuffix]}",
|
68
135
|
:expires_at => Time.now + params[:expiration].to_i,
|
69
|
-
:captcha_validated => false
|
136
|
+
:captcha_validated => false,
|
137
|
+
:owner => request.env['REMOTE_USER']
|
70
138
|
}).delete_if {|key, val| key == "redir_uri" || key == "remoteuser" || key == "remotepw"})
|
71
139
|
|
72
140
|
# Cache the remote URI
|
@@ -96,21 +164,29 @@ module Desviar
|
|
96
164
|
@desviar[:cipher_iv] = obj.iv
|
97
165
|
end
|
98
166
|
|
167
|
+
# Apply field rules:
|
168
|
+
# - Discard redir_uri from data record if redir_retain != 'keep'
|
169
|
+
# - Set num_uses to -1 (unlimited) if not set
|
99
170
|
@desviar[:redir_uri] = $config[:redir_retain] == "keep" ? params[:redir_uri] : ""
|
171
|
+
if @params[:num_uses].to_i <= 0 || @params[:num_uses].empty?
|
172
|
+
@desviar[:num_uses] = -1
|
173
|
+
end
|
100
174
|
|
101
175
|
# Insert the new record and display the new link
|
102
176
|
if @desviar.save
|
103
|
-
Desviar::Public::log "Created #{@desviar.id} #{@desviar.redir_uri} #{@desviar.expires_at} #{request.ip}"
|
177
|
+
Desviar::Public::log "Created #{@desviar.id} #{@desviar.redir_uri} #{@desviar.expires_at} #{@desviar.num_uses} #{request.ip} #{request.env['REMOTE_USER']}"
|
104
178
|
redirect "/link/#{@desviar.id}"
|
105
179
|
else
|
106
180
|
error 400
|
107
181
|
end
|
108
182
|
end
|
109
183
|
|
110
|
-
# show link
|
184
|
+
# show link metadata info
|
111
185
|
get '/link/:id' do
|
112
186
|
@desviar = Desviar::Model::Main.get(params[:id])
|
113
|
-
if @desviar && DateTime.now < @desviar[:expires_at]
|
187
|
+
if @desviar && DateTime.now < @desviar[:expires_at] &&
|
188
|
+
(request.env['REMOTE_USER'] == $config[:adminuser] ||
|
189
|
+
request.env['REMOTE_USER'] == @desviar['owner'])
|
114
190
|
erb :show
|
115
191
|
else
|
116
192
|
error 404
|
@@ -120,7 +196,9 @@ module Desviar
|
|
120
196
|
# show link info - json format
|
121
197
|
get '/link/json/:id' do
|
122
198
|
@desviar = Desviar::Model::Main.get(params[:id])
|
123
|
-
if @desviar && DateTime.now < @desviar[:expires_at]
|
199
|
+
if @desviar && DateTime.now < @desviar[:expires_at] &&
|
200
|
+
(request.env['REMOTE_USER'] == $config[:adminuser] ||
|
201
|
+
request.env['REMOTE_USER'] == @desviar['owner'])
|
124
202
|
json @desviar.attributes.delete_if {|key, val| key == :content || key == :cipher_iv || key == :hmac}
|
125
203
|
else
|
126
204
|
error 404
|
@@ -132,23 +210,33 @@ module Desviar
|
|
132
210
|
# TODO: figure out the clean "native" way of DataMapper::Collection.destroy
|
133
211
|
# - but this works fine for small databases
|
134
212
|
@desviar = DataMapper.repository(:default).adapter
|
135
|
-
|
136
|
-
|
137
|
-
|
213
|
+
query = {
|
214
|
+
:expires_at.lt => DateTime.now,
|
215
|
+
:fields => [ :id ]
|
216
|
+
}
|
217
|
+
if request.env['REMOTE_USER'] != $config[:adminuser]
|
218
|
+
query[:owner] = request.env['REMOTE_USER']
|
219
|
+
end
|
220
|
+
@records = Desviar::Model::Main.all(query)
|
138
221
|
count = @records.length
|
139
222
|
@records.each do |item|
|
140
223
|
@desviar.execute("DELETE FROM desviar_model_mains WHERE id=#{item.id};")
|
141
224
|
end
|
142
|
-
Desviar::Public::log "Cleaned #{count} records" if count != 0
|
225
|
+
Desviar::Public::log "Cleaned #{count} records by #{request.env['REMOTE_USER']}" if count != 0
|
143
226
|
redirect "/list"
|
144
227
|
end
|
145
228
|
|
146
|
-
# list
|
229
|
+
# list most recent records
|
147
230
|
get '/list' do
|
148
|
-
|
231
|
+
query = {
|
149
232
|
:limit => $config[:recordsmax],
|
150
233
|
:order => [ :created_at.desc ],
|
151
|
-
:fields => [ :id, :created_at, :expires_at, :redir_uri, :captcha, :notes ]
|
234
|
+
:fields => [ :id, :created_at, :expires_at, :redir_uri, :captcha, :notes ]
|
235
|
+
}
|
236
|
+
if request.env['REMOTE_USER'] != $config[:adminuser]
|
237
|
+
query[:owner] = request.env['REMOTE_USER']
|
238
|
+
end
|
239
|
+
@desviar = Desviar::Model::Main.all(query)
|
152
240
|
@total = @desviar.length
|
153
241
|
@count = [ @total, $config[:recordsmax] ].min
|
154
242
|
erb :list
|
@@ -156,11 +244,15 @@ module Desviar
|
|
156
244
|
|
157
245
|
# list - json
|
158
246
|
get '/list/json' do
|
159
|
-
|
247
|
+
query = {
|
160
248
|
:limit => $config[:recordsmax],
|
161
249
|
:order => [ :created_at.desc ],
|
162
|
-
:fields => [ :id, :
|
163
|
-
|
250
|
+
:fields => [ :id, :created_at, :expires_at, :redir_uri, :captcha, :notes ]
|
251
|
+
}
|
252
|
+
if request.env['REMOTE_USER'] != $config[:adminuser]
|
253
|
+
query[:owner] = request.env['REMOTE_USER']
|
254
|
+
end
|
255
|
+
@desviar = Desviar::Model::Main.all(query)
|
164
256
|
list = Array.new
|
165
257
|
@desviar.each do |item|
|
166
258
|
list << {
|
@@ -173,144 +265,63 @@ module Desviar
|
|
173
265
|
json list
|
174
266
|
end
|
175
267
|
|
176
|
-
# configuration
|
268
|
+
# form - configuration
|
177
269
|
get '/config' do
|
178
|
-
|
270
|
+
@ver = Desviar.info
|
271
|
+
if request.env['REMOTE_USER'] == $config[:adminuser]
|
272
|
+
erb :config
|
273
|
+
else
|
274
|
+
error 404
|
275
|
+
end
|
179
276
|
end
|
180
277
|
|
181
|
-
# configuration - json
|
278
|
+
# query configuration - json
|
182
279
|
get '/config/json' do
|
183
|
-
|
280
|
+
if request.env['REMOTE_USER'] == $config[:adminuser]
|
281
|
+
json $config.reject { |opt, val|
|
184
282
|
opt.to_s.index('msg_') == 0 ||
|
185
283
|
$config[:hidden].include?(opt.to_s) ||
|
186
284
|
$config[:hashed].include?(opt.to_s)
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
# submit
|
191
|
-
post '/config' do
|
192
|
-
params['config'].each do |opt, val|
|
193
|
-
if $config[opt.to_sym].class == Fixnum
|
194
|
-
$config[opt.to_sym] = val.to_i
|
195
|
-
elsif val != "" || !$config[:hashed].include?(opt)
|
196
|
-
$config[opt.to_sym] = case val
|
197
|
-
when "true" then true
|
198
|
-
when "false" then false
|
199
|
-
when "nil" then nil
|
200
|
-
else val
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
DataMapper::Logger.new($stdout, :debug) if $config[:debug]
|
206
|
-
$config[:cryptkey] = SecureRandom.base64(32) if $config[:cryptkey].nil?
|
207
|
-
|
208
|
-
puts $config.inspect if $config[:debug]
|
209
|
-
Desviar::Public::log "Configuration updated"
|
210
|
-
redirect "/list"
|
211
|
-
end
|
212
|
-
|
213
|
-
def self.new(*)
|
214
|
-
# TODO: htpasswd parsing
|
215
|
-
# Desviar::Auth::authenticate!
|
216
|
-
# @auth.call()
|
217
|
-
app = Rack::Auth::Digest::MD5.new(super) do |username|
|
218
|
-
{$config[:adminuser] => $config[:adminpw]}[username]
|
219
|
-
end
|
220
|
-
app.realm = $config[:authprompt]
|
221
|
-
app.opaque = $config[:authsalt]
|
222
|
-
app
|
223
|
-
end
|
224
|
-
|
225
|
-
=begin
|
226
|
-
class Mytest
|
227
|
-
def initialize(app)
|
228
|
-
# Desviar::Public::log "Initializing auth from .htpasswd"
|
229
|
-
@obj = Rack::Auth::Basic.new(app) do |username, password|
|
230
|
-
unless File.exist?('../config/.htpasswd')
|
231
|
-
raise PasswordFileNotFound.new("#{file} is not found. Please create it with htpasswd")
|
232
|
-
end
|
233
|
-
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(file)
|
234
|
-
crypted = htpasswd.get_passwd(nil, user, false)
|
235
|
-
crypted == pass.crypt(crypted) if crypted
|
285
|
+
}
|
286
|
+
else
|
287
|
+
error 404
|
236
288
|
end
|
237
289
|
end
|
238
|
-
end
|
239
|
-
=end
|
240
|
-
|
241
|
-
end
|
242
290
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
cache_control :public, :max_age => 30
|
257
|
-
if @desviar && DateTime.now < @desviar[:expires_at]
|
258
|
-
if @desviar[:captcha] && !@desviar[:captcha_validated]
|
259
|
-
@button = @desviar[:captcha_button]
|
260
|
-
erb :captcha
|
261
|
-
else
|
262
|
-
@desviar.update(:captcha_validated => false) if @desviar[:captcha_validated]
|
263
|
-
if !$config[:dbencrypt]
|
264
|
-
@content = @desviar[:content]
|
265
|
-
else
|
266
|
-
obj = Desviar::EncryptedItem::Decryptor::for({
|
267
|
-
'cipher' => $config[:dbencrypt],
|
268
|
-
'version' => 2,
|
269
|
-
'encrypted_data' => @desviar[:content],
|
270
|
-
'iv' => Base64.encode64(@desviar[:cipher_iv]),
|
271
|
-
'hmac' => @desviar[:hmac]}, $config[:cryptkey])
|
272
|
-
@content = obj.for_decrypted_item
|
291
|
+
# config update
|
292
|
+
post '/config' do
|
293
|
+
if request.env['REMOTE_USER'] == $config[:adminuser]
|
294
|
+
params['config'].each do |opt, val|
|
295
|
+
if $config[opt.to_sym].class == Fixnum
|
296
|
+
$config[opt.to_sym] = val.to_i
|
297
|
+
elsif val != "" || !$config[:hashed].include?(opt)
|
298
|
+
$config[opt.to_sym] = case val
|
299
|
+
when "true" then true
|
300
|
+
when "false" then false
|
301
|
+
when "nil" then nil
|
302
|
+
else val
|
303
|
+
end
|
273
304
|
end
|
274
|
-
Desviar::Public::log "Fetched #{@desviar.id} #{@desviar.redir_uri} #{@desviar.content.bytesize} #{@desviar.notes[0,50]}"
|
275
|
-
erb :content, :layout => false
|
276
305
|
end
|
306
|
+
|
307
|
+
DataMapper::Logger.new($stdout, :debug) if $config[:debug]
|
308
|
+
$config[:cryptkey] = SecureRandom.base64(32) if $config[:cryptkey].nil?
|
309
|
+
|
310
|
+
puts $config.inspect if $config[:debug]
|
311
|
+
Desviar::Public::log "Configuration updated by #{request.env['REMOTE_USER']}"
|
312
|
+
redirect "/list"
|
277
313
|
else
|
278
314
|
error 404
|
279
315
|
end
|
280
316
|
end
|
281
317
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
end
|
289
|
-
redirect "/desviar/#{params[:temp_uri]}"
|
290
|
-
end
|
291
|
-
|
292
|
-
def self.log(message, priority = Syslog::LOG_INFO)
|
293
|
-
if $config[:log_facility]
|
294
|
-
Syslog.open($0, Syslog::LOG_PID | Syslog::LOG_CONS | $config[:log_facility]) { |obj| obj.info message }
|
295
|
-
end
|
296
|
-
puts "#{Time.now} #{message}" if $config[:debug]
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
=begin
|
301
|
-
# TODO - switch to MiniTest, move to test subdir
|
302
|
-
class Test <Test::Unit::TestCase
|
303
|
-
include Rack::Test::Methods
|
304
|
-
|
305
|
-
def app
|
306
|
-
Desviar
|
307
|
-
end
|
308
|
-
|
309
|
-
def test_list
|
310
|
-
get '/list'
|
311
|
-
assert last_response.ok?
|
318
|
+
def self.new(*)
|
319
|
+
app = @auth.authenticate!(super)
|
320
|
+
# TODO - scope of request.env is needed
|
321
|
+
# user = request.env['REMOTE_USER']
|
322
|
+
Desviar::Public::log "Authenticated new user session"
|
323
|
+
app
|
312
324
|
end
|
313
325
|
end
|
314
|
-
=end
|
315
326
|
end
|
316
327
|
|
data/lib/model.rb
CHANGED
@@ -29,6 +29,7 @@ module Desviar
|
|
29
29
|
property :cipher_iv, Binary, :length => 16
|
30
30
|
property :hmac, String, :length => 46
|
31
31
|
property :owner, String, :length => 16
|
32
|
+
property :num_uses, Integer
|
32
33
|
property :created_at, DateTime
|
33
34
|
property :updated_at, DateTime
|
34
35
|
property :expires_at, DateTime
|
data/lib/version.rb
CHANGED
data/lib/views/captcha.erb
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
<%= @desviar.captcha_prompt %>
|
3
3
|
<hr>
|
4
4
|
<form action="/desviar/<%= @desviar.temp_uri %>" method="POST">
|
5
|
-
<%=
|
5
|
+
<%= if $config[:captchapub].empty?
|
6
|
+
"<b>Configuration error</b>: CAPTCHA required but no key is set -- get one at <a href='http://www.google.com/recaptcha/whyrecaptcha'>Google signup</a>"
|
7
|
+
else
|
8
|
+
recaptcha_tag(:challenge)
|
9
|
+
end %>
|
6
10
|
<input type="submit" value="<%= @button %>"/>
|
7
11
|
</form>
|
8
12
|
</div>
|
data/lib/views/config.erb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
<h2>Run-time configuration</h2>
|
3
3
|
<hr>
|
4
4
|
<form action="/config" method="POST">
|
5
|
+
<font size=-1 color='DarkSlateGray'><%= @ver %> Copyright © 2013 Rich Braun <a href='http://www.apache.org/licenses/LICENSE-2.0'>Apache 2.0</a></font>
|
5
6
|
<p>
|
6
7
|
<%=
|
7
8
|
values = $config.select { |key, val| !$config[:hidden].include?(key.to_s) &&
|
data/lib/views/create.erb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
<form action="/create" method="POST">
|
5
5
|
<p><label for="uri">Existing URI: </label><input type="text" name="redir_uri" /><br>
|
6
6
|
<label for="expire">Seconds before Expiration: </label><input type="text" name="expiration" value="<%= $config[:exp] %>"/><p>
|
7
|
+
<label for="num_uses">Number of allowed uses: </label><input type="text" name="num_uses" /><p>
|
7
8
|
<label for="user">Username (if needed): </label><input type="text" name="remoteuser" /><p>
|
8
9
|
<label for="pass">Password (if needed): </label><input type="password" name="remotepw" /><p>
|
9
10
|
<label for="captcha">Require CAPTCHA: </label><input type="radio" name="captcha" value="0" checked/>No
|
data/lib/views/list.erb
CHANGED
@@ -1,11 +1,45 @@
|
|
1
1
|
<div class="desviar">
|
2
2
|
<h2>URI Listing (<%= @count %> of <%= @total %>)</h2>
|
3
3
|
<table class="grid">
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
<%=
|
5
|
+
cols = [ 'id', 'created_at', 'expires_at', 'num_uses', 'redir_uri', 'captcha', 'size', 'owner', 'notes' ]
|
6
|
+
html = ""
|
7
|
+
html << "<tr>"
|
8
|
+
cols.each do |col| html << "<th>#{col}</th>" end
|
9
|
+
html << "</tr>"
|
10
|
+
@desviar.each do |item|
|
11
|
+
html << "<tr>"
|
12
|
+
cols.each do |col|
|
13
|
+
html << "<td>"
|
14
|
+
case col
|
15
|
+
when 'id'
|
16
|
+
if item['expires_at'] < DateTime.now || item['num_uses'] == 0
|
17
|
+
html << item[col].to_s
|
18
|
+
else
|
19
|
+
html << "<a href=/link/#{item[col]}>#{item[col].to_s}</a>"
|
20
|
+
end
|
21
|
+
when 'expires_at'
|
22
|
+
if item['expires_at'] < DateTime.now || item['num_uses'] == 0
|
23
|
+
html << "<del>#{item[col].to_s}</del>"
|
24
|
+
else
|
25
|
+
html << item[col].to_s
|
26
|
+
end
|
27
|
+
when 'captcha'
|
28
|
+
html << (item[col] ? "<center>•</center>" : "")
|
29
|
+
when 'size'
|
30
|
+
html << item['content'].bytesize.to_s
|
31
|
+
else
|
32
|
+
html << item[col].to_s[0..50]
|
33
|
+
end
|
34
|
+
html << "</td>"
|
35
|
+
end
|
36
|
+
html << "</tr>"
|
37
|
+
end
|
38
|
+
html
|
39
|
+
%>
|
8
40
|
</table>
|
9
|
-
<p><a href="/create">Create</a>
|
41
|
+
<p><a href="/create">Create</a>
|
42
|
+
<% if request.env['REMOTE_USER'] == $config[:adminuser] %><a href="/config">Config</a> <% end %>
|
43
|
+
<a href="/clean">Clean</a>
|
10
44
|
<hr>
|
11
45
|
</div>
|
data/lib/views/show.erb
CHANGED
@@ -9,8 +9,9 @@
|
|
9
9
|
Created <%= @desviar.created_at.strftime("%D at %T %Z") %><br>
|
10
10
|
Expires <%= @desviar.expires_at.strftime("%D at %T %Z") %><br>
|
11
11
|
ID: <%= @desviar.id %>
|
12
|
+
<%= if @desviar.num_uses > 0 then "<br>Uses: #{@desviar.num_uses}" end %>
|
12
13
|
<%= @desviar.captcha ? "<br>CAPTCHA is required" : "" %>
|
13
14
|
</div>
|
14
15
|
<hr>
|
15
|
-
<a href="/create">Create
|
16
|
+
<a href="/create">Create Another</a> <a href="/list">List</a>
|
16
17
|
</div>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: desviar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.17
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dm-core
|
@@ -236,12 +236,12 @@ files:
|
|
236
236
|
- Apache-License.txt
|
237
237
|
- README.md
|
238
238
|
- config.ru
|
239
|
+
- config/.htdigest
|
239
240
|
- config/config.rb.example
|
240
241
|
- desviar.gemspec
|
241
242
|
- examples/bash-client.sh
|
242
243
|
- examples/ruby-client.rb
|
243
244
|
- lib/auth.rb
|
244
|
-
- lib/authorization.rb
|
245
245
|
- lib/desviar.rb
|
246
246
|
- lib/desviar/client.rb
|
247
247
|
- lib/encrypt.rb
|
@@ -258,7 +258,7 @@ files:
|
|
258
258
|
homepage: http://github.com/instantlinux/desviar
|
259
259
|
licenses: []
|
260
260
|
post_install_message: ! "------------------------------------------------------------------------------\nDesviar
|
261
|
-
v0.0.
|
261
|
+
v0.0.17\n\nTo configure, download from:\n https://raw.github.com/instantlinux/desviar/master/config/config.rb.example\ninto
|
262
262
|
a new file config.rb and export DESVIAR_CONFIG=<path>/config.rb.\n\nThanks for using
|
263
263
|
Desviar.\n------------------------------------------------------------------------------\n"
|
264
264
|
rdoc_options: []
|
data/lib/authorization.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
require 'htauth'
|
2
|
-
|
3
|
-
module Sinatra
|
4
|
-
module Authorization
|
5
|
-
module HelperMethods
|
6
|
-
|
7
|
-
def passwd_file
|
8
|
-
File.expand_path '../config/.htpasswd', __FILE__
|
9
|
-
end
|
10
|
-
|
11
|
-
def auth
|
12
|
-
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
13
|
-
# @auth ||= Rack::Auth::Digest::MD5.new(request.env)
|
14
|
-
end
|
15
|
-
|
16
|
-
def unauthorized!(realm = "Please Authenticate")
|
17
|
-
header 'WWW-Authenticate' => %(Basic realm="#{realm}")
|
18
|
-
throw :halt, [ 401, 'Authorization Required' ]
|
19
|
-
end
|
20
|
-
|
21
|
-
def bad_request!
|
22
|
-
throw :halt, [ 400, 'Bad Request' ]
|
23
|
-
end
|
24
|
-
|
25
|
-
def authorized?
|
26
|
-
request.env['REMOTE_USER']
|
27
|
-
end
|
28
|
-
|
29
|
-
def authorize(username, password)
|
30
|
-
return false if !File.exists?(passwd_file)
|
31
|
-
pf = HTAuth::PasswdFile.new(passwd_file)
|
32
|
-
user = pf.fetch(username)
|
33
|
-
!user.nil? && user.authenticated?(password)
|
34
|
-
end
|
35
|
-
|
36
|
-
def require_administrative_privileges
|
37
|
-
return if authorized?
|
38
|
-
unauthorized! unless auth.provided?
|
39
|
-
bad_request! unless auth.basic?
|
40
|
-
unauthorized! unless authorize(*auth.credentials)
|
41
|
-
request.env['REMOTE_USER'] = auth.username
|
42
|
-
end
|
43
|
-
|
44
|
-
def admin?
|
45
|
-
authorized?
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
|
-
end
|
50
|
-
def self.registered(app)
|
51
|
-
app.helpers HelperMethods
|
52
|
-
end
|
53
|
-
end
|
54
|
-
register Authorization
|
55
|
-
end
|