rack-oauth2-server 1.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/Gemfile +17 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +423 -0
- data/Rakefile +60 -0
- data/lib/rack-oauth2-server.rb +1 -0
- data/lib/rack/oauth2/models.rb +37 -0
- data/lib/rack/oauth2/models/access_grant.rb +75 -0
- data/lib/rack/oauth2/models/access_token.rb +65 -0
- data/lib/rack/oauth2/models/auth_request.rb +88 -0
- data/lib/rack/oauth2/models/client.rb +73 -0
- data/lib/rack/oauth2/rails.rb +105 -0
- data/lib/rack/oauth2/server.rb +312 -0
- data/lib/rack/oauth2/server/errors.rb +97 -0
- data/lib/rack/oauth2/server/helper.rb +142 -0
- data/lib/rack/oauth2/server/utils.rb +24 -0
- data/lib/rack/oauth2/server/version.rb +9 -0
- data/lib/rack/oauth2/sinatra.rb +71 -0
- data/rack-oauth2-server.gemspec +25 -0
- data/test/access_grant_test.rb +216 -0
- data/test/access_token_test.rb +237 -0
- data/test/authorization_test.rb +267 -0
- data/test/rails/app/controllers/api_controller.rb +40 -0
- data/test/rails/app/controllers/application_controller.rb +4 -0
- data/test/rails/app/controllers/oauth_controller.rb +14 -0
- data/test/rails/config/environment.rb +12 -0
- data/test/rails/config/environments/test.rb +0 -0
- data/test/rails/config/routes.rb +13 -0
- data/test/rails/log/test.log +14710 -0
- data/test/setup.rb +73 -0
- data/test/sinatra/my_app.rb +67 -0
- metadata +148 -0
@@ -0,0 +1,312 @@
|
|
1
|
+
require "rack/oauth2/models"
|
2
|
+
require "rack/oauth2/server/errors"
|
3
|
+
require "rack/oauth2/server/utils"
|
4
|
+
require "rack/oauth2/server/helper"
|
5
|
+
require "rack/oauth2/server/version"
|
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
|
+
class << self
|
15
|
+
# Return AuthRequest from authorization request handle.
|
16
|
+
def get_auth_request(authorization)
|
17
|
+
AuthRequest.find(authorization)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns Client from client identifier.
|
21
|
+
def get_client(client_id)
|
22
|
+
Client.find(client_id)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns AccessToken from token.
|
26
|
+
def get_access_token(token)
|
27
|
+
AccessToken.from_token(token)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns all AccessTokens for a resource.
|
31
|
+
def list_access_tokens(resource)
|
32
|
+
AccessToken.from_resource(resource)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(app, options = {}, &authenticator)
|
37
|
+
@app = app
|
38
|
+
@options = options
|
39
|
+
@options[:authenticator] ||= authenticator
|
40
|
+
@options[:access_token_path] ||= "/oauth/access_token"
|
41
|
+
@options[:authorize_path] ||= "/oauth/authorize"
|
42
|
+
@options[:authorization_types] ||= %w{code token}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Options are:
|
46
|
+
# - :access_token_path -- Path for requesting access token. By convention
|
47
|
+
# defaults to /oauth/access_token.
|
48
|
+
# - :authenticator -- For username/password authorization. A block that
|
49
|
+
# receives the credentials and returns resource string (e.g. user ID) or
|
50
|
+
# nil.
|
51
|
+
# - :authorization_types -- Array of supported authorization types.
|
52
|
+
# Defaults to ["code", "token"], and you can change it to just one of
|
53
|
+
# these names.
|
54
|
+
# - :authorize_path -- Path for requesting end-user authorization. By
|
55
|
+
# convention defaults to /oauth/authorize.
|
56
|
+
# - :database -- Mongo::DB instance.
|
57
|
+
# - :realm -- Authorization realm that will show up in 401 responses.
|
58
|
+
# Defaults to use the request host name.
|
59
|
+
# - :scopes -- Array listing all supported scopes, e.g. %w{read write}.
|
60
|
+
# - :logger -- The logger to use. Under Rails, defaults to use the Rails
|
61
|
+
# logger. Will use Rack::Logger if available.
|
62
|
+
attr_reader :options
|
63
|
+
|
64
|
+
def call(env)
|
65
|
+
# Use options[:database] if specified.
|
66
|
+
org_database, Server.database = Server.database, options[:database] || Server.database
|
67
|
+
logger = options[:logger] || env["rack.logger"]
|
68
|
+
request = OAuthRequest.new(env)
|
69
|
+
|
70
|
+
# 3. Obtaining End-User Authorization
|
71
|
+
# Flow starts here.
|
72
|
+
return request_authorization(request, logger) if request.path == options[:authorize_path]
|
73
|
+
# 4. Obtaining an Access Token
|
74
|
+
return respond_with_access_token(request, logger) if request.path == options[:access_token_path]
|
75
|
+
|
76
|
+
# 5. Accessing a Protected Resource
|
77
|
+
if request.authorization
|
78
|
+
# 5.1.1. The Authorization Request Header Field
|
79
|
+
token = request.credentials if request.oauth?
|
80
|
+
else
|
81
|
+
# 5.1.2. URI Query Parameter
|
82
|
+
# 5.1.3. Form-Encoded Body Parameter
|
83
|
+
token = request.GET["oauth_token"] || request.POST["oauth_token"]
|
84
|
+
end
|
85
|
+
|
86
|
+
if token
|
87
|
+
begin
|
88
|
+
access_token = AccessToken.from_token(token)
|
89
|
+
raise InvalidTokenError if access_token.nil? || access_token.revoked
|
90
|
+
raise ExpiredTokenError if access_token.expires_at && access_token.expires_at <= Time.now.utc
|
91
|
+
request.env["oauth.access_token"] = token
|
92
|
+
request.env["oauth.resource"] = access_token.resource
|
93
|
+
logger.info "Authorized #{access_token.resource}" if logger
|
94
|
+
rescue Error=>error
|
95
|
+
# 5.2. The WWW-Authenticate Response Header Field
|
96
|
+
logger.info "HTTP authorization failed #{error.code}" if logger
|
97
|
+
return unauthorized(request, error)
|
98
|
+
rescue =>ex
|
99
|
+
logger.info "HTTP authorization failed #{ex.message}" if logger
|
100
|
+
return unauthorized(request)
|
101
|
+
end
|
102
|
+
|
103
|
+
# We expect application to use 403 if request has insufficient scope,
|
104
|
+
# and return appropriate WWW-Authenticate header.
|
105
|
+
response = @app.call(env)
|
106
|
+
if response[0] == 403
|
107
|
+
scope = response[1]["oauth.no_scope"] || ""
|
108
|
+
scope = scope.join(" ") if scope.respond_to?(:join)
|
109
|
+
challenge = 'OAuth realm="%s", error="insufficient_scope", scope="%s"' % [(options[:realm] || request.host), scope]
|
110
|
+
return [403, { "WWW-Authenticate"=>challenge }, []]
|
111
|
+
else
|
112
|
+
return response
|
113
|
+
end
|
114
|
+
else
|
115
|
+
response = @app.call(env)
|
116
|
+
if response[1] && response[1]["oauth.no_access"]
|
117
|
+
# OAuth access required.
|
118
|
+
return unauthorized(request)
|
119
|
+
elsif response[1] && response[1]["oauth.authorization"]
|
120
|
+
# 3. Obtaining End-User Authorization
|
121
|
+
# Flow ends here.
|
122
|
+
return authorization_response(response, logger)
|
123
|
+
else
|
124
|
+
return response
|
125
|
+
end
|
126
|
+
end
|
127
|
+
ensure
|
128
|
+
Server.database = org_database
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
# Get here for authorization request. Check the request parameters and
|
134
|
+
# redirect with an error if we find any issue. Otherwise, create a new
|
135
|
+
# authorization request, set in oauth.request and pass control to the
|
136
|
+
# application.
|
137
|
+
def request_authorization(request, logger)
|
138
|
+
# 3. Obtaining End-User Authorization
|
139
|
+
begin
|
140
|
+
redirect_uri = Utils.parse_redirect_uri(request.GET["redirect_uri"])
|
141
|
+
rescue InvalidRequestError=>error
|
142
|
+
logger.error "Authorization request with invalid redirect_uri: #{request.GET["redirect_uri"]} #{error.message}" if logger
|
143
|
+
return bad_request(error.message)
|
144
|
+
end
|
145
|
+
state = request.GET["state"]
|
146
|
+
|
147
|
+
begin
|
148
|
+
# 3. Obtaining End-User Authorization
|
149
|
+
client = get_client(request)
|
150
|
+
raise RedirectUriMismatchError unless client.redirect_uri.nil? || client.redirect_uri == redirect_uri.to_s
|
151
|
+
requested_scope = request.GET["scope"].to_s.split.uniq.join(" ")
|
152
|
+
response_type = request.GET["response_type"].to_s
|
153
|
+
raise UnsupportedResponseTypeError unless options[:authorization_types].include?(response_type)
|
154
|
+
if scopes = options[:scopes]
|
155
|
+
allowed_scope = scopes.respond_to?(:all?) ? scopes : scopes.split
|
156
|
+
raise InvalidScopeError unless requested_scope.split.all? { |v| allowed_scope.include?(v) }
|
157
|
+
end
|
158
|
+
# Create object to track authorization request and let application
|
159
|
+
# handle the rest.
|
160
|
+
auth_request = AuthRequest.create(client.id, requested_scope, redirect_uri.to_s, response_type, state)
|
161
|
+
request.env["oauth.authorization"] = auth_request.id.to_s
|
162
|
+
logger.info "Request #{auth_request.id}: Client #{client.display_name} requested #{response_type} with scope #{requested_scope}" if logger
|
163
|
+
return @app.call(request.env)
|
164
|
+
rescue Error=>error
|
165
|
+
logger.error "Authorization request error: #{error.code} #{error.message}" if logger
|
166
|
+
params = Rack::Utils.parse_query(redirect_uri.query).merge(:error=>error.code, :error_description=>error.message, :state=>state)
|
167
|
+
redirect_uri.query = Rack::Utils.build_query(params)
|
168
|
+
return redirect_to(redirect_uri)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Get here on completion of the authorization. Authorization response in
|
173
|
+
# oauth.response either grants or denies authroization. In either case, we
|
174
|
+
# redirect back with the proper response.
|
175
|
+
def authorization_response(response, logger)
|
176
|
+
status, headers, body = response
|
177
|
+
auth_request = self.class.get_auth_request(headers["oauth.authorization"])
|
178
|
+
redirect_uri = URI.parse(auth_request.redirect_uri)
|
179
|
+
if status == 401
|
180
|
+
auth_request.deny!
|
181
|
+
else
|
182
|
+
auth_request.grant! headers["oauth.resource"]
|
183
|
+
end
|
184
|
+
# 3.1. Authorization Response
|
185
|
+
if auth_request.response_type == "code" && auth_request.grant_code
|
186
|
+
logger.info "Request #{auth_request.id}: Client #{auth_request.client_id} granted access code #{auth_request.grant_code}" if logger
|
187
|
+
params = { :code=>auth_request.grant_code, :scope=>auth_request.scope, :state=>auth_request.state }
|
188
|
+
params = Rack::Utils.parse_query(redirect_uri.query).merge(params)
|
189
|
+
redirect_uri.query = Rack::Utils.build_query(params)
|
190
|
+
return redirect_to(redirect_uri)
|
191
|
+
elsif auth_request.response_type == "token" && auth_request.access_token
|
192
|
+
logger.info "Request #{auth_request.id}: Client #{auth_request.client_id} granted access token #{auth_request.access_token}" if logger
|
193
|
+
params = { :access_token=>auth_request.access_token, :scope=>auth_request.scope, :state=>auth_request.state }
|
194
|
+
redirect_uri.fragment = Rack::Utils.build_query(params)
|
195
|
+
return redirect_to(redirect_uri)
|
196
|
+
else
|
197
|
+
logger.info "Request #{auth_request.id}: Client #{auth_request.client_id} denied authorization" if logger
|
198
|
+
params = Rack::Utils.parse_query(redirect_uri.query).merge(:error=>:access_denied, :state=>auth_request.state)
|
199
|
+
redirect_uri.query = Rack::Utils.build_query(params)
|
200
|
+
return redirect_to(redirect_uri)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# 4. Obtaining an Access Token
|
205
|
+
def respond_with_access_token(request, logger)
|
206
|
+
return [405, { "Content-Type"=>"application/json" }, ["POST only"]] unless request.post?
|
207
|
+
# 4.2. Access Token Response
|
208
|
+
begin
|
209
|
+
client = get_client(request)
|
210
|
+
case request.POST["grant_type"]
|
211
|
+
when "authorization_code"
|
212
|
+
# 4.1.1. Authorization Code
|
213
|
+
grant = AccessGrant.from_code(request.POST["code"])
|
214
|
+
raise InvalidGrantError unless grant && client.id == grant.client_id
|
215
|
+
raise InvalidGrantError unless grant.redirect_uri.nil? || grant.redirect_uri == Utils.parse_redirect_uri(request.POST["redirect_uri"]).to_s
|
216
|
+
access_token = grant.authorize!
|
217
|
+
when "password"
|
218
|
+
raise UnsupportedGrantType unless options[:authenticator]
|
219
|
+
# 4.1.2. Resource Owner Password Credentials
|
220
|
+
username, password = request.POST.values_at("username", "password")
|
221
|
+
requested_scope = request.POST["scope"].to_s.split.uniq.join(" ")
|
222
|
+
raise InvalidGrantError unless username && password
|
223
|
+
resource = options[:authenticator].call(username, password)
|
224
|
+
raise InvalidGrantError unless resource
|
225
|
+
if scopes = options[:scopes]
|
226
|
+
allowed_scope = scopes.respond_to?(:all?) ? scopes : scopes.split
|
227
|
+
raise InvalidScopeError unless requested_scope.split.all? { |v| allowed_scope.include?(v) }
|
228
|
+
end
|
229
|
+
access_token = AccessToken.get_token_for(resource, requested_scope.to_s, client.id)
|
230
|
+
else raise UnsupportedGrantType
|
231
|
+
end
|
232
|
+
logger.info "Access token #{access_token.token} granted to client #{client.display_name}, resource #{access_token.resource}" if logger
|
233
|
+
response = { :access_token=>access_token.token }
|
234
|
+
response[:scope] = access_token.scope unless access_token.scope.empty?
|
235
|
+
return [200, { "Content-Type"=>"application/json", "Cache-Control"=>"no-store" }, response.to_json]
|
236
|
+
# 4.3. Error Response
|
237
|
+
rescue Error=>error
|
238
|
+
logger.error "Access token request error: #{error.code} #{error.message}" if logger
|
239
|
+
return unauthorized(request, error) if InvalidClientError === error && request.basic?
|
240
|
+
return [400, { "Content-Type"=>"application/json", "Cache-Control"=>"no-store" },
|
241
|
+
{ :error=>error.code, :error_description=>error.message }.to_json]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Returns client from request based on credentials. Raises
|
246
|
+
# InvalidClientError if client doesn't exist or secret doesn't match.
|
247
|
+
def get_client(request)
|
248
|
+
# 2.1 Client Password Credentials
|
249
|
+
if request.basic?
|
250
|
+
client_id, client_secret = request.credentials
|
251
|
+
elsif request.form_data?
|
252
|
+
client_id, client_secret = request.POST.values_at("client_id", "client_secret")
|
253
|
+
else
|
254
|
+
client_id, client_secret = request.GET.values_at("client_id", "client_secret")
|
255
|
+
end
|
256
|
+
client = self.class.get_client(client_id)
|
257
|
+
raise InvalidClientError unless client && client.secret == client_secret
|
258
|
+
raise InvalidClientError if client.revoked
|
259
|
+
return client
|
260
|
+
rescue BSON::InvalidObjectId
|
261
|
+
raise InvalidClientError
|
262
|
+
end
|
263
|
+
|
264
|
+
# Rack redirect response. The argument is typically a URI object.
|
265
|
+
def redirect_to(uri)
|
266
|
+
return [302, { "Location"=>uri.to_s }, []]
|
267
|
+
end
|
268
|
+
|
269
|
+
def bad_request(message)
|
270
|
+
return [400, { "Content-Type"=>"text/plain" }, [message]]
|
271
|
+
end
|
272
|
+
|
273
|
+
# Returns WWW-Authenticate header.
|
274
|
+
def unauthorized(request, error = nil)
|
275
|
+
challenge = 'OAuth realm="%s"' % (options[:realm] || request.host)
|
276
|
+
challenge << ', error="%s", error_description="%s"' % [error.code, error.message] if error
|
277
|
+
return [401, { "WWW-Authenticate"=>challenge }, []]
|
278
|
+
end
|
279
|
+
|
280
|
+
# Wraps Rack::Request to expose Basic and OAuth authentication
|
281
|
+
# credentials.
|
282
|
+
class OAuthRequest < Rack::Request
|
283
|
+
|
284
|
+
AUTHORIZATION_KEYS = %w{HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION}
|
285
|
+
|
286
|
+
# Returns authorization header.
|
287
|
+
def authorization
|
288
|
+
@authorization ||= AUTHORIZATION_KEYS.inject(nil) { |auth, key| auth || @env[key] }
|
289
|
+
end
|
290
|
+
|
291
|
+
# True if authentication scheme is OAuth.
|
292
|
+
def oauth?
|
293
|
+
authorization[/^oauth/i] if authorization
|
294
|
+
end
|
295
|
+
|
296
|
+
# True if authentication scheme is Basic.
|
297
|
+
def basic?
|
298
|
+
authorization[/^basic/i] if authorization
|
299
|
+
end
|
300
|
+
|
301
|
+
# If Basic auth, returns username/password, if OAuth, returns access
|
302
|
+
# token.
|
303
|
+
def credentials
|
304
|
+
basic? ? authorization.gsub(/\n/, "").split[1].unpack("m*").first.split(/:/, 2) :
|
305
|
+
oauth? ? authorization.gsub(/\n/, "").split[1] : nil
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
end
|
312
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
class Server
|
4
|
+
|
5
|
+
# Base class for all OAuth errors. These map to error codes in the spec.
|
6
|
+
class Error < StandardError
|
7
|
+
|
8
|
+
def initialize(code, message)
|
9
|
+
super message
|
10
|
+
@code = code.to_sym
|
11
|
+
end
|
12
|
+
|
13
|
+
# The OAuth error code.
|
14
|
+
attr_reader :code
|
15
|
+
end
|
16
|
+
|
17
|
+
# Access token expired, client expected to request new one using refresh
|
18
|
+
# token.
|
19
|
+
class ExpiredTokenError < Error
|
20
|
+
def initialize
|
21
|
+
super :expired_token, "The access token has expired."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# The client identifier provided is invalid, the client failed to
|
26
|
+
# authenticate, the client did not include its credentials, provided
|
27
|
+
# multiple client credentials, or used unsupported credentials type.
|
28
|
+
class InvalidClientError < Error
|
29
|
+
def initialize
|
30
|
+
super :invalid_client, "Client ID and client secret do not match."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# The provided access grant is invalid, expired, or revoked (e.g. invalid
|
35
|
+
# assertion, expired authorization token, bad end-user password credentials,
|
36
|
+
# or mismatching authorization code and redirection URI).
|
37
|
+
class InvalidGrantError < Error
|
38
|
+
def initialize
|
39
|
+
super :invalid_grant, "This access grant is no longer valid."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Invalid_request, the request is missing a required parameter, includes an
|
44
|
+
# unsupported parameter or parameter value, repeats the same parameter, uses
|
45
|
+
# more than one method for including an access token, or is otherwise
|
46
|
+
# malformed.
|
47
|
+
class InvalidRequestError < Error
|
48
|
+
def initialize(message)
|
49
|
+
super :invalid_request, message || "The request has the wrong parameters."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# The requested scope is invalid, unknown, or malformed.
|
54
|
+
class InvalidScopeError < Error
|
55
|
+
def initialize
|
56
|
+
super :invalid_scope, "The requested scope is not supported."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Access token expired, client cannot refresh and needs new authorization.
|
61
|
+
class InvalidTokenError < Error
|
62
|
+
def initialize
|
63
|
+
super :invalid_token, "The access token is no longer valid."
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# The redirection URI provided does not match a pre-registered value.
|
68
|
+
class RedirectUriMismatchError < Error
|
69
|
+
def initialize
|
70
|
+
super :redirect_uri_mismatch, "Must use the same redirect URI you registered with us."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# The authenticated client is not authorized to use the access grant type provided.
|
75
|
+
class UnauthorizedClientError < Error
|
76
|
+
def initialize
|
77
|
+
super :unauthorized_client, "You are not allowed to access this resource."
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# This access grant type is not supported by this server.
|
82
|
+
class UnsupportedGrantType < Error
|
83
|
+
def initialize
|
84
|
+
super :unsupported_grant_type, "This access grant type is not supported by this server."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# The requested response type is not supported by the authorization server.
|
89
|
+
class UnsupportedResponseTypeError < Error
|
90
|
+
def initialize
|
91
|
+
super :unsupported_response_type, "The requested response type is not supported."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
class Server
|
4
|
+
|
5
|
+
# Helper methods that provide access to the OAuth state during the
|
6
|
+
# authorization flow, and from authenticated requests. For example:
|
7
|
+
#
|
8
|
+
# def show
|
9
|
+
# logger.info "#{oauth.client.display_name} accessing #{oauth.scope}"
|
10
|
+
# end
|
11
|
+
class Helper
|
12
|
+
|
13
|
+
def initialize(request, response)
|
14
|
+
@request, @response = request, response
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the access token. Only applies if client authenticated.
|
18
|
+
#
|
19
|
+
# @return [String, nil] Access token, if authenticated
|
20
|
+
def access_token
|
21
|
+
@access_token ||= @request.env["oauth.access_token"]
|
22
|
+
end
|
23
|
+
|
24
|
+
# True if client authenticated.
|
25
|
+
#
|
26
|
+
# @return [true, false] True if authenticated
|
27
|
+
def authenticated?
|
28
|
+
!!access_token
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the authenticated resource. Only applies if client
|
32
|
+
# authenticated.
|
33
|
+
#
|
34
|
+
# @return [String, nil] Resource, if authenticated
|
35
|
+
def resource
|
36
|
+
@resource ||= @request.env["oauth.resource"]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the Client object associated with this request. Available if
|
40
|
+
# client authenticated, or while processing authorization request.
|
41
|
+
#
|
42
|
+
# @return [Client, nil] Client if authenticated, or while authorizing
|
43
|
+
def client
|
44
|
+
if access_token
|
45
|
+
@client ||= Server.get_client(Server.get_access_token(access_token).client_id)
|
46
|
+
elsif authorization
|
47
|
+
@client ||= Server.get_client(Server.get_auth_request(authorization).client_id)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns scope associated with this request. Available if client
|
52
|
+
# authenticated, or while processing authorization request.
|
53
|
+
#
|
54
|
+
# @return [Array<String>, nil] Scope names, e.g ["read, "write"]
|
55
|
+
def scope
|
56
|
+
if access_token
|
57
|
+
@scope ||= Server.get_access_token(access_token).scope.split
|
58
|
+
elsif authorization
|
59
|
+
@scope ||= Server.get_auth_request(authorization).scope.split
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Rejects the request and returns 401 (Unauthorized). You can just
|
64
|
+
# return 401, but this also sets the WWW-Authenticate header the right
|
65
|
+
# value.
|
66
|
+
#
|
67
|
+
# @return 401
|
68
|
+
def no_access!
|
69
|
+
@response["oauth.no_access"] = true
|
70
|
+
@response.status = 401
|
71
|
+
end
|
72
|
+
|
73
|
+
# Rejects the request and returns 403 (Forbidden). You can just
|
74
|
+
# return 403, but this also sets the WWW-Authenticate header the right
|
75
|
+
# value. Indicates which scope the client needs to make this request.
|
76
|
+
#
|
77
|
+
# @param [String] scope The missing scope, e.g. "read"
|
78
|
+
# @return 403
|
79
|
+
def no_scope!(scope)
|
80
|
+
@response["oauth.no_scope"] = scope
|
81
|
+
@response.status = 403
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the authorization request handle. Available when starting an
|
85
|
+
# authorization request (i.e. /oauth/authorize).
|
86
|
+
#
|
87
|
+
# @return [String] Authorization handle
|
88
|
+
def authorization
|
89
|
+
@request_id ||= @request.env["oauth.authorization"]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Sets the authorization request handle. Use this during the
|
93
|
+
# authorization flow.
|
94
|
+
#
|
95
|
+
# @param [String] authorization handle
|
96
|
+
def authorization=(authorization)
|
97
|
+
@scope, @client = nil
|
98
|
+
@request_id = authorization
|
99
|
+
end
|
100
|
+
|
101
|
+
# Grant authorization request. Call this at the end of the authorization
|
102
|
+
# flow to signal that the user has authorized the client to access the
|
103
|
+
# specified resource. Don't render anything else.
|
104
|
+
#
|
105
|
+
# @param [String] authorization Authorization handle
|
106
|
+
# @param [String] resource Resource string
|
107
|
+
# @return 200
|
108
|
+
def grant!(authorization, resource)
|
109
|
+
@response["oauth.authorization"] = authorization
|
110
|
+
@response["oauth.resource"] = resource.to_s
|
111
|
+
@response.status = 200
|
112
|
+
end
|
113
|
+
|
114
|
+
# Deny authorization request. Call this at the end of the authorization
|
115
|
+
# flow to signal that the user has not authorized the client. Don't
|
116
|
+
# render anything else.
|
117
|
+
#
|
118
|
+
# @param [String] authorization Authorization handle
|
119
|
+
# @return 401
|
120
|
+
def deny!(authorization)
|
121
|
+
@response["oauth.authorization"] = authorization
|
122
|
+
@response.status = 401
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns all access tokens associated with this resource.
|
126
|
+
#
|
127
|
+
# @param [String] resource Resource string
|
128
|
+
# @return [Array<AccessToken>]
|
129
|
+
def list_access_tokens(resource)
|
130
|
+
Rack::OAuth2::Server.list_access_tokens(resource)
|
131
|
+
end
|
132
|
+
|
133
|
+
def inspect
|
134
|
+
authorization ? "Authorization request for #{scope.join(",")} on behalf of #{client.display_name}" :
|
135
|
+
authenticated? ? "Authenticated as #{resource}" : nil
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|