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,61 @@
1
+ module Rack
2
+ module OAuth2
3
+ class Server
4
+
5
+ # Authorization request. Represents request on behalf of client to access
6
+ # particular scope. Use this to keep state from incoming authorization
7
+ # request to grant/deny redirect.
8
+ class AuthRequest < ActiveRecord::Base
9
+ belongs_to :client, :class_name => 'Rack::OAuth2::Server::Client'
10
+
11
+ # Find AuthRequest from identifier.
12
+ # def find(request_id)
13
+ # id = BSON::ObjectId(request_id.to_s)
14
+ # Server.new_instance self, collection.find_one(id)
15
+ # rescue BSON::InvalidObjectId
16
+ # end
17
+
18
+ # Create a new authorization request. This holds state, so in addition
19
+ # to client ID and scope, we need to know the URL to redirect back to
20
+ # and any state value to pass back in that redirect.
21
+ def self.create(client, scope, redirect_uri, response_type, state)
22
+ scope = Utils.normalize_scope(scope) & Utils.normalize_scope(client.scope) # Only allowed scope
23
+
24
+ attributes = {
25
+ :code => Server.secure_random,
26
+ :client_id => client.id,
27
+ :scope => scope,
28
+ :redirect_uri => (client.redirect_uri || redirect_uri),
29
+ :response_type => response_type,
30
+ :state => state
31
+ }
32
+
33
+ super(attributes)
34
+ end
35
+
36
+ # Grant access to the specified identity.
37
+ def grant!(identity)
38
+ raise ArgumentError, "Must supply a identity" unless identity
39
+ return if revoked
40
+
41
+ if response_type == "code" # Requested authorization code
42
+ access_grant = AccessGrant.create(identity, client, scope, redirect_uri)
43
+ update_attributes(:grant_code => access_grant.code, :authorized_at => Time.now)
44
+ else # Requested access token
45
+ access_token = AccessToken.get_token_for(identity, client, scope)
46
+ update_attributes(:access_token => access_token.token, :authorized_at => Time.now)
47
+ end
48
+ end
49
+
50
+ # Deny access.
51
+ # this seems broken … ?
52
+ def deny!
53
+ # self.authorized_at = Time.now.to_i
54
+ # self.class.collection.update({ :_id=>id }, { :$set=>{ :authorized_at=>authorized_at } })
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,93 @@
1
+ module Rack
2
+ module OAuth2
3
+ class Server
4
+
5
+ class Client < ActiveRecord::Base
6
+ has_many :auth_requests, :dependent => :destroy
7
+ has_many :access_grants, :dependent => :destroy
8
+ has_many :access_tokens, :dependent => :destroy
9
+
10
+ validates_presence_of :display_name
11
+ validates_presence_of :link
12
+ validates_presence_of :code
13
+ validates_presence_of :secret
14
+
15
+ validates_uniqueness_of :display_name
16
+ validates_uniqueness_of :link
17
+ validates_uniqueness_of :code
18
+ validates_uniqueness_of :secret
19
+
20
+ before_validation_on_create :assign_code_and_secret
21
+
22
+ # Create a new client. Client provides the following properties:
23
+ # # :display_name -- Name to show (e.g. UberClient)
24
+ # # :link -- Link to client Web site (e.g. http://uberclient.dot)
25
+ # # :image_url -- URL of image to show alongside display name
26
+ # # :redirect_uri -- Registered redirect URI.
27
+ # # :scope -- List of names the client is allowed to request.
28
+ # # :notes -- Free form text.
29
+ #
30
+ # This method does not validate any of these fields, in fact, you're
31
+ # not required to set them, use them, or use them as suggested. Using
32
+ # them as suggested would result in better user experience. Don't ask
33
+ # how we learned that.
34
+ # def self.create(args)
35
+ # unless args[:redirect_uri].blank?
36
+ # redirect_uri = Server::Utils.parse_redirect_uri(args.delete(:redirect_uri)).to_s
37
+ # end
38
+ #
39
+ # scope = Server::Utils.normalize_scope(args[:scope])
40
+ # args.merge!({:redirect_uri => redirect_uri})
41
+ #
42
+ # if args[:id] && args[:secret]
43
+ # args[:code] = args.delete(:id)
44
+ # super(args)
45
+ # else
46
+ # args[:secret] = Server.secure_random
47
+ # super(args)
48
+ # end
49
+ # end
50
+
51
+ def assign_code_and_secret
52
+ self.code = Server.secure_random[0,20]
53
+ self.secret = Server.secure_random
54
+ end
55
+
56
+ def redirect_url=(url)
57
+ self[:redirect_uri] = Server::Utils.parse_redirect_uri(url).to_s
58
+ end
59
+
60
+ # Lookup client by ID, display name or URL.
61
+ def self.lookup(field)
62
+ find_by_id(field) || find_by_code(field) || find_by_display_name(field) || find_by_link(field)
63
+ end
64
+
65
+ # # Counts how many access tokens were granted.
66
+ # attr_reader :tokens_granted
67
+ # # Counts how many access tokens were revoked.
68
+ # attr_reader :tokens_revoked
69
+
70
+ # Revoke all authorization requests, access grants and access tokens for
71
+ # this client. Ward off the evil.
72
+ def revoke!
73
+ revoked_at = Time.now
74
+ update_attribute(:revoked, revoked_at)
75
+ # can we use the association here
76
+ AuthRequest.update_all(:revoked=>revoked_at, :client_id=>id)
77
+ AccessGrant.update_all(:revoked=>revoked_at, :client_id=>id)
78
+ AccessToken.update_all(:revoked=>revoked_at, :client_id=>id)
79
+ end
80
+
81
+ # def update(args)
82
+ # redirect_url = Server::Utils.parse_redirect_uri(args[:redirect_uri]).to_s unless args[:redirect_uri].blank?
83
+ # args.merge!({
84
+ # :redirect_url => redirect_url,
85
+ # :scope => Server::Utils.normalize_scope(args.delete(:scope))
86
+ # })
87
+ #
88
+ # update_attributes(args)
89
+ # end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,105 @@
1
+ require "rack/oauth2/server"
2
+
3
+ module Rack
4
+ module OAuth2
5
+
6
+ # Rails support.
7
+ #
8
+ # Adds oauth instance method that returns Rack::OAuth2::Helper, see there for
9
+ # more details.
10
+ #
11
+ # Adds oauth_required filter method. Use this filter with actions that require
12
+ # authentication, and with actions 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 config/environment.rb
19
+ # require "rack/oauth2/rails"
20
+ #
21
+ # Rails::Initializer.run do |config|
22
+ # config.oauth[:scope] = %w{read write}
23
+ # config.oauth[:authenticator] = lambda do |username, password|
24
+ # User.authenticated username, password
25
+ # end
26
+ # . . .
27
+ # end
28
+ #
29
+ # @example app/controllers/my_controller.rb
30
+ # class MyController < ApplicationController
31
+ #
32
+ # oauth_required :only=>:show
33
+ # oauth_required :only=>:update, :scope=>"write"
34
+ #
35
+ # . . .
36
+ #
37
+ # protected
38
+ # def current_user
39
+ # @current_user ||= User.find(oauth.identity) if oauth.authenticated?
40
+ # end
41
+ # end
42
+ #
43
+ # @see Helpers
44
+ # @see Filters
45
+ # @see Configuration
46
+ module Rails
47
+
48
+ # Helper methods available to controller instance and views.
49
+ module Helpers
50
+ # Returns the OAuth helper.
51
+ #
52
+ # @return [Server::Helper]
53
+ def oauth
54
+ @oauth ||= Rack::OAuth2::Server::Helper.new(request, response)
55
+ end
56
+
57
+ # Filter that denies access if the request is not authenticated. If you
58
+ # do not specify a scope, the class method oauth_required will use this
59
+ # filter; you can set the filter in a parent class and skip it in child
60
+ # classes that need special handling.
61
+ def oauth_required
62
+ head oauth.no_access! unless oauth.authenticated?
63
+ end
64
+ end
65
+
66
+ # Filter methods available in controller.
67
+ module Filters
68
+
69
+ # Adds before filter to require authentication on all the listed paths.
70
+ # Use the :scope option if client must also have access to that scope.
71
+ #
72
+ # @param [Hash] options Accepts before_filter options like :only and
73
+ # :except, and the :scope option.
74
+ def oauth_required(options = {})
75
+ if scope = options.delete(:scope)
76
+ before_filter options do |controller|
77
+ if controller.oauth.authenticated?
78
+ if !controller.oauth.scope.include?(scope)
79
+ controller.send :head, controller.oauth.no_scope!(scope)
80
+ end
81
+ else
82
+ controller.send :head, controller.oauth.no_access!
83
+ end
84
+ end
85
+ else
86
+ before_filter :oauth_required, options
87
+ end
88
+ end
89
+ end
90
+
91
+ # Configuration methods available in config/environment.rb.
92
+ module Configuration
93
+
94
+ # Rack module settings.
95
+ #
96
+ # @return [Hash] Settings
97
+ def oauth
98
+ @oauth ||= Server::Options.new
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,458 @@
1
+ require "rack"
2
+ require "rack/oauth2/models"
3
+ require "rack/oauth2/server/errors"
4
+ require "rack/oauth2/server/utils"
5
+ require "rack/oauth2/server/helper"
6
+
7
+
8
+ module Rack
9
+ module OAuth2
10
+
11
+ # Implements an OAuth 2 Authorization Server, based on http://tools.ietf.org/html/draft-ietf-oauth-v2-10
12
+ class Server
13
+
14
+ # Same as gem version number.
15
+ VERSION = IO.read(::File.expand_path("../../../VERSION", ::File.dirname(__FILE__))).strip
16
+
17
+ class << self
18
+ # Return AuthRequest from authorization request handle.
19
+ #
20
+ # @param [String] authorization Authorization handle (e.g. from
21
+ # oauth.authorization)
22
+ # @return [AuthReqeust]
23
+ def get_auth_request(authorization)
24
+ AuthRequest.find_by_code(authorization)
25
+ end
26
+
27
+ # Returns Client from client identifier.
28
+ #
29
+ # @param [String] client_id Client identifier (e.g. from oauth.client.id)
30
+ # @return [Client]
31
+ def get_client(client_id)
32
+ p client_id
33
+ Client.find_by_code(client_id)
34
+ end
35
+
36
+ # Registers and returns a new Client. Can also be used to update
37
+ # existing client registration, by passing identifier (and secret) of
38
+ # existing client record. That way, your setup script can create a new
39
+ # client application and run repeatedly without fail.
40
+ #
41
+ # @param [Hash] args Arguments for registering client application
42
+ # @option args [String] :id Client identifier. Use this to update
43
+ # existing client registration (in combination wih secret)
44
+ # @option args [String] :secret Client secret. Use this to update
45
+ # existing client registration.
46
+ # @option args [String] :display_name Name to show when authorizing
47
+ # access (e.g. "My Awesome Application")
48
+ # @option args [String] link Link to client application's Web site
49
+ # @option args [String] image_url URL of image to show alongside display
50
+ # name.
51
+ # @option args [String] redirect_uri Redirect URL: authorization
52
+ # requests for this client will always redirect back to this URL.
53
+ # @option args [Array] scope Scope that client application can request
54
+ # (list of names).
55
+ # @option args [Array] notes Free form text, for internal use.
56
+ #
57
+ # @example Registering new client application
58
+ # Server.register :display_name=>"My Application",
59
+ # :link=>"http://example.com", :scope=>%w{read write},
60
+ # :redirect_uri=>"http://example.com/oauth/callback"
61
+ # @example Migration using configuration file
62
+ # config = YAML.load_file(Rails.root + "config/oauth.yml")
63
+ # Server.register config["id"], config["secret"],
64
+ # :display_name=>"My Application", :link=>"http://example.com",
65
+ # :scope=>config["scope"],
66
+ # :redirect_uri=>"http://example.com/oauth/callback"
67
+
68
+ def register(args)
69
+ if args[:id] && args[:secret] && (client = get_client(args[:id]))
70
+ fail "Client secret does not match" unless client.secret == args[:secret]
71
+ client.update args
72
+ else
73
+ Client.create(args)
74
+ end
75
+ end
76
+
77
+ # Creates and returns a new access grant. Actually, returns only the
78
+ # authorization code which you can turn into an access token by
79
+ # making a request to /oauth/access_token.
80
+ #
81
+ # @param [String,Integer] identity User ID, account ID, etc
82
+ # @param [String] client_id Client identifier
83
+ # @param [Array, nil] scope Array of string, nil if you want 'em all
84
+ # @param [Integer, nil] expires How many seconds before access grant
85
+ # expires (default to 5 minutes)
86
+ # @return [String] Access grant authorization code
87
+ def access_grant(identity, client_id, scope = nil, expires = nil)
88
+ client = get_client(client_id) or fail "No such client"
89
+ AccessGrant.create(identity, client, scope || client.scope, nil, expires).code
90
+ end
91
+
92
+ # Returns AccessToken from token.
93
+ #
94
+ # @param [String] token Access token (e.g. from oauth.access_token)
95
+ # @return [AccessToken]
96
+ def get_access_token(token)
97
+ AccessToken.from_token(token)
98
+ end
99
+
100
+ # Returns AccessToken for the specified identity, client application and
101
+ # scope. You can use this method to request existing access token, new
102
+ # token generated if one does not already exists.
103
+ #
104
+ # @param [String,Integer] identity Identity, e.g. user ID, account ID
105
+ # @param [String] client_id Client application identifier
106
+ # @param [Array, nil] scope Array of names, nil if you want 'em all
107
+ # @return [String] Access token
108
+ def token_for(identity, client_id, scope = nil)
109
+ client = get_client(client_id) or fail "No such client"
110
+ AccessToken.get_token_for(identity, client, scope || client.scope).token
111
+ end
112
+
113
+ # Returns all AccessTokens for an identity.
114
+ #
115
+ # @param [String] identity Identity, e.g. user ID, account ID
116
+ # @return [Array<AccessToken>]
117
+ def list_access_tokens(identity)
118
+ AccessToken.from_identity(identity)
119
+ end
120
+
121
+ end
122
+
123
+ # Options are:
124
+ # - :access_token_path -- Path for requesting access token. By convention
125
+ # defaults to /oauth/access_token.
126
+ # - :authenticator -- For username/password authorization. A block that
127
+ # receives the credentials and returns identity string (e.g. user ID) or
128
+ # nil.
129
+ # - :authorization_types -- Array of supported authorization types.
130
+ # Defaults to ["code", "token"], and you can change it to just one of
131
+ # these names.
132
+ # - :authorize_path -- Path for requesting end-user authorization. By
133
+ # convention defaults to /oauth/authorize.
134
+ # - :database -- Mongo::DB instance.
135
+ # - :host -- Only check requests sent to this host.
136
+ # - :path -- Only check requests for resources under this path.
137
+ # - :param_authentication -- If true, supports authentication using
138
+ # query/form parameters.
139
+ # - :realm -- Authorization realm that will show up in 401 responses.
140
+ # Defaults to use the request host name.
141
+ # - :logger -- The logger to use. Under Rails, defaults to use the Rails
142
+ # logger. Will use Rack::Logger if available.
143
+ #
144
+ # Authenticator is a block that receives either two or four parameters.
145
+ # The first two are username and password. The other two are the client
146
+ # identifier and scope. It authenticated, it returns an identity,
147
+ # otherwise it can return nil or false. For example:
148
+ # oauth.authenticator = lambda do |username, password|
149
+ # user = User.find_by_username(username)
150
+ # user if user && user.authenticated?(password)
151
+ # end
152
+ Options = Struct.new(:access_token_path, :authenticator, :authorization_types,
153
+ :authorize_path, :database, :host, :param_authentication, :path, :realm, :logger)
154
+
155
+ def initialize(app, options = Options.new, &authenticator)
156
+ @app = app
157
+ @options = options
158
+ @options.authenticator ||= authenticator
159
+ @options.access_token_path ||= "/oauth/access_token"
160
+ @options.authorize_path ||= "/oauth/authorize"
161
+ @options.authorization_types ||= %w{code token}
162
+ @options.param_authentication ||= false
163
+ end
164
+
165
+ # @see Options
166
+ attr_reader :options
167
+
168
+ def call(env)
169
+ request = OAuthRequest.new(env)
170
+ return @app.call(env) if options.host && options.host != request.host
171
+ return @app.call(env) if options.path && request.path.index(options.path) != 0
172
+
173
+ begin
174
+ logger = options.logger || env["rack.logger"]
175
+
176
+ # 3. Obtaining End-User Authorization
177
+ # Flow starts here.
178
+ return request_authorization(request, logger) if request.path == options.authorize_path
179
+ # 4. Obtaining an Access Token
180
+ return respond_with_access_token(request, logger) if request.path == options.access_token_path
181
+
182
+ # 5. Accessing a Protected Resource
183
+ if request.authorization
184
+ # 5.1.1. The Authorization Request Header Field
185
+ token = request.credentials if request.oauth?
186
+ elsif options.param_authentication && !request.GET["oauth_verifier"] # Ignore OAuth 1.0 callbacks
187
+ # 5.1.2. URI Query Parameter
188
+ # 5.1.3. Form-Encoded Body Parameter
189
+ token = request.GET["oauth_token"] || request.POST["oauth_token"]
190
+ token ||= request.GET['access_token'] || request.POST['access_token']
191
+ end
192
+
193
+ if token
194
+ begin
195
+ access_token = AccessToken.from_token(token)
196
+ raise InvalidTokenError if access_token.nil? || access_token.revoked
197
+ raise ExpiredTokenError if access_token.expires_at && access_token.expires_at <= Time.now.to_i
198
+ request.env["oauth.access_token"] = token
199
+
200
+ request.env["oauth.identity"] = access_token.identity
201
+ access_token.access!
202
+ logger.info "RO2S: Authorized #{access_token.identity}" if logger
203
+ rescue OAuthError=>error
204
+ # 5.2. The WWW-Authenticate Response Header Field
205
+ logger.info "RO2S: HTTP authorization failed #{error.code}" if logger
206
+ return unauthorized(request, error)
207
+ rescue =>ex
208
+ logger.info "RO2S: HTTP authorization failed #{ex.message}" if logger
209
+ return unauthorized(request)
210
+ end
211
+
212
+ # We expect application to use 403 if request has insufficient scope,
213
+ # and return appropriate WWW-Authenticate header.
214
+ response = @app.call(env)
215
+ if response[0] == 403
216
+ scope = Utils.normalize_scope(response[1]["oauth.no_scope"])
217
+ challenge = 'OAuth realm="%s", error="insufficient_scope", scope="%s"' % [(options.realm || request.host), scope]
218
+ response[1]["WWW-Authenticate"] = challenge
219
+ return response
220
+ else
221
+ return response
222
+ end
223
+ else
224
+ response = @app.call(env)
225
+ if response[1] && response[1].delete("oauth.no_access")
226
+ logger.debug "RO2S: Unauthorized request" if logger
227
+ # OAuth access required.
228
+ return unauthorized(request)
229
+ elsif response[1] && response[1]["oauth.authorization"]
230
+ # 3. Obtaining End-User Authorization
231
+ # Flow ends here.
232
+ return authorization_response(response, logger)
233
+ else
234
+ return response
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ protected
241
+
242
+ # Get here for authorization request. Check the request parameters and
243
+ # redirect with an error if we find any issue. Otherwise, create a new
244
+ # authorization request, set in oauth.request and pass control to the
245
+ # application.
246
+ def request_authorization(request, logger)
247
+ state = request.GET["state"]
248
+ begin
249
+
250
+ if request.GET["authorization"]
251
+ auth_request = self.class.get_auth_request(request.GET["authorization"]) rescue nil
252
+ if !auth_request || auth_request.revoked
253
+ logger.error "RO2S: Invalid authorization request #{auth_request}" if logger
254
+ return bad_request("Invalid authorization request")
255
+ end
256
+ response_type = auth_request.response_type # Needed for error handling
257
+ client = auth_request.client
258
+ # Pass back to application, watch for 403 (deny!)
259
+ logger.info "RO2S: Client #{client.display_name} requested #{auth_request.response_type} with scope #{auth_request.scope}" if logger
260
+ request.env["oauth.authorization"] = auth_request.code
261
+ response = @app.call(request.env)
262
+ raise AccessDeniedError if response[0] == 403
263
+ return response
264
+
265
+ else
266
+
267
+ # 3. Obtaining End-User Authorization
268
+ begin
269
+ redirect_uri = Utils.parse_redirect_uri(request.GET["redirect_uri"])
270
+ rescue InvalidRequestError=>error
271
+ logger.error "RO2S: Authorization request with invalid redirect_uri: #{request.GET["redirect_uri"]} #{error.message}" if logger
272
+ return bad_request(error.message)
273
+ end
274
+
275
+ # 3. Obtaining End-User Authorization
276
+ response_type = request.GET["response_type"].to_s # Need this first, for error handling
277
+ client = get_client(request, :dont_authenticate => true)
278
+ raise RedirectUriMismatchError unless client.redirect_uri.nil? || client.redirect_uri == redirect_uri.to_s
279
+ raise UnsupportedResponseTypeError unless options.authorization_types.include?(response_type)
280
+ requested_scope = Utils.normalize_scope(request.GET["scope"])
281
+ allowed_scope = Utils.normalize_scope(client.scope)
282
+ raise InvalidScopeError unless (requested_scope - allowed_scope).empty?
283
+ # Create object to track authorization request and let application
284
+ # handle the rest.
285
+ auth_request = AuthRequest.create(client, requested_scope, redirect_uri.to_s, response_type, state)
286
+ uri = URI.parse(request.url)
287
+ uri.query = "authorization=#{auth_request.code}"
288
+ return redirect_to(uri, 303)
289
+ end
290
+ rescue OAuthError=>error
291
+ logger.error "RO2S: Authorization request error #{error.code}: #{error.message}" if logger
292
+ params = { :error=>error.code, :error_description=>error.message, :state=>state }
293
+ if response_type == "token"
294
+ redirect_uri.fragment = Rack::Utils.build_query(params)
295
+ else # response type is code, or invalid
296
+ params = Rack::Utils.parse_query(redirect_uri.query).merge(params)
297
+ redirect_uri.query = Rack::Utils.build_query(params)
298
+ end
299
+ return redirect_to(redirect_uri)
300
+ end
301
+ end
302
+
303
+ # Get here on completion of the authorization. Authorization response in
304
+ # oauth.response either grants or denies authroization. In either case, we
305
+ # redirect back with the proper response.
306
+ def authorization_response(response, logger)
307
+ status, headers, body = response
308
+ auth_request = self.class.get_auth_request(headers["oauth.authorization"])
309
+ redirect_uri = URI.parse(auth_request.redirect_uri)
310
+ if status == 403
311
+ auth_request.deny!
312
+ else
313
+ auth_request.grant! headers["oauth.identity"]
314
+ end
315
+ # 3.1. Authorization Response
316
+ if auth_request.response_type == "code"
317
+ if auth_request.grant_code
318
+ logger.info "RO2S: Client #{auth_request.client_id} granted access code #{auth_request.grant_code}" if logger
319
+ params = { :code=>auth_request.grant_code, :scope=>auth_request.scope, :state=>auth_request.state }
320
+ else
321
+ logger.info "RO2S: Client #{auth_request.client_id} denied authorization" if logger
322
+ params = { :error=>:access_denied, :state=>auth_request.state }
323
+ end
324
+ params = Rack::Utils.parse_query(redirect_uri.query).merge(params)
325
+ redirect_uri.query = Rack::Utils.build_query(params)
326
+ else # response type if token
327
+ if auth_request.access_token
328
+ logger.info "RO2S: Client #{auth_request.client_id} granted access token #{auth_request.access_token}" if logger
329
+ params = { :access_token=>auth_request.access_token, :scope=>auth_request.scope, :state=>auth_request.state }
330
+ else
331
+ logger.info "RO2S: Client #{auth_request.client_id} denied authorization" if logger
332
+ params = { :error=>:access_denied, :state=>auth_request.state }
333
+ end
334
+ redirect_uri.fragment = Rack::Utils.build_query(params)
335
+ end
336
+ return redirect_to(redirect_uri)
337
+ end
338
+
339
+ # 4. Obtaining an Access Token
340
+ def respond_with_access_token(request, logger)
341
+ return [405, { "Content-Type"=>"application/json" }, ["POST only"]] unless request.post?
342
+ # 4.2. Access Token Response
343
+ begin
344
+ client = get_client(request)
345
+ case request.POST["grant_type"]
346
+ when "none"
347
+ # 4.1 "none" access grant type (i.e. two-legged OAuth flow)
348
+ requested_scope = request.POST["scope"] ? Utils.normalize_scope(request.POST["scope"]) : Utils.normalize_scope(client.scope)
349
+ access_token = AccessToken.create_token_for(client, requested_scope)
350
+ when "authorization_code"
351
+ # 4.1.1. Authorization Code
352
+ grant = AccessGrant.from_code(request.POST["code"])
353
+ p grant
354
+ raise InvalidGrantError, "Wrong client" unless grant && client == grant.client
355
+ unless client.redirect_uri.nil? || client.redirect_uri.to_s.empty?
356
+ raise InvalidGrantError, "Wrong redirect URI" unless grant.redirect_uri == Utils.parse_redirect_uri(request.POST["redirect_uri"]).to_s
357
+ end
358
+ raise InvalidGrantError, "This access grant expired" if grant.expires_at && grant.expires_at <= Time.now.to_i
359
+ access_token = grant.authorize!
360
+ when "password"
361
+ raise UnsupportedGrantType unless options.authenticator
362
+ # 4.1.2. Resource Owner Password Credentials
363
+ username, password = request.POST.values_at("username", "password")
364
+ raise InvalidGrantError, "Missing username/password" unless username && password
365
+ requested_scope = request.POST["scope"] ? Utils.normalize_scope(request.POST["scope"]) : Utils.normalize_scope(client.scope)
366
+ allowed_scope = Utils.normalize_scope(client.scope)
367
+ raise InvalidScopeError unless (requested_scope - allowed_scope).empty?
368
+ args = [username, password]
369
+ args << client.id << requested_scope unless options.authenticator.arity == 2
370
+ identity = options.authenticator.call(*args)
371
+ raise InvalidGrantError, "Username/password do not match" unless identity
372
+ access_token = AccessToken.get_token_for(identity, client, requested_scope)
373
+ else
374
+ raise UnsupportedGrantType
375
+ end
376
+ logger.info "RO2S: Access token #{access_token.token} granted to client #{client.display_name}, identity #{access_token.identity}" if logger
377
+ response = { :access_token=>access_token.token }
378
+ response[:scope] = access_token.scope
379
+ return [200, { "Content-Type"=>"application/json", "Cache-Control"=>"no-store" }, [response.to_json]]
380
+ # 4.3. Error Response
381
+ rescue OAuthError=>error
382
+ logger.error "RO2S: Access token request error #{error.code}: #{error.message}" if logger
383
+ return unauthorized(request, error) if InvalidClientError === error && request.basic?
384
+ return [400, { "Content-Type"=>"application/json", "Cache-Control"=>"no-store" },
385
+ [{ :error=>error.code, :error_description=>error.message }.to_json]]
386
+ end
387
+ end
388
+
389
+ # Returns client from request based on credentials. Raises
390
+ # InvalidClientError if client doesn't exist or secret doesn't match.
391
+ def get_client(request, options={})
392
+ # 2.1 Client Password Credentials
393
+ if request.basic?
394
+ client_id, client_secret = request.credentials
395
+ elsif request.post?
396
+ client_id, client_secret = request.POST.values_at("client_id", "client_secret")
397
+ else
398
+ client_id, client_secret = request.GET.values_at("client_id", "client_secret")
399
+ end
400
+ client = self.class.get_client(client_id)
401
+ raise InvalidClientError if !client
402
+ unless options[:dont_authenticate]
403
+ raise InvalidClientError unless client.secret == client_secret
404
+ end
405
+ raise InvalidClientError if client.revoked
406
+ return client
407
+ end
408
+
409
+ # Rack redirect response.
410
+ # The argument is typically a URI object, and the status should be a 302 or 303.
411
+ def redirect_to(uri, status = 302)
412
+ return [status, { "Content-Type"=>"text/plain", "Location"=>uri.to_s }, ["You are being redirected"]]
413
+ end
414
+
415
+ def bad_request(message)
416
+ return [400, { "Content-Type"=>"text/plain" }, [message]]
417
+ end
418
+
419
+ # Returns WWW-Authenticate header.
420
+ def unauthorized(request, error = nil)
421
+ challenge = 'OAuth realm="%s"' % (options.realm || request.host)
422
+ challenge << ', error="%s", error_description="%s"' % [error.code, error.message] if error
423
+ return [401, { "WWW-Authenticate"=>challenge }, [error && error.message || ""]]
424
+ end
425
+
426
+ # Wraps Rack::Request to expose Basic and OAuth authentication
427
+ # credentials.
428
+ class OAuthRequest < Rack::Request
429
+
430
+ AUTHORIZATION_KEYS = %w{HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION}
431
+
432
+ # Returns authorization header.
433
+ def authorization
434
+ @authorization ||= AUTHORIZATION_KEYS.inject(nil) { |auth, key| auth || @env[key] }
435
+ end
436
+
437
+ # True if authentication scheme is OAuth.
438
+ def oauth?
439
+ authorization[/^oauth/i] if authorization
440
+ end
441
+
442
+ # True if authentication scheme is Basic.
443
+ def basic?
444
+ authorization[/^basic/i] if authorization
445
+ end
446
+
447
+ # If Basic auth, returns username/password, if OAuth, returns access
448
+ # token.
449
+ def credentials
450
+ basic? ? authorization.gsub(/\n/, "").split[1].unpack("m*").first.split(/:/, 2) :
451
+ oauth? ? authorization.gsub(/\n/, "").split[1] : nil
452
+ end
453
+ end
454
+
455
+ end
456
+
457
+ end
458
+ end