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 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
- - [ ] Parse htpasswd files to support multiple credentials
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
@@ -0,0 +1,2 @@
1
+ desviar:desviar_user@instantlinux.net:8869405c6b1cec1825a65de76464e1e1
2
+ test:desviar_user@instantlinux.net:8f21c3b4d00eb51ac1e9ee010e5d610f
@@ -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", "adminpw", "cryptkey", "captchapriv" ],
70
+ :hashed => [ "authsalt", "cryptkey", "captchapriv" ],
74
71
 
75
72
  :msg_debug => "Debug mode",
76
73
  :debug => false
@@ -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
- "notes" => "example 3" }
54
+ :num_uses => 10,
55
+ :notes => "example 3" }
55
56
 
56
57
  puts "========= Desviar::Client list ============"
57
58
  pp obj.list
@@ -1,102 +1,53 @@
1
- require 'webrick/httpauth/htpasswd'
2
-
3
- module Desviar::Auth
4
-
5
- def self.htpasswd
6
- # @htpasswd ||= Htpasswd.new(git.path_to("htpasswd"))
7
- @htpasswd ||= Htpasswd.new('.htpasswd')
8
- end
9
-
10
- def self.authentication
11
- @authentication ||= Rack::Auth::Basic::Request.new request.env
12
- end
13
-
14
- def self.authenticated?
15
- request.env["REMOTE_USER"] && request.env["desviar.authenticated"]
16
- end
17
-
18
- def self.authenticate(username, password)
19
- checked = [ username, password ] == authentication.credentials
20
- validated = authentication.provided? && authentication.basic?
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
- # def self.unauthorized!(realm = Desviar::info)
31
- def self.unauthorized!(realm = 'desviar-realm')
32
- headers "WWW-Authenticate" => %(Basic realm="#{realm}")
33
- throw :halt, [ 401, "Authorization Required" ]
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
- def self.bad_request!
37
- throw :halt, [ 400, "Bad Request" ]
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 self.authenticate!
41
- return if authenticated?
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 self.access_granted?(username, password)
49
- authenticated? || authenticate(username, password)
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
@@ -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
- # Auth parsing is work-in-progress
37
- # require File.expand_path '../auth', __FILE__
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
- # create
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
- # submit
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 ID
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
- @records = Desviar::Model::Main.all(:expires_at.lt => DateTime.now,
136
- :fields => [ :id ])
137
- Desviar::Public::log "debug cleanup found #{@records.length}"
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 of most recent records
229
+ # list most recent records
147
230
  get '/list' do
148
- @desviar = Desviar::Model::Main.all(
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
- @desviar = Desviar::Model::Main.all(
247
+ query = {
160
248
  :limit => $config[:recordsmax],
161
249
  :order => [ :created_at.desc ],
162
- :fields => [ :id, :redir_uri, :temp_uri, :expiration, :captcha,
163
- :notes, :owner, :created_at, :expires_at ])
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
- erb :config
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
- json $config.reject { |opt, val|
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
- end
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
- # Class Desviar::Public - routes without auth
245
-
246
- class Public < Sinatra::Base
247
-
248
- configure do
249
- use Rack::Recaptcha, :public_key => $config[:captchapub], :private_key => $config[:captchapriv]
250
- helpers Rack::Recaptcha::Helpers
251
- end
252
-
253
- # display content
254
- get '/:temp_uri' do
255
- @desviar = Desviar::Model::Main.first(:temp_uri => params[:temp_uri])
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
- # handle reCAPTCHA
283
- post '/:temp_uri' do
284
- if recaptcha_valid?
285
- @desviar = Desviar::Model::Main.first(:temp_uri => params[:temp_uri],
286
- :fields => [ :id, :temp_uri, :captcha_validated ])
287
- @desviar.update(:captcha_validated => true)
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
 
@@ -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
@@ -1,7 +1,7 @@
1
1
  module Desviar
2
- VERSION = "0.0.16"
3
- RELEASE = "2013-08-03"
4
- TIMESTAMP = "2013-08-03 09:12:27 -07:00"
2
+ VERSION = "0.0.17"
3
+ RELEASE = "2013-08-04"
4
+ TIMESTAMP = "2013-08-04 21:02:33 -07:00"
5
5
 
6
6
  def self.info
7
7
  "#{name} v#{VERSION} (#{RELEASE})"
@@ -2,7 +2,11 @@
2
2
  <%= @desviar.captcha_prompt %>
3
3
  <hr>
4
4
  <form action="/desviar/<%= @desviar.temp_uri %>" method="POST">
5
- <%= recaptcha_tag(:challenge) %>
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>
@@ -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 &copy; 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) &&
@@ -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
@@ -1,11 +1,45 @@
1
1
  <div class="desviar">
2
2
  <h2>URI Listing (<%= @count %> of <%= @total %>)</h2>
3
3
  <table class="grid">
4
- <tr><th>id</th><th>created_at</th><th>expires_at</th><th>redir_uri</th><th>c</th><th>size</th><th>notes</th></tr>
5
- <% @desviar.each do |item| %>
6
- <%= "<tr><td><a href=/link/#{item.id}>#{item.id}</a></td><td>#{item.created_at}</td><td>#{item.expires_at}</td><td>#{item.redir_uri}</td><td>#{'&bull;' if item.captcha}</td><td>#{item.content.bytesize}</td><td>#{item.notes[0,50]}</td>" %>
7
- <% end %>
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>&bull;</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> <a href="/config">Config</a> <a href="/clean">Clean</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>
@@ -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 new temporary URI!</a>
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.16
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-03 00:00:00.000000000 Z
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.16\n\nTo configure, download from:\n https://raw.github.com/instantlinux/desviar/master/config/config.rb.example\ninto
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: []
@@ -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