aws-auth 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1 @@
1
+ Rack middleware that provides AWS (Amazon Web Services) style authentication.
@@ -0,0 +1,8 @@
1
+ $:.unshift "./lib"
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/gempackagetask'
5
+ require 'aws-auth'
6
+ require 'aws-auth/tasks'
7
+ require 'bundler'
8
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,4 @@
1
+ ---
2
+ :auth:
3
+ :database: db/aws-users.db
4
+ :adapter: sqlite3
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
+
Binary file
@@ -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 &raquo; #{@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