danski-ooh-auth 0.1.2

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.
Files changed (47) hide show
  1. data/LICENSE +20 -0
  2. data/Rakefile +58 -0
  3. data/app/controllers/application.rb +16 -0
  4. data/app/controllers/authenticating_clients.rb +60 -0
  5. data/app/controllers/tokens.rb +94 -0
  6. data/app/helpers/application_helper.rb +64 -0
  7. data/app/helpers/authenticating_clients_helper.rb +5 -0
  8. data/app/helpers/authentications_helper.rb +5 -0
  9. data/app/models/authenticating_client.rb +12 -0
  10. data/app/models/authenticating_client/dm_authenticating_client.rb +71 -0
  11. data/app/models/token.rb +12 -0
  12. data/app/models/token/dm_token.rb +150 -0
  13. data/app/views/authenticating_clients/_help.html.erb +1 -0
  14. data/app/views/authenticating_clients/edit.html.erb +27 -0
  15. data/app/views/authenticating_clients/index.html.erb +24 -0
  16. data/app/views/authenticating_clients/new.html.erb +47 -0
  17. data/app/views/authenticating_clients/show.html.erb +40 -0
  18. data/app/views/layout/ooh_auth.html.erb +23 -0
  19. data/app/views/tokens/create.html.erb +34 -0
  20. data/app/views/tokens/edit.html.erb +4 -0
  21. data/app/views/tokens/new.html.erb +52 -0
  22. data/app/views/tokens/show.html.erb +1 -0
  23. data/lib/ooh-auth.rb +103 -0
  24. data/lib/ooh-auth/authentication_mixin.rb +13 -0
  25. data/lib/ooh-auth/controller_mixin.rb +38 -0
  26. data/lib/ooh-auth/key_generators.rb +57 -0
  27. data/lib/ooh-auth/merbtasks.rb +103 -0
  28. data/lib/ooh-auth/request_verification_mixin.rb +160 -0
  29. data/lib/ooh-auth/slicetasks.rb +18 -0
  30. data/lib/ooh-auth/spectasks.rb +65 -0
  31. data/lib/ooh-auth/strategies/oauth.rb +16 -0
  32. data/public/javascripts/master.js +0 -0
  33. data/public/stylesheets/master.css +2 -0
  34. data/readme.markdown +43 -0
  35. data/spec/controllers/application_spec.rb +35 -0
  36. data/spec/controllers/authenticating_clients_spec.rb +119 -0
  37. data/spec/controllers/tokens_spec.rb +173 -0
  38. data/spec/merb-auth-slice-fullfat_spec.rb +41 -0
  39. data/spec/models/authenticating_client_spec.rb +44 -0
  40. data/spec/models/oauth_strategy_spec.rb +48 -0
  41. data/spec/models/request_verification_mixin_spec.rb +121 -0
  42. data/spec/models/token_spec.rb +139 -0
  43. data/spec/spec_fixtures.rb +19 -0
  44. data/spec/spec_helper.rb +107 -0
  45. data/stubs/app/controllers/application.rb +2 -0
  46. data/stubs/app/controllers/main.rb +2 -0
  47. metadata +133 -0
@@ -0,0 +1,103 @@
1
+ namespace :slices do
2
+ namespace :ooh_auth do
3
+
4
+ desc "Install OohAuth"
5
+ task :install => [:preflight, :setup_directories, :copy_assets, :migrate]
6
+
7
+ desc "Test for any dependencies"
8
+ task :preflight do # see slicetasks.rb
9
+ end
10
+
11
+ desc "Setup directories"
12
+ task :setup_directories do
13
+ puts "Creating directories for host application"
14
+ OohAuth.mirrored_components.each do |type|
15
+ if File.directory?(OohAuth.dir_for(type))
16
+ if !File.directory?(dst_path = OohAuth.app_dir_for(type))
17
+ relative_path = dst_path.relative_path_from(Merb.root)
18
+ puts "- creating directory :#{type} #{File.basename(Merb.root) / relative_path}"
19
+ mkdir_p(dst_path)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ desc "Copy stub files to host application"
26
+ task :stubs do
27
+ puts "Copying stubs for OohAuth - resolves any collisions"
28
+ copied, preserved = OohAuth.mirror_stubs!
29
+ puts "- no files to copy" if copied.empty? && preserved.empty?
30
+ copied.each { |f| puts "- copied #{f}" }
31
+ preserved.each { |f| puts "! preserved override as #{f}" }
32
+ end
33
+
34
+ desc "Copy stub files and views to host application"
35
+ task :patch => [ "stubs", "freeze:views" ]
36
+
37
+ desc "Copy public assets to host application"
38
+ task :copy_assets do
39
+ puts "Copying assets for OohAuth - resolves any collisions"
40
+ copied, preserved = OohAuth.mirror_public!
41
+ puts "- no files to copy" if copied.empty? && preserved.empty?
42
+ copied.each { |f| puts "- copied #{f}" }
43
+ preserved.each { |f| puts "! preserved override as #{f}" }
44
+ end
45
+
46
+ desc "Migrate the database"
47
+ task :migrate do # see slicetasks.rb
48
+ end
49
+
50
+ desc "Freeze OohAuth into your app (only ooh-auth/app)"
51
+ task :freeze => [ "freeze:app" ]
52
+
53
+ namespace :freeze do
54
+
55
+ desc "Freezes OohAuth by installing the gem into application/gems"
56
+ task :gem do
57
+ ENV["GEM"] ||= "ooh-auth"
58
+ Rake::Task['slices:install_as_gem'].invoke
59
+ end
60
+
61
+ desc "Freezes OohAuth by copying all files from ooh-auth/app to your application"
62
+ task :app do
63
+ puts "Copying all ooh-auth/app files to your application - resolves any collisions"
64
+ copied, preserved = OohAuth.mirror_app!
65
+ puts "- no files to copy" if copied.empty? && preserved.empty?
66
+ copied.each { |f| puts "- copied #{f}" }
67
+ preserved.each { |f| puts "! preserved override as #{f}" }
68
+ end
69
+
70
+ desc "Freeze all views into your application for easy modification"
71
+ task :views do
72
+ puts "Copying all view templates to your application - resolves any collisions"
73
+ copied, preserved = OohAuth.mirror_files_for :view
74
+ puts "- no files to copy" if copied.empty? && preserved.empty?
75
+ copied.each { |f| puts "- copied #{f}" }
76
+ preserved.each { |f| puts "! preserved override as #{f}" }
77
+ end
78
+
79
+ desc "Freeze all models into your application for easy modification"
80
+ task :models do
81
+ puts "Copying all models to your application - resolves any collisions"
82
+ copied, preserved = OohAuth.mirror_files_for :model
83
+ puts "- no files to copy" if copied.empty? && preserved.empty?
84
+ copied.each { |f| puts "- copied #{f}" }
85
+ preserved.each { |f| puts "! preserved override as #{f}" }
86
+ end
87
+
88
+ desc "Freezes OohAuth as a gem and copies over ooh-auth/app"
89
+ task :app_with_gem => [:gem, :app]
90
+
91
+ desc "Freezes OohAuth by unpacking all files into your application"
92
+ task :unpack do
93
+ puts "Unpacking OohAuth files to your application - resolves any collisions"
94
+ copied, preserved = OohAuth.unpack_slice!
95
+ puts "- no files to copy" if copied.empty? && preserved.empty?
96
+ copied.each { |f| puts "- copied #{f}" }
97
+ preserved.each { |f| puts "! preserved override as #{f}" }
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,160 @@
1
+ =begin
2
+ OohAuth::Request::VerificationMixin is a mixin module for Merb's internal Request class.
3
+
4
+ It provides
5
+ =end
6
+
7
+ require 'hmac-sha1'
8
+ require 'hmac-md5'
9
+
10
+ module OohAuth
11
+ module Request
12
+ module VerificationMixin
13
+
14
+ # Returns TRUE if the request contains api-flavour parameters. At least an api_token and an api_signature must be present
15
+ def oauth_request?
16
+ (consumer_key)? true : false
17
+ end
18
+
19
+ # Returns the authenticating client referenced by the consumer key in the given request, or nil if
20
+ # no consumer key was given or if the given consumer key was invalid.
21
+ def authenticating_client
22
+ #return false unless signed?
23
+ @authenticating_client ||= OohAuth::AuthenticatingClient.first(:api_key=>consumer_key)
24
+ end
25
+
26
+ # Returns the stored token referenced by the oauth_token header or parameter, or nil if none was found.
27
+ def authentication_token
28
+ @authentication_token ||= OohAuth::Token.first(:token_key=>token)
29
+ end
30
+
31
+ # Attempts to verify the request's signature using the strategy covered in signing.markdown.
32
+ # Takes one argument, which is the authenticating client you wish to check the signature against.
33
+ # Returns a true on success, false on fail.
34
+ def signed?
35
+ # Fail immediately if the request is not signed at all
36
+ return false unless oauth_request? and authenticating_client
37
+ # mash and compare with given signature
38
+ self.signature == build_signature
39
+ end
40
+
41
+ # Creates a signature for this request, returning the final hash required for insertion in a signed URL.
42
+ def build_signature
43
+ sig = case signature_method
44
+ when "HMAC-SHA1"
45
+ Base64.encode64(HMAC::SHA1.digest(signature_secret, signature_base_string)).chomp.gsub(/\n/,'')
46
+ when "HMAC-MD5"
47
+ Base64.encode64(HMAC::MD5.digest(signature_secret, signature_base_string)).chomp.gsub(/\n/,'')
48
+ else
49
+ false
50
+ end
51
+ Merb::Parse.escape(sig)
52
+ end
53
+
54
+ # Creates a plaintext version of the signature base string ready to be run through any#
55
+ # of the support OAuth signature methods.
56
+ # See http://oauth.net/core/1.0#signing_process for more information.
57
+ def signature_base_string
58
+ "#{method.to_s.upcase}&#{full_uri}&#{normalise_signature_params}"
59
+ end
60
+
61
+ # Returns the signature secret, which is expected to be the HMAC encryption key for signed requests.
62
+ # If the request refers to a token, the token will be retrieved
63
+ def signature_secret
64
+ "#{authenticating_client.secret}&#{authentication_token ? authentication_token.secret : nil}" rescue raise Merb::ControllerExceptions::NotAcceptable
65
+ end
66
+
67
+ # Scrubs route parameters from the known params, returning a hash of known GET and POST parameters.
68
+ # Basically, this returns the parameters needed in the signature key/value gibberish.
69
+ # FIXME unidentified request gremlins seeding params with mix of symbol and string keys, requiring to_s filth all over the match block.
70
+ def signature_params
71
+ route, route_params = Merb::Router.route_for(self)
72
+ #raise RuntimeError, route_params.inspect
73
+ return oauth_merged_params.delete_if {|k,v| route_params.keys.map{|s|s.to_s}.include?(k.to_s) or k.to_s == "oauth_signature"}
74
+ end
75
+
76
+ # Returns the signature_params as a normalised string in line with
77
+ # http://oauth.net/core/1.0#signing_process
78
+ def normalise_signature_params
79
+ signature_params.sort.collect{|key, value| "#{key}=#{value}"}.join("&")
80
+ end
81
+
82
+ # Returns the params properly merged with the oauth headers if they were given.
83
+ # OAuth headers take priority if a GET/POST parameter with the same name exists.
84
+ def oauth_merged_params
85
+ params.merge(signature_oauth_headers)
86
+ end
87
+
88
+ # Returns any given OAuth headers as specified in http://oauth.net/core/1.0#auth_header as a hash.
89
+ def oauth_headers
90
+ @oauth_headers ||= parse_oauth_headers
91
+ end
92
+
93
+ # Returns the auth headers for duplicating the request signature,
94
+ # missing the realm variable as defined in
95
+ # http://oauth.net/core/1.0#signing_process
96
+ def signature_oauth_headers
97
+ o = oauth_headers.dup; o.delete(:realm); o
98
+ end
99
+
100
+ # Parses the given OAuth headers into a hash. See http://oauth.net/core/1.0#auth_header for parsing method.
101
+ def parse_oauth_headers
102
+ # Pull headers and return blank hash if no header variables found
103
+ headers = env['AUTHORIZATION']; result = {};
104
+ return result unless headers && headers[0,5] == 'OAuth'
105
+ # Headers found. Go ahead and match 'em
106
+ headers.split(/,\n*\r*/).each do |param|
107
+ phrase, key, value = param.match(/([A-Za-z0-9_\s]+)="([^"]+)"/).to_a.map{|v| v.strip}
108
+ result[(key["OAuth"])? :realm : key.to_sym] = value
109
+ end
110
+ result
111
+ end
112
+
113
+
114
+
115
+ # OAuth variable accessors
116
+ # --------------------------------------------------------------------------------------------
117
+
118
+ # Returns the requested signature signing mechanism from the auth headers, defaulting to HMAC-SHA1
119
+ def signature_method
120
+ oauth_merged_params[:oauth_signature_method] || "HMAC-SHA1"
121
+ end
122
+
123
+ # Returns the oauth_consumer_key from the Authorization header or the GET/POST params, or nil if not present.
124
+ def consumer_key
125
+ oauth_merged_params[:oauth_consumer_key]
126
+ end
127
+
128
+ # Returns the oauth_token from the Authorization header or the GET/POST params, or nil if not present.
129
+ def token
130
+ oauth_merged_params[:oauth_token]
131
+ end
132
+
133
+ # Returns the oauth_signature from the Authorization header or the GET/POST params, or nil if not present.
134
+ def signature
135
+ # FIXME merb keeps mangling this by replacing "+" with "\s"
136
+ oauth_merged_params[:oauth_signature]
137
+ end
138
+
139
+ # Returns the oauth_timestamp from the Authorization header or the GET/POST params, or nil if not present.
140
+ def timestamp
141
+ oauth_merged_params[:oauth_timestamp]
142
+ end
143
+
144
+ # Returns the oauth_nonce from the Authorization header or the GET/POST params, or nil if not present.
145
+ def nonce
146
+ oauth_merged_params[:oauth_nonce]
147
+ end
148
+
149
+ def callback
150
+ oauth_merged_params[:oauth_callback]
151
+ end
152
+
153
+ # Returns the oauth_version from the Authorization header or the GET/POST params, or nil if not present, defaulting to "1.0" if not given.
154
+ def oauth_version
155
+ oauth_merged_params[:oauth_version] || "1.0"
156
+ end
157
+
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,18 @@
1
+ namespace :slices do
2
+ namespace :ooh_auth do
3
+
4
+ # add your own ooh-auth tasks here
5
+
6
+ # implement this to test for structural/code dependencies
7
+ # like certain directories or availability of other files
8
+ desc "Test for any dependencies"
9
+ task :preflight do
10
+ end
11
+
12
+ # implement this to perform any database related setup steps
13
+ desc "Migrate the database"
14
+ task :migrate do
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,65 @@
1
+ namespace :slices do
2
+ namespace :ooh_auth do
3
+
4
+ desc "Run slice specs within the host application context"
5
+ task :spec => [ "spec:explain", "spec:default" ]
6
+
7
+ namespace :spec do
8
+
9
+ slice_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
10
+
11
+ task :explain do
12
+ puts "\nNote: By running OohAuth specs inside the application context any\n" +
13
+ "overrides could break existing specs. This isn't always a problem,\n" +
14
+ "especially in the case of views. Use these spec tasks to check how\n" +
15
+ "well your application conforms to the original slice implementation."
16
+ end
17
+
18
+ Spec::Rake::SpecTask.new('default') do |t|
19
+ t.spec_opts = ["--format", "specdoc", "--colour"]
20
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
21
+ end
22
+
23
+ desc "Run all model specs, run a spec for a specific Model with MODEL=MyModel"
24
+ Spec::Rake::SpecTask.new('model') do |t|
25
+ t.spec_opts = ["--format", "specdoc", "--colour"]
26
+ if(ENV['MODEL'])
27
+ t.spec_files = Dir["#{slice_root}/spec/models/**/#{ENV['MODEL']}_spec.rb"].sort
28
+ else
29
+ t.spec_files = Dir["#{slice_root}/spec/models/**/*_spec.rb"].sort
30
+ end
31
+ end
32
+
33
+ desc "Run all controller specs, run a spec for a specific Controller with CONTROLLER=MyController"
34
+ Spec::Rake::SpecTask.new('controller') do |t|
35
+ t.spec_opts = ["--format", "specdoc", "--colour"]
36
+ if(ENV['CONTROLLER'])
37
+ t.spec_files = Dir["#{slice_root}/spec/controllers/**/#{ENV['CONTROLLER']}_spec.rb"].sort
38
+ else
39
+ t.spec_files = Dir["#{slice_root}/spec/controllers/**/*_spec.rb"].sort
40
+ end
41
+ end
42
+
43
+ desc "Run all view specs, run specs for a specific controller (and view) with CONTROLLER=MyController (VIEW=MyView)"
44
+ Spec::Rake::SpecTask.new('view') do |t|
45
+ t.spec_opts = ["--format", "specdoc", "--colour"]
46
+ if(ENV['CONTROLLER'] and ENV['VIEW'])
47
+ t.spec_files = Dir["#{slice_root}/spec/views/**/#{ENV['CONTROLLER']}/#{ENV['VIEW']}*_spec.rb"].sort
48
+ elsif(ENV['CONTROLLER'])
49
+ t.spec_files = Dir["#{slice_root}/spec/views/**/#{ENV['CONTROLLER']}/*_spec.rb"].sort
50
+ else
51
+ t.spec_files = Dir["#{slice_root}/spec/views/**/*_spec.rb"].sort
52
+ end
53
+ end
54
+
55
+ desc "Run all specs and output the result in html"
56
+ Spec::Rake::SpecTask.new('html') do |t|
57
+ t.spec_opts = ["--format", "html"]
58
+ t.libs = ['lib', 'server/lib' ]
59
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,16 @@
1
+ # Authentication Strategy for OohAuth's AuthenticatingClient model.
2
+
3
+ class Merb::Authentication
4
+ module Strategies
5
+ class OAuth < Merb::Authentication::Strategy
6
+
7
+ def run!
8
+ if request.signed? and request.token and request.consumer_key
9
+ return OohAuth::Token.authenticate!(request.consumer_key, request.token)
10
+ end
11
+ return nil
12
+ end
13
+
14
+ end # APIToken
15
+ end # Strategies
16
+ end # MAuth
File without changes
@@ -0,0 +1,2 @@
1
+ html, body { margin: 0; padding: 0; }
2
+ #container { width: 800px; margin: 4em auto; padding: 4em 4em 6em 4em; background: #DDDDDD; }
data/readme.markdown ADDED
@@ -0,0 +1,43 @@
1
+ There's Auth, there's OAuth, and there's OohAuth.
2
+ =================================================
3
+
4
+ OohAuth extends merb-auth-more with a functionally-complete approach to OAuth, turning your merb-auth applications into full OAuth providers.
5
+
6
+ OAuth at a glance:
7
+ ==================
8
+
9
+ * Your users won't have to give their names and passwords to client applications
10
+ * Your users can revoke or limit access from a particular client at any time
11
+ * Your users do not have to give client applications everything they need to steal their account
12
+ * Your developer community can authenticate using a solid authentication schema endorsed by [industry giants](http://google.com)
13
+ * Resilient to both man-in-the-middle and signature replay attacks.
14
+
15
+ OohAuth gives you:
16
+ ========================
17
+
18
+ * Integration with merb-auth and your application's own User model
19
+ * RESTful creation of API keys for client apps
20
+ * RESTful creation of request and access tokens to allow client apps to authenticate on behalf of users
21
+ * merb-auth strategies for both web-based and non web-based API authentication.
22
+
23
+ It depends on:
24
+ ==============
25
+
26
+ * merb-slices
27
+ * merb-action-args
28
+ * merb-auth-core
29
+ * merb-auth-more
30
+ * nokogiri (tests only)
31
+ * ruby-hmac
32
+ * Erb **(we need your help to get started on HAML support)**
33
+ * datamapper **(we need your help to become ORM-agnostic)**
34
+
35
+ You should read:
36
+ ================
37
+
38
+ * [Why we wrote it](http://singlecell.angryamoeba.co.uk/post/62022487/the-api-antipattern-twitter-and-the-fail-whales-new)
39
+ * [OohAuth on github](http://github.com/danski/ooh-auth)
40
+ * [OAuth 1.0 specification](http://oauth.net/core/1.0) a hefty spec document containing instructions for authenticating with OAuth apps and more.
41
+ * [securing.markdown](http://github.com/danski/ooh-auth/tree/master/securing.markdown), your guide to properly securing an application using OohAuth.
42
+ * [OohAuth's bugtracker on Tails](http://www.bugtails.com/projects/171)
43
+
@@ -0,0 +1,35 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
+
3
+ describe OohAuth::Application do
4
+
5
+ before :all do
6
+ Merb::Router.prepare { add_slice(:OohAuth) } if standalone?
7
+ @controller = dispatch_to(OohAuth::Public, :index)
8
+ end
9
+
10
+ after :all do
11
+ Merb::Router.reset! if standalone?
12
+ end
13
+
14
+ it "should have access to the slice module" do
15
+ @controller.slice.should == OohAuth
16
+ @controller.slice.should == OohAuth::Public.slice
17
+ end
18
+
19
+ it "should have helper methods for dealing with public paths" do
20
+ @controller.public_path_for(:image).should == "/slices/ooh-auth/images"
21
+ @controller.public_path_for(:javascript).should == "/slices/ooh-auth/javascripts"
22
+ @controller.public_path_for(:stylesheet).should == "/slices/ooh-auth/stylesheets"
23
+
24
+ @controller.image_path.should == "/slices/ooh-auth/images"
25
+ @controller.javascript_path.should == "/slices/ooh-auth/javascripts"
26
+ @controller.stylesheet_path.should == "/slices/ooh-auth/stylesheets"
27
+ end
28
+
29
+ it "should have a slice-specific _template_root" do
30
+ OohAuth::Public._template_root.should == OohAuth.dir_for(:view)
31
+ OohAuth::Public._template_root.should == OohAuth::Application._template_root
32
+ end
33
+
34
+
35
+ end