tpitale-rack-oauth2-server 2.2.1

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