tpitale-rack-oauth2-server 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG +202 -0
  2. data/Gemfile +16 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +604 -0
  5. data/Rakefile +90 -0
  6. data/VERSION +1 -0
  7. data/bin/oauth2-server +206 -0
  8. data/lib/rack-oauth2-server.rb +4 -0
  9. data/lib/rack/oauth2/admin/css/screen.css +347 -0
  10. data/lib/rack/oauth2/admin/images/loading.gif +0 -0
  11. data/lib/rack/oauth2/admin/images/oauth-2.png +0 -0
  12. data/lib/rack/oauth2/admin/js/application.coffee +220 -0
  13. data/lib/rack/oauth2/admin/js/jquery.js +166 -0
  14. data/lib/rack/oauth2/admin/js/jquery.tmpl.js +414 -0
  15. data/lib/rack/oauth2/admin/js/protovis-r3.2.js +277 -0
  16. data/lib/rack/oauth2/admin/js/sammy.js +5 -0
  17. data/lib/rack/oauth2/admin/js/sammy.json.js +5 -0
  18. data/lib/rack/oauth2/admin/js/sammy.oauth2.js +142 -0
  19. data/lib/rack/oauth2/admin/js/sammy.storage.js +5 -0
  20. data/lib/rack/oauth2/admin/js/sammy.title.js +5 -0
  21. data/lib/rack/oauth2/admin/js/sammy.tmpl.js +5 -0
  22. data/lib/rack/oauth2/admin/js/underscore.js +722 -0
  23. data/lib/rack/oauth2/admin/views/client.tmpl +58 -0
  24. data/lib/rack/oauth2/admin/views/clients.tmpl +52 -0
  25. data/lib/rack/oauth2/admin/views/edit.tmpl +80 -0
  26. data/lib/rack/oauth2/admin/views/index.html +39 -0
  27. data/lib/rack/oauth2/admin/views/no_access.tmpl +4 -0
  28. data/lib/rack/oauth2/models.rb +27 -0
  29. data/lib/rack/oauth2/models/access_grant.rb +54 -0
  30. data/lib/rack/oauth2/models/access_token.rb +129 -0
  31. data/lib/rack/oauth2/models/auth_request.rb +61 -0
  32. data/lib/rack/oauth2/models/client.rb +93 -0
  33. data/lib/rack/oauth2/rails.rb +105 -0
  34. data/lib/rack/oauth2/server.rb +458 -0
  35. data/lib/rack/oauth2/server/admin.rb +250 -0
  36. data/lib/rack/oauth2/server/errors.rb +104 -0
  37. data/lib/rack/oauth2/server/helper.rb +147 -0
  38. data/lib/rack/oauth2/server/practice.rb +79 -0
  39. data/lib/rack/oauth2/server/railtie.rb +24 -0
  40. data/lib/rack/oauth2/server/utils.rb +30 -0
  41. data/lib/rack/oauth2/sinatra.rb +71 -0
  42. data/rack-oauth2-server.gemspec +24 -0
  43. data/rails/init.rb +11 -0
  44. data/test/admin/api_test.rb +228 -0
  45. data/test/admin/ui_test.rb +38 -0
  46. data/test/oauth/access_grant_test.rb +276 -0
  47. data/test/oauth/access_token_test.rb +311 -0
  48. data/test/oauth/authorization_test.rb +298 -0
  49. data/test/oauth/server_methods_test.rb +292 -0
  50. data/test/rails2/app/controllers/api_controller.rb +40 -0
  51. data/test/rails2/app/controllers/application_controller.rb +2 -0
  52. data/test/rails2/app/controllers/oauth_controller.rb +17 -0
  53. data/test/rails2/config/environment.rb +19 -0
  54. data/test/rails2/config/environments/test.rb +0 -0
  55. data/test/rails2/config/routes.rb +13 -0
  56. data/test/rails3/app/controllers/api_controller.rb +40 -0
  57. data/test/rails3/app/controllers/application_controller.rb +2 -0
  58. data/test/rails3/app/controllers/oauth_controller.rb +17 -0
  59. data/test/rails3/config/application.rb +19 -0
  60. data/test/rails3/config/environment.rb +2 -0
  61. data/test/rails3/config/routes.rb +12 -0
  62. data/test/setup.rb +120 -0
  63. data/test/sinatra/my_app.rb +69 -0
  64. metadata +145 -0
@@ -0,0 +1,79 @@
1
+ require "rack/oauth2/server/admin"
2
+
3
+ module Rack
4
+ module OAuth2
5
+ class Server
6
+
7
+ class Practice < ::Sinatra::Base
8
+ register Rack::OAuth2::Sinatra
9
+
10
+ get "/" do
11
+ <<-HTML
12
+ <html>
13
+ <head>
14
+ <title>OAuth 2.0 Practice Server</title>
15
+ </head>
16
+ <body>
17
+ <h1>Welcome to OAuth 2.0 Practice Server</h1>
18
+ <p>This practice server is for testing your OAuth 2.0 client library.</p>
19
+ <dl>
20
+ <dt>Authorization end-point:</dt>
21
+ <dd>http://#{request.host}:#{request.port}/oauth/authorize</dd>
22
+ <dt>Access token end-point:<//dt>
23
+ <dd>http://#{request.host}:#{request.port}/oauth/access_token</dd>
24
+ <dt>Resource requiring authentication:</dt>
25
+ <dd>http://#{request.host}:#{request.port}/secret</dd>
26
+ <dt>Resource requiring authorization and scope "sudo":</dt>
27
+ <dd>http://#{request.host}:#{request.port}/make</dd>
28
+ </dl>
29
+ <p>The scope can be "nobody", "sudo", "oauth-admin" or combination of the three.</p>
30
+ <p>You can manage client applications and tokens from the <a href="/oauth/admin">OAuth console</a>.</p>
31
+ </body>
32
+ </html>
33
+ HTML
34
+ end
35
+
36
+ # -- Simple authorization --
37
+
38
+ get "/oauth/authorize" do
39
+ <<-HTML
40
+ <html>
41
+ <head>
42
+ <title>OAuth 2.0 Practice Server</title>
43
+ </head>
44
+ <body>
45
+ <h1><a href="#{oauth.client.link}">#{oauth.client.display_name}</a> wants to access your account with the scope #{oauth.scope.join(", ")}</h1>
46
+ <form action="/oauth/grant" method="post" style="display:inline-block">
47
+ <button>Grant</button>
48
+ <input type="hidden" name="authorization" value="#{oauth.authorization}">
49
+ </form>
50
+ <form action="/oauth/deny" method="post" style="display:inline-block">
51
+ <button>Deny</button>
52
+ <input type="hidden" name="authorization" value="#{oauth.authorization}">
53
+ </form>
54
+ </body>
55
+ </html>
56
+ HTML
57
+ end
58
+ post "/oauth/grant" do
59
+ oauth.grant! "Superman"
60
+ end
61
+ post "/oauth/deny" do
62
+ oauth.deny!
63
+ end
64
+
65
+ # -- Protected resources --
66
+
67
+ oauth_required "/secret"
68
+ get "/private" do
69
+ "You're awesome!"
70
+ end
71
+
72
+ oauth_required "/make", :scope=>"sudo"
73
+ get "/write" do
74
+ "Sandwhich"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,24 @@
1
+ require "rack/oauth2/server"
2
+ require "rack/oauth2/rails"
3
+ require "rails"
4
+
5
+ module Rack
6
+ module OAuth2
7
+ class Server
8
+ # Rails 3.x integration.
9
+ class Railtie < ::Rails::Railtie # :nodoc:
10
+ config.oauth = Server::Options.new
11
+
12
+ initializer "rack-oauth2-server" do |app|
13
+ app.middleware.use ::Rack::OAuth2::Server, app.config.oauth
14
+ config.oauth.logger ||= ::Rails.logger
15
+ class ::ActionController::Base
16
+ helper ::Rack::OAuth2::Rails::Helpers
17
+ include ::Rack::OAuth2::Rails::Helpers
18
+ extend ::Rack::OAuth2::Rails::Filters
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ module Rack
2
+ module OAuth2
3
+ class Server
4
+
5
+ module Utils
6
+ module_function
7
+
8
+ # Parses the redirect URL, normalizes it and returns a URI object.
9
+ #
10
+ # Raises InvalidRequestError if not an absolute HTTP/S URL.
11
+ def parse_redirect_uri(redirect_uri)
12
+ raise InvalidRequestError, "Missing redirect URL" unless redirect_uri
13
+ uri = URI.parse(redirect_uri).normalize rescue nil
14
+ raise InvalidRequestError, "Redirect URL looks fishy to me" unless uri
15
+ raise InvalidRequestError, "Redirect URL must be absolute URL" unless uri.absolute? && uri.host
16
+ raise InvalidRequestError, "Redirect URL must point to HTTP/S location" unless uri.scheme == "http" || uri.scheme == "https"
17
+ uri
18
+ end
19
+
20
+ # Given scope as either array or string, return array of same names,
21
+ # unique and sorted.
22
+ def normalize_scope(scope)
23
+ (Array === scope ? scope.join(" ") : scope || "").split(/\s+/).compact.uniq.sort
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,71 @@
1
+ require "rack/oauth2/server"
2
+
3
+ module Rack
4
+ module OAuth2
5
+
6
+ # Sinatra support.
7
+ #
8
+ # Adds oauth instance method that returns Rack::OAuth2::Helper, see there for
9
+ # more details.
10
+ #
11
+ # Adds oauth_required class method. Use this filter with paths that require
12
+ # authentication, and with paths that require client to have a specific
13
+ # access scope.
14
+ #
15
+ # Adds oauth setting you can use to configure the module (e.g. setting
16
+ # available scope, see example).
17
+ #
18
+ # @example
19
+ # require "rack/oauth2/sinatra"
20
+ # class MyApp < Sinatra::Base
21
+ # register Rack::OAuth2::Sinatra
22
+ # oauth[:scope] = %w{read write}
23
+ #
24
+ # oauth_required "/api"
25
+ # oauth_required "/api/edit", :scope=>"write"
26
+ #
27
+ # before { @user = User.find(oauth.identity) if oauth.authenticated? }
28
+ # end
29
+ #
30
+ # @see Helpers
31
+ module Sinatra
32
+
33
+ # Adds before filter to require authentication on all the listed paths.
34
+ # Use the :scope option if client must also have access to that scope.
35
+ #
36
+ # @param [String, ...] path One or more paths that require authentication
37
+ # @param [optional, Hash] options Currently only :scope is supported.
38
+ def oauth_required(*args)
39
+ options = args.pop if Hash === args.last
40
+ scope = options[:scope] if options
41
+ args.each do |path|
42
+ before path do
43
+ if oauth.authenticated?
44
+ if scope && !oauth.scope.include?(scope)
45
+ halt oauth.no_scope! scope
46
+ end
47
+ else
48
+ halt oauth.no_access!
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ module Helpers
55
+ # Returns the OAuth helper.
56
+ #
57
+ # @return [Server::Helper]
58
+ def oauth
59
+ @oauth ||= Server::Helper.new(request, response)
60
+ end
61
+ end
62
+
63
+ def self.registered(base)
64
+ base.helpers Helpers
65
+ base.set :oauth, Server::Options.new
66
+ base.use Server, base.settings.oauth
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,24 @@
1
+ $: << File.dirname(__FILE__) + "/lib"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "tpitale-rack-oauth2-server"
5
+ spec.version = IO.read("VERSION")
6
+ spec.author = "Assaf Arkin"
7
+ spec.email = "assaf@labnotes.org"
8
+ spec.homepage = "http://github.com/flowtown/#{spec.name}"
9
+ spec.summary = "OAuth 2.0 Authorization Server as a Rack module for ActiveRecord"
10
+ spec.description = "Because you don't allow strangers into your app, and OAuth 2.0 is the new awesome."
11
+ spec.post_install_message = "To get started, run the command oauth2-server"
12
+
13
+ spec.files = Dir["{bin,lib,rails,test}/**/*", "CHANGELOG", "VERSION", "MIT-LICENSE", "README.rdoc", "Rakefile", "Gemfile", "*.gemspec"]
14
+ spec.executable = "oauth2-server"
15
+
16
+ spec.extra_rdoc_files = "README.rdoc", "CHANGELOG"
17
+ spec.rdoc_options = "--title", "tpitale-rack-oauth2-server #{spec.version}", "--main", "README.rdoc",
18
+ "--webcvs", "http://github.com/tpitale/rack-oauth2-server"
19
+ spec.license = "MIT"
20
+
21
+ spec.required_ruby_version = '>= 1.8.7'
22
+ spec.add_dependency "rack", "~>1.1"
23
+ spec.add_dependency "rails", ">= 2.3.11"
24
+ end
@@ -0,0 +1,11 @@
1
+ # Rails 2.x initialization.
2
+ require "rack/oauth2/rails"
3
+
4
+ config.extend ::Rack::OAuth2::Rails::Configuration
5
+ config.oauth.logger ||= Rails.logger
6
+ config.middleware.use ::Rack::OAuth2::Server, config.oauth
7
+ class ActionController::Base
8
+ helper ::Rack::OAuth2::Rails::Helpers
9
+ include ::Rack::OAuth2::Rails::Helpers
10
+ extend ::Rack::OAuth2::Rails::Filters
11
+ end
@@ -0,0 +1,228 @@
1
+ require "test/setup"
2
+
3
+ class AdminApiTest < Test::Unit::TestCase
4
+ module Helpers
5
+ def should_fail_authentication
6
+ should "respond with status 401 (Unauthorized)" do
7
+ assert_equal 401, last_response.status
8
+ end
9
+ end
10
+
11
+ def should_forbid_access
12
+ should "respond with status 403 (Forbidden)" do
13
+ assert_equal 403, last_response.status
14
+ end
15
+ end
16
+ end
17
+ extend Helpers
18
+
19
+
20
+ def without_scope
21
+ token = Server.token_for("Superman", client.id, "nobody")
22
+ header "Authorization", "OAuth #{token}"
23
+ end
24
+
25
+ def with_scope
26
+ token = Server.token_for("Superman", client.id, "oauth-admin")
27
+ header "Authorization", "OAuth #{token}"
28
+ end
29
+
30
+ def json
31
+ JSON.parse(last_response.body)
32
+ end
33
+
34
+
35
+ context "force SSL" do
36
+ setup do
37
+ Server::Admin.force_ssl = true
38
+ with_scope
39
+ end
40
+
41
+ context "HTTP request" do
42
+ setup { get "/oauth/admin/api/clients" }
43
+
44
+ should "redirect to HTTPS" do
45
+ assert_equal 302, last_response.status
46
+ assert_equal "https://example.org/oauth/admin/api/clients", last_response.location
47
+ end
48
+ end
49
+
50
+ context "HTTPS request" do
51
+ setup { get "https://example.org/oauth/admin/api/clients" }
52
+
53
+ should "serve request" do
54
+ assert_equal 200, last_response.status
55
+ assert Array === json["list"]
56
+ end
57
+ end
58
+
59
+ teardown { Server::Admin.force_ssl = false }
60
+ end
61
+
62
+
63
+ # -- /oauth/admin/api/clients
64
+
65
+ context "all clients" do
66
+ context "without authentication" do
67
+ setup { get "/oauth/admin/api/clients" }
68
+ should_fail_authentication
69
+ end
70
+
71
+ context "without scope" do
72
+ setup { without_scope ; get "/oauth/admin/api/clients" }
73
+ should_forbid_access
74
+ end
75
+
76
+ context "proper request" do
77
+ setup { with_scope ; get "/oauth/admin/api/clients" }
78
+ should "return OK" do
79
+ assert_equal 200, last_response.status
80
+ end
81
+ should "return JSON document" do
82
+ assert_equal "application/json", last_response.content_type.split(";").first
83
+ end
84
+ should "return list of clients" do
85
+ assert Array === json["list"]
86
+ end
87
+ should "return known scope" do
88
+ assert_equal %w{read write}, json["scope"]
89
+ end
90
+ end
91
+
92
+ context "client list" do
93
+ setup do
94
+ with_scope
95
+ get "/oauth/admin/api/clients"
96
+ @first = json["list"].first
97
+ end
98
+
99
+ should "provide client identifier" do
100
+ assert_equal client.id.to_s, @first["id"]
101
+ end
102
+ should "provide client secret" do
103
+ assert_equal client.secret, @first["secret"]
104
+ end
105
+ should "provide redirect URI" do
106
+ assert_equal client.redirect_uri, @first["redirectUri"]
107
+ end
108
+ should "provide display name" do
109
+ assert_equal client.display_name, @first["displayName"]
110
+ end
111
+ should "provide site URL" do
112
+ assert_equal client.link, @first["link"]
113
+ end
114
+ should "provide image URL" do
115
+ assert_equal client.image_url, @first["imageUrl"]
116
+ end
117
+ should "provide created timestamp" do
118
+ assert_equal client.created_at.to_i, @first["created"]
119
+ end
120
+ should "provide link to client resource"do
121
+ assert_equal ["/oauth/admin/api/client", client.id].join("/"), @first["url"]
122
+ end
123
+ should "provide link to revoke resource"do
124
+ assert_equal ["/oauth/admin/api/client", client.id, "revoke"].join("/"), @first["revoke"]
125
+ end
126
+ should "provide scope for client" do
127
+ assert_equal %w{oauth-admin read write}, @first["scope"]
128
+ end
129
+ should "tell if not revoked" do
130
+ assert @first["revoked"].nil?
131
+ end
132
+ end
133
+
134
+ context "revoked client" do
135
+ setup do
136
+ client.revoke!
137
+ with_scope
138
+ get "/oauth/admin/api/clients"
139
+ @first = json["list"].first
140
+ end
141
+
142
+ should "provide revoked timestamp" do
143
+ assert_equal client.revoked.to_i, @first["revoked"]
144
+ end
145
+ end
146
+
147
+ context "tokens" do
148
+ setup do
149
+ tokens = []
150
+ 1.upto(10).map do |days|
151
+ Timecop.travel -days*86400 do
152
+ tokens << Server.token_for("Superman#{days}", client.id)
153
+ end
154
+ end
155
+ # Revoke one token today (within past 7 days), one 10 days ago (beyond)
156
+ Timecop.travel -7 * 86400 do
157
+ Server.get_access_token(tokens[0]).revoke!
158
+ end
159
+ Server.get_access_token(tokens[1]).revoke!
160
+ with_scope ; get "/oauth/admin/api/clients"
161
+ end
162
+
163
+ should "return total number of tokens" do
164
+ assert_equal 11, json["tokens"]["total"]
165
+ end
166
+ should "return number of tokens created past week" do
167
+ assert_equal 7, json["tokens"]["week"]
168
+ end
169
+ should "return number of revoked token past week" do
170
+ assert_equal 1, json["tokens"]["revoked"]
171
+ end
172
+ end
173
+ end
174
+
175
+
176
+ # -- /oauth/admin/api/client/:id
177
+
178
+ context "single client" do
179
+ context "without authentication" do
180
+ setup { get "/oauth/admin/api/client/#{client.id}" }
181
+ should_fail_authentication
182
+ end
183
+
184
+ context "without scope" do
185
+ setup { without_scope ; get "/oauth/admin/api/client/#{client.id}" }
186
+ should_forbid_access
187
+ end
188
+
189
+ context "with scope" do
190
+ setup { with_scope ; get "/oauth/admin/api/client/#{client.id}" }
191
+
192
+ should "return OK" do
193
+ assert_equal 200, last_response.status
194
+ end
195
+ should "return JSON document" do
196
+ assert_equal "application/json", last_response.content_type.split(";").first
197
+ end
198
+ should "provide client identifier" do
199
+ assert_equal client.id.to_s, json["id"]
200
+ end
201
+ should "provide client secret" do
202
+ assert_equal client.secret, json["secret"]
203
+ end
204
+ should "provide redirect URI" do
205
+ assert_equal client.redirect_uri, json["redirectUri"]
206
+ end
207
+ should "provide display name" do
208
+ assert_equal client.display_name, json["displayName"]
209
+ end
210
+ should "provide site URL" do
211
+ assert_equal client.link, json["link"]
212
+ end
213
+ should "provide image URL" do
214
+ assert_equal client.image_url, json["imageUrl"]
215
+ end
216
+ should "provide created timestamp" do
217
+ assert_equal client.created_at.to_i, json["created"]
218
+ end
219
+ should "provide link to client resource"do
220
+ assert_equal ["/oauth/admin/api/client", client.id].join("/"), json["url"]
221
+ end
222
+ should "provide link to revoke resource"do
223
+ assert_equal ["/oauth/admin/api/client", client.id, "revoke"].join("/"), json["revoke"]
224
+ end
225
+ end
226
+ end
227
+
228
+ end