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 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