aws-auth 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +1 -0
- data/Rakefile +8 -0
- data/aws-auth.yml +4 -0
- data/db/aws-users.db +0 -0
- data/db/migrate/002_create_users.rb +24 -0
- data/db/test.db +0 -0
- data/lib/aws-auth.rb +126 -0
- data/lib/aws-auth/admin.rb +270 -0
- data/lib/aws-auth/tasks.rb +28 -0
- data/lib/aws-auth/user.rb +35 -0
- data/public/css/control.css +232 -0
- data/public/images/external-link.gif +0 -0
- data/public/js/prototype.js +2539 -0
- data/public/js/upload_status.js +117 -0
- metadata +109 -0
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Rack middleware that provides AWS (Amazon Web Services) style authentication.
|
data/Rakefile
ADDED
data/aws-auth.yml
ADDED
data/db/aws-users.db
ADDED
Binary file
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class CreateUsers < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
create_table :users do |t|
|
5
|
+
t.column :id, :integer, :null => false
|
6
|
+
t.column :login, :string, :limit => 40
|
7
|
+
t.column :password, :string, :limit => 40
|
8
|
+
t.column :email, :string, :limit => 64
|
9
|
+
t.column :key, :string, :limit => 64
|
10
|
+
t.column :secret, :string, :limit => 64
|
11
|
+
t.column :created_at, :datetime
|
12
|
+
t.column :updated_at, :timestamp
|
13
|
+
t.column :activated_at, :datetime
|
14
|
+
t.column :superuser, :integer, :default => 0
|
15
|
+
t.column :deleted, :integer, :default => 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.down
|
20
|
+
drop_table :users
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
data/db/test.db
ADDED
Binary file
|
data/lib/aws-auth.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
require 'active_record'
|
4
|
+
require 'base64'
|
5
|
+
require 'digest/sha1'
|
6
|
+
require 'openssl'
|
7
|
+
require 'sinatra'
|
8
|
+
|
9
|
+
module AWSAuth
|
10
|
+
class Base
|
11
|
+
|
12
|
+
VERSION = "0.9.0"
|
13
|
+
ROOT_DIR = ENV['AWS_ROOT_DIR'] || File.join(File.dirname(__FILE__),'..')
|
14
|
+
DEFAULT_PASSWORD = "testp@ss"
|
15
|
+
|
16
|
+
def self.config
|
17
|
+
@@config ||= load_config()
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.config_path=(val)
|
21
|
+
@@config_path = val
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(app)
|
25
|
+
@app = app
|
26
|
+
AWSAuth::User.establish_connection(AWSAuth::Base.config[:auth])
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
date_s = env['HTTP_X_AMZ_DATE'] || env['HTTP_DATE']
|
31
|
+
request = Rack::Request.new(env)
|
32
|
+
auth = Rack::Auth::Basic::Request.new(env)
|
33
|
+
|
34
|
+
# Basic Auth
|
35
|
+
if auth.provided? && auth.basic?
|
36
|
+
user = AWSAuth::User.find_by_login(auth.credentials[0])
|
37
|
+
unless user.nil?
|
38
|
+
if user.password == AWSAuth::Base.hmac_sha1( auth.credentials[1], user.secret )
|
39
|
+
env['AWS_AUTH_USER'] = user
|
40
|
+
end
|
41
|
+
end
|
42
|
+
# Route 53
|
43
|
+
elsif env['HTTP_X_AMZN_AUTHORIZATION'] =~ /^AWS3-HTTPS AWSAccessKeyId=(.*),Algorithm=HmacSHA256,Signature=(.*)$/
|
44
|
+
access_key = $1
|
45
|
+
signature = $2
|
46
|
+
user = AWSAuth::User.find_by_key(access_key)
|
47
|
+
unless user.nil?
|
48
|
+
hmac = HMAC::SHA256.new(user.secret)
|
49
|
+
hmac.update(date_s)
|
50
|
+
if Base64.encode64(hmac.digest).chomp == signature
|
51
|
+
env['AWS_AUTH_USER'] = user
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# S3
|
55
|
+
elsif env['HTTP_AUTHORIZATION'] =~ /^AWS (\w+):(.+)$/ || !request['Signature'].nil?
|
56
|
+
meta, amz = {}, {}
|
57
|
+
env.each do |k,v|
|
58
|
+
k = k.downcase.gsub('_', '-')
|
59
|
+
amz[$1] = v.strip if k =~ /^http-x-amz-([-\w]+)$/
|
60
|
+
meta[$1] = v if k =~ /^http-x-amz-meta-([-\w]+)$/
|
61
|
+
end
|
62
|
+
|
63
|
+
auth, key_s, secret_s = *env['HTTP_AUTHORIZATION'].to_s.match(/^AWS (\w+):(.+)$/)
|
64
|
+
if request.params.has_key?('Signature') and Time.at(request['Expires'].to_i) >= Time.now
|
65
|
+
key_s, secret_s, date_s = request['AWSAccessKeyId'], request['Signature'], request['Expires']
|
66
|
+
end
|
67
|
+
uri = env['PATH_INFO']
|
68
|
+
uri += "?" + env['QUERY_STRING'] if %w[acl versioning torrent].include?(env['QUERY_STRING'])
|
69
|
+
canonical = [env['REQUEST_METHOD'], env['HTTP_CONTENT_MD5'], env['CONTENT_TYPE'],
|
70
|
+
date_s, uri]
|
71
|
+
amz.sort.each do |k, v|
|
72
|
+
canonical[-1,0] = "x-amz-#{k}:#{v}"
|
73
|
+
end
|
74
|
+
|
75
|
+
user = AWSAuth::User.find_by_key key_s
|
76
|
+
unless (user and secret_s != AWSAuth::Base.hmac_sha1(user.secret, canonical.map{|v|v.to_s.strip} * "\n")) || (user and user.deleted == 1)
|
77
|
+
env['AWS_AUTH_USER'] = user
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@app.call(env)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.generate_secret
|
85
|
+
abc = %{ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz}
|
86
|
+
(1..40).map { abc[rand(abc.size),1] }.join
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.generate_key
|
90
|
+
abc = %{ABCDEF0123456789}
|
91
|
+
(1..20).map { abc[rand(abc.size),1] }.join
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.hmac_sha1(key, s)
|
95
|
+
ipad = [].fill(0x36, 0, 64)
|
96
|
+
opad = [].fill(0x5C, 0, 64)
|
97
|
+
key = key.unpack("C*")
|
98
|
+
if key.length < 64 then
|
99
|
+
key += [].fill(0, 0, 64-key.length)
|
100
|
+
end
|
101
|
+
|
102
|
+
inner = []
|
103
|
+
64.times { |i| inner.push(key[i] ^ ipad[i]) }
|
104
|
+
inner += s.unpack("C*")
|
105
|
+
|
106
|
+
outer = []
|
107
|
+
64.times { |i| outer.push(key[i] ^ opad[i]) }
|
108
|
+
outer = outer.pack("c*")
|
109
|
+
outer += Digest::SHA1.digest(inner.pack("c*"))
|
110
|
+
|
111
|
+
return Base64::encode64(Digest::SHA1.digest(outer)).chomp
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
def self.load_config()
|
116
|
+
@@config_path ||= File.join(File.dirname(__FILE__), '../aws-auth.yml')
|
117
|
+
return YAML::load(File.read(ENV['AWS_AUTH_PATH'])) if ENV['AWS_AUTH_PATH'] && File.exists?(ENV['AWS_AUTH_PATH'])
|
118
|
+
return YAML::load(File.read(File.expand_path("~/.aws-auth.yml"))) if File.exists?(File.expand_path("~/.aws-auth.yml"))
|
119
|
+
return YAML::load(File.read(@@config_path)) if @@config_path && File.exists?(@@config_path)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
require File.join(File.dirname(__FILE__),'aws-auth/user')
|
126
|
+
require File.join(File.dirname(__FILE__),'aws-auth/admin')
|
@@ -0,0 +1,270 @@
|
|
1
|
+
module AWS
|
2
|
+
|
3
|
+
class Admin < Sinatra::Base
|
4
|
+
|
5
|
+
POST = %{if(!this.title||confirm(this.title+'?')){var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();}return false;}
|
6
|
+
|
7
|
+
set :sessions, :on
|
8
|
+
enable :inline_templates
|
9
|
+
|
10
|
+
@@navigation_tabs = [["users","/control/users",true],["profile","/control/profile"],["logout","/control/logout"]]
|
11
|
+
@@home_page = "/control/buckets"
|
12
|
+
|
13
|
+
def self.add_tab(name, path, admin_only=false)
|
14
|
+
t = [name,path]
|
15
|
+
t << admin_only unless admin_only == false
|
16
|
+
@@navigation_tabs = [t] + @@navigation_tabs
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.tabs
|
20
|
+
@@navigation_tabs
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.home_page
|
24
|
+
@@home_page
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.home_page=(val)
|
28
|
+
@@home_page = val
|
29
|
+
end
|
30
|
+
|
31
|
+
before do
|
32
|
+
ActiveRecord::Base.verify_active_connections!
|
33
|
+
end
|
34
|
+
|
35
|
+
get '/?' do
|
36
|
+
login_required
|
37
|
+
redirect @@home_page
|
38
|
+
end
|
39
|
+
|
40
|
+
get %r{^/s/(.*)} do
|
41
|
+
expires 500, :public
|
42
|
+
open(File.join(AWSAuth::Base::ROOT_DIR,'public', params[:captures].first))
|
43
|
+
end
|
44
|
+
|
45
|
+
get '/login' do
|
46
|
+
r :login, "Login"
|
47
|
+
end
|
48
|
+
|
49
|
+
post '/login' do
|
50
|
+
@user = AWSAuth::User.find_by_login params[:login]
|
51
|
+
if @user
|
52
|
+
if @user.password == AWSAuth::Base.hmac_sha1( params[:password], @user.secret )
|
53
|
+
session[:user_id] = @user.id
|
54
|
+
redirect @@home_page
|
55
|
+
else
|
56
|
+
@user.errors.add(:password, 'is incorrect')
|
57
|
+
end
|
58
|
+
else
|
59
|
+
@user = AWSAuth::User.new
|
60
|
+
@user.errors.add(:login, 'not found')
|
61
|
+
end
|
62
|
+
r :login, "Login"
|
63
|
+
end
|
64
|
+
|
65
|
+
get '/logout' do
|
66
|
+
session[:user_id] = nil
|
67
|
+
redirect '/control'
|
68
|
+
end
|
69
|
+
|
70
|
+
get "/profile/?" do
|
71
|
+
login_required
|
72
|
+
@usero = @user
|
73
|
+
r :profile, "Your Profile"
|
74
|
+
end
|
75
|
+
|
76
|
+
post "/profile/?" do
|
77
|
+
login_required
|
78
|
+
@user.update_attributes(params['user'])
|
79
|
+
@usero = @user
|
80
|
+
r :profile, "Your Profile"
|
81
|
+
end
|
82
|
+
|
83
|
+
get "/users/?" do
|
84
|
+
login_required
|
85
|
+
only_superusers
|
86
|
+
@usero = AWSAuth::User.new
|
87
|
+
@users = AWSAuth::User.find :all, :conditions => ['deleted != 1'], :order => 'login'
|
88
|
+
r :users, "User List"
|
89
|
+
end
|
90
|
+
|
91
|
+
post "/users/?" do
|
92
|
+
login_required
|
93
|
+
only_superusers
|
94
|
+
@usero = AWSAuth::User.new params['user'].merge(:activated_at => Time.now)
|
95
|
+
if @usero.valid?
|
96
|
+
@usero.save()
|
97
|
+
redirect "/control/users"
|
98
|
+
else
|
99
|
+
@users = AWSAuth::User.find :all, :conditions => ['deleted != 1'], :order => 'login'
|
100
|
+
r :users, "User List"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
get "/users/:login/?" do
|
105
|
+
login_required
|
106
|
+
only_superusers
|
107
|
+
@usero = AWSAuth::User.find_by_login params[:login]
|
108
|
+
r :profile, @usero.login
|
109
|
+
end
|
110
|
+
|
111
|
+
post "/users/:login/?" do
|
112
|
+
login_required
|
113
|
+
only_superusers
|
114
|
+
@usero = AWSAuth::User.find_by_login params[:login]
|
115
|
+
|
116
|
+
# if were not changing passwords remove blank values
|
117
|
+
if params['user']['password'].blank? && params['user']['password_confirmation'].blank?
|
118
|
+
params['user'].delete('password')
|
119
|
+
params['user'].delete('password_confirmation')
|
120
|
+
end
|
121
|
+
|
122
|
+
if @usero.update_attributes(params['user'])
|
123
|
+
redirect "/control/users/#{@usero.login}"
|
124
|
+
else
|
125
|
+
r :profile, @usero.login
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
post "/users/delete/:login/?" do
|
130
|
+
login_required
|
131
|
+
only_superusers
|
132
|
+
@usero = AWSAuth::User.find_by_login params[:login]
|
133
|
+
if @usero.id == @user.id
|
134
|
+
# FIXME: notify user they cannot delete themselves
|
135
|
+
else
|
136
|
+
@usero.destroy
|
137
|
+
end
|
138
|
+
redirect "/control/users"
|
139
|
+
end
|
140
|
+
|
141
|
+
protected
|
142
|
+
def login_required
|
143
|
+
@user = AWSAuth::User.find(session[:user_id]) unless session[:user_id].nil?
|
144
|
+
redirect '/control/login' if @user.nil?
|
145
|
+
end
|
146
|
+
|
147
|
+
def r(name, title, layout = :layout)
|
148
|
+
@title = title
|
149
|
+
haml name, :layout => layout
|
150
|
+
end
|
151
|
+
|
152
|
+
def errors_for(model)
|
153
|
+
ret = ""
|
154
|
+
if model.errors.size > 0
|
155
|
+
ret += "<ul class=\"errors\">"
|
156
|
+
model.errors.each_full do |error|
|
157
|
+
ret += "<li>#{error}</li>"
|
158
|
+
end
|
159
|
+
ret += "</ul>"
|
160
|
+
end
|
161
|
+
ret
|
162
|
+
end
|
163
|
+
|
164
|
+
def only_superusers
|
165
|
+
redirect '/control/login' unless @user.superuser?
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
__END__
|
173
|
+
|
174
|
+
@@ layout
|
175
|
+
%html
|
176
|
+
%head
|
177
|
+
%title Control Center » #{@title}
|
178
|
+
%script{ :language => "JavaScript", :type => "text/javascript", :src => "/control/s/js/prototype.js" }
|
179
|
+
%style{ :type => "text/css" }
|
180
|
+
@import '/control/s/css/control.css';
|
181
|
+
%body
|
182
|
+
%div#page
|
183
|
+
- if @user and not @login
|
184
|
+
%div.menu
|
185
|
+
%ul
|
186
|
+
%li
|
187
|
+
- for tab in AWS::Admin.tabs
|
188
|
+
- if tab[2].nil? || (tab[2] && @user.superuser?)
|
189
|
+
%a{ :href => tab[1] }= tab[0]
|
190
|
+
%div#header
|
191
|
+
%h1 Control Center
|
192
|
+
%h2 #{@title}
|
193
|
+
%div#content
|
194
|
+
= yield
|
195
|
+
|
196
|
+
@@ login
|
197
|
+
%form.create{ :method => "post" }
|
198
|
+
%div.required
|
199
|
+
%label{ :for => "login" } User
|
200
|
+
%input#login{ :type => "text", :name => "login" }
|
201
|
+
%div.required
|
202
|
+
%label{ :for => "password" } Password
|
203
|
+
%input#password{ :type => "password", :name => "password" }
|
204
|
+
%input#loggo{ :type => "submit", :value => "Login", :name => "loggo" }
|
205
|
+
|
206
|
+
@@ users
|
207
|
+
%table
|
208
|
+
%thead
|
209
|
+
%tr
|
210
|
+
%th Login
|
211
|
+
%th Activated On
|
212
|
+
%th Actions
|
213
|
+
%tbody
|
214
|
+
- @users.each do |user|
|
215
|
+
%tr
|
216
|
+
%th
|
217
|
+
%a{ :href => "/control/users/#{user.login}" } #{user.login}
|
218
|
+
%td #{user.activated_at}
|
219
|
+
%td
|
220
|
+
%a{ :href => "/control/users/delete/#{user.login}", :onclick => POST, :title => "Delete user #{user.login}" } Delete
|
221
|
+
%h3 Create a User
|
222
|
+
%form.create{ :action => "/control/users", :method => "post" }
|
223
|
+
= preserve errors_for(@usero)
|
224
|
+
%div.required
|
225
|
+
%label{ :for => "user[login]" } Login
|
226
|
+
%input.large{ :type => "text", :value => @usero.login, :name => "user[login]" }
|
227
|
+
%div.required.inline
|
228
|
+
%label{ :for => "user[superuser]" } Is a super-admin?
|
229
|
+
%input{ :type => "checkbox", :name => "user[superuser]", :value => @usero.superuser }
|
230
|
+
%div.required
|
231
|
+
%label{ :for => "user[password]" } Password
|
232
|
+
%input.fixed{ :type => "password", :name => "user[password]" }
|
233
|
+
%div.required
|
234
|
+
%label{ :for => "user[password_confirmation]" } Password again
|
235
|
+
%input.fixed{ :type => "password", :name => "user[password_confirmation]" }
|
236
|
+
%div.required
|
237
|
+
%label{ :for => "user[email]" } Email
|
238
|
+
%input{ :type => "text", :value => @usero.email, :name => "user[email]" }
|
239
|
+
%div.required
|
240
|
+
%label{ :for => "user[key]" } Key (must be unique)
|
241
|
+
%input.fixed.long{ :type => "text", :value => (@usero.key || AWSAuth::Base.generate_key), :name => "user[key]" }
|
242
|
+
%div.required
|
243
|
+
%label{ :for => "user[secret]" } Secret
|
244
|
+
%input.fixed.long{ :type => "text", :value => (@usero.secret || AWSAuth::Base.generate_secret), :name => "user[secret]" }
|
245
|
+
%input.newuser{ :type => "submit", :value => "Create", :name => "newuser" }
|
246
|
+
|
247
|
+
@@ profile
|
248
|
+
%form.create{ :method => "post" }
|
249
|
+
= preserve errors_for(@usero)
|
250
|
+
- if @user.superuser?
|
251
|
+
%div.required.inline
|
252
|
+
%label{ :for => "user[superuser]" } Is a super-admin?
|
253
|
+
%input{ :type => "hidden", :name => "user[superuser]", :value => 0 }
|
254
|
+
%input{ :type => "checkbox", :name => "user[superuser]", :value => 1, :checked => @usero.superuser? }
|
255
|
+
%div.required
|
256
|
+
%label{ :for => "user[password]" } Password
|
257
|
+
%input.fixed{ :type => "password", :name => "user[password]" }
|
258
|
+
%div.required
|
259
|
+
%label{ :for => "user[password_confirmation]" } Password again
|
260
|
+
%input.fixed{ :type => "password", :name => "user[password_confirmation]" }
|
261
|
+
%div.required
|
262
|
+
%label{ :for => "user[email]" } Email
|
263
|
+
%input{ :type => "text", :value => @usero.email, :name => "user[email]" }
|
264
|
+
%div.required
|
265
|
+
%label{ :for => "key" } Key
|
266
|
+
%h4 #{@usero.key}
|
267
|
+
%div.required
|
268
|
+
%label{ :for => "secret" } Secret
|
269
|
+
%h4 #{@usero.secret}
|
270
|
+
%input#saveuser{ :type => "submit", :value => "Save", :name => "saveuser" }
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require File.join(File.dirname(__FILE__), '../aws-auth')
|
5
|
+
|
6
|
+
namespace :auth do
|
7
|
+
task :environment do
|
8
|
+
ActiveRecord::Base.establish_connection(AWSAuth::Base.config[:auth])
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Migrate the database"
|
12
|
+
task(:migrate => :environment) do
|
13
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
14
|
+
ActiveRecord::Migration.verbose = true
|
15
|
+
|
16
|
+
out_dir = File.dirname(AWSAuth::Base.config[:auth][:database])
|
17
|
+
FileUtils.mkdir_p(out_dir) unless File.exists?(out_dir)
|
18
|
+
|
19
|
+
ActiveRecord::Migrator.migrate(File.join(AWSAuth::Base::ROOT_DIR, 'db', 'migrate'), ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
|
20
|
+
num_users = AWSAuth::User.count || 0
|
21
|
+
if num_users == 0
|
22
|
+
puts "** No users found, creating the `admin' user."
|
23
|
+
AWSAuth::User.create :login => "admin", :password => AWSAuth::Base::DEFAULT_PASSWORD,
|
24
|
+
:email => "admin@parkplace.net", :key => AWSAuth::Base.generate_key(), :secret => AWSAuth::Base.generate_secret(),
|
25
|
+
:activated_at => Time.now, :superuser => 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|