Class: Rack::OAuth2::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/oauth2/server.rb,
lib/rack/oauth2/models.rb,
lib/rack/oauth2/server/admin.rb,
lib/rack/oauth2/server/utils.rb,
lib/rack/oauth2/models/client.rb,
lib/rack/oauth2/server/errors.rb,
lib/rack/oauth2/server/helper.rb,
lib/rack/oauth2/server/railtie.rb,
lib/rack/oauth2/server/practice.rb,
lib/rack/oauth2/models/access_grant.rb,
lib/rack/oauth2/models/access_token.rb,
lib/rack/oauth2/models/auth_request.rb

Overview

Implements an OAuth 2 Authorization Server, based on tools.ietf.org/html/draft-ietf-oauth-v2-10

Defined Under Namespace

Modules: Utils Classes: AccessDeniedError, AccessGrant, AccessToken, Admin, AuthRequest, Client, ExpiredTokenError, Helper, InvalidClientError, InvalidGrantError, InvalidRequestError, InvalidScopeError, InvalidTokenError, OAuthError, OAuthRequest, Options, Practice, Railtie, RedirectUriMismatchError, UnauthorizedClientError, UnsupportedGrantType, UnsupportedResponseTypeError

Constant Summary collapse

VERSION =

Same as gem version number.

IO.read(::File.expand_path("../../../VERSION", ::File.dirname(__FILE__))).strip

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, options = nil, &authenticator) ⇒ Server

Returns a new instance of Server.



168
169
170
171
172
173
174
175
176
177
# File 'lib/rack/oauth2/server.rb', line 168

def initialize(app, options = nil, &authenticator)
  @app = app
  @options = options || Server.options
  @options.authenticator ||= authenticator
  @options.access_token_path ||= "/oauth/access_token"
  @options.authorize_path ||= "/oauth/authorize"
  @options.authorization_types ||=  %w{code token}
  @options.param_authentication ||= false
  @options.collection_prefix ||= "oauth2"
end

Instance Attribute Details

#optionsObject (readonly)

Options specific for this handle. @see Options



180
181
182
# File 'lib/rack/oauth2/server.rb', line 180

def options
  @options
end

Class Method Details

.access_grant(identity, client_id, scope = nil, expires_in = nil) ⇒ String

Creates and returns a new access grant. Actually, returns only the authorization code which you can turn into an access token by making a request to /oauth/access_token.

expires (default to 5 minutes)

Parameters:

  • identity (String, Integer)

    User ID, account ID, etc

  • client_id (String)

    Client identifier

  • scope (Array, nil) (defaults to: nil)

    Array of string, nil if you want ‘em all

  • expires_in (Integer, nil) (defaults to: nil)

    How many seconds before access grant

Returns:

  • (String)

    Access grant authorization code



86
87
88
89
# File 'lib/rack/oauth2/server.rb', line 86

def access_grant(identity, client_id, scope = nil, expires_in = nil)
  client = get_client(client_id) or fail "No such client"
  AccessGrant.create(identity, client, scope || client.scope, nil, expires_in).code
end

.create_indexes(&block) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/rack/oauth2/models.rb', line 27

def create_indexes(&block)
  if block
    @create_indexes ||= []
    @create_indexes << block
  elsif @create_indexes
    @create_indexes.each do |block|
      block.call
    end
    @create_indexes = nil
  end
end

.databaseObject

A Mongo::DB object.



40
41
42
43
44
45
# File 'lib/rack/oauth2/models.rb', line 40

def database
  @database ||= Server.options.database
  raise "No database Configured. You must configure it using Server.options.database = Mongo::Connection.new()[db_name]" unless @database
  raise "You set Server.database to #{Server.database.class}, should be a Mongo::DB object" unless Mongo::DB === @database
  @database
end

.get_access_token(token) ⇒ AccessToken

Returns AccessToken from token.

Parameters:

  • token (String)

    Access token (e.g. from oauth.access_token)

Returns:



95
96
97
# File 'lib/rack/oauth2/server.rb', line 95

def get_access_token(token)
  AccessToken.from_token(token)
end

.get_auth_request(authorization) ⇒ AuthReqeust

Return AuthRequest from authorization request handle.

oauth.authorization)

Parameters:

  • authorization (String)

    Authorization handle (e.g. from

Returns:

  • (AuthReqeust)


23
24
25
# File 'lib/rack/oauth2/server.rb', line 23

def get_auth_request(authorization)
  AuthRequest.find(authorization)
end

.get_client(client_id) ⇒ Client

Returns Client from client identifier.

Parameters:

  • client_id (String)

    Client identifier (e.g. from oauth.client.id)

Returns:



31
32
33
34
# File 'lib/rack/oauth2/server.rb', line 31

def get_client(client_id)
  return client_id if Client === client_id
  Client.find(client_id)
end

.list_access_tokens(identity) ⇒ Array<AccessToken>

Returns all AccessTokens for an identity.

Parameters:

  • identity (String)

    Identity, e.g. user ID, account ID

Returns:



118
119
120
# File 'lib/rack/oauth2/server.rb', line 118

def list_access_tokens(identity)
  AccessToken.from_identity(identity)
end

.new_instance(klass, fields) ⇒ Object

Create new instance of the klass and populate its attributes.



12
13
14
15
16
17
18
19
# File 'lib/rack/oauth2/models.rb', line 12

def new_instance(klass, fields)
  return unless fields
  instance = klass.new
  fields.each do |name, value|
    instance.instance_variable_set :"@#{name}", value
  end
  instance
end

.optionsObject

Global options. This is what we set during configuration (e.g. Rails’ config/application), and options all handlers inherit by default.



162
163
164
# File 'lib/rack/oauth2/server.rb', line 162

def self.options
  @options
end

.register(args) ⇒ Object

Registers and returns a new Client. Can also be used to update existing client registration, by passing identifier (and secret) of existing client record. That way, your setup script can create a new client application and run repeatedly without fail.

existing client registration (in combination wih secret) existing client registration. access (e.g. “My Awesome Application”) name. requests for this client will always redirect back to this URL. (list of names).

Examples:

Registering new client application

Server.register :display_name=>"My Application",
  :link=>"http://example.com", :scope=>%w{read write},
  :redirect_uri=>"http://example.com/oauth/callback"

Migration using configuration file

config = YAML.load_file(Rails.root + "config/oauth.yml")
Server.register config["id"], config["secret"],
  :display_name=>"My  Application", :link=>"http://example.com",
  :scope=>config["scope"],
  :redirect_uri=>"http://example.com/oauth/callback"

Parameters:

  • args (Hash)

    Arguments for registering client application

Options Hash (args):

  • :id (String)

    Client identifier. Use this to update

  • :secret (String)

    Client secret. Use this to update

  • :display_name (String)

    Name to show when authorizing

  • link (String)

    Link to client application’s Web site

  • image_url (String)

    URL of image to show alongside display

  • redirect_uri (String)

    Redirect URL: authorization

  • scope (Array)

    Scope that client application can request

  • notes (Array)

    Free form text, for internal use.



67
68
69
70
71
72
73
74
# File 'lib/rack/oauth2/server.rb', line 67

def register(args)
  if args[:id] && args[:secret] && (client = get_client(args[:id]))
    fail "Client secret does not match" unless client.secret == args[:secret]
    client.update args
  else
    Client.create(args)
  end
end

.secure_randomObject

Long, random and hexy.



22
23
24
# File 'lib/rack/oauth2/models.rb', line 22

def secure_random
  OpenSSL::Random.random_bytes(32).unpack("H*")[0]
end

.token_for(identity, client_id, scope = nil, expires_in = nil) ⇒ String

Returns AccessToken for the specified identity, client application and scope. You can use this method to request existing access token, new token generated if one does not already exists.

expires, defaults to never. If zero or nil, token never expires.

Parameters:

  • identity (String, Integer)

    Identity, e.g. user ID, account ID

  • client_id (String)

    Client application identifier

  • scope (Array, nil) (defaults to: nil)

    Array of names, nil if you want ‘em all

  • expires (Integer, nil)

    How many seconds before access token

Returns:

  • (String)

    Access token



109
110
111
112
# File 'lib/rack/oauth2/server.rb', line 109

def token_for(identity, client_id, scope = nil, expires_in = nil)
  client = get_client(client_id) or fail "No such client"
  AccessToken.get_token_for(identity, client, scope || client.scope, expires_in).token
end

Instance Method Details

#call(env) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/rack/oauth2/server.rb', line 182

def call(env)
  request = OAuthRequest.new(env)
  return @app.call(env) if options.host && options.host != request.host
  return @app.call(env) if options.path && request.path.index(options.path) != 0

  logger = options.logger || env["rack.logger"]

  # 3.  Obtaining End-User Authorization
  # Flow starts here.
  return request_authorization(request, logger) if request.path == options.authorize_path
  # 4.  Obtaining an Access Token
  return respond_with_access_token(request, logger) if request.path == options.access_token_path

  # 5.  Accessing a Protected Resource
  if request.authorization
    # 5.1.1.  The Authorization Request Header Field
    token = request.credentials if request.oauth?
  elsif options.param_authentication && !request.GET["oauth_verifier"] # Ignore OAuth 1.0 callbacks
    # 5.1.2.  URI Query Parameter
    # 5.1.3.  Form-Encoded Body Parameter
    token   = request.GET["oauth_token"] || request.POST["oauth_token"]
    token ||= request.GET['access_token'] || request.POST['access_token']
  end

  if token
    begin
      access_token = AccessToken.from_token(token)
      raise InvalidTokenError if access_token.nil? || access_token.revoked
      raise ExpiredTokenError if access_token.expires_at && access_token.expires_at <= Time.now.to_i
      request.env["oauth.access_token"] = token

      request.env["oauth.identity"] = access_token.identity
      access_token.access!
      logger.info "RO2S: Authorized #{access_token.identity}" if logger
    rescue OAuthError=>error
      # 5.2.  The WWW-Authenticate Response Header Field
      logger.info "RO2S: HTTP authorization failed #{error.code}" if logger
      return unauthorized(request, error)
    rescue =>ex
      logger.info "RO2S: HTTP authorization failed #{ex.message}" if logger
      return unauthorized(request)
    end

    # We expect application to use 403 if request has insufficient scope,
    # and return appropriate WWW-Authenticate header.
    response = @app.call(env)
    if response[0] == 403
      scope = Utils.normalize_scope(response[1].delete("oauth.no_scope"))
      challenge = 'OAuth realm="%s", error="insufficient_scope", scope="%s"' % [(options.realm || request.host), scope.join(" ")]
      response[1]["WWW-Authenticate"] = challenge
      return response
    else
      return response
    end
  else
    response = @app.call(env)
    if response[1] && response[1].delete("oauth.no_access")
      logger.debug "RO2S: Unauthorized request" if logger
      # OAuth access required.
      return unauthorized(request)
    elsif response[1] && response[1]["oauth.authorization"]
      # 3.  Obtaining End-User Authorization
      # Flow ends here.
      return authorization_response(response, logger)
    else
      return response
    end
  end
end