desviar 0.0.16 → 0.0.17
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.
- 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
|