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/models/issuer.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, Issuer, 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.



203
204
205
206
207
208
209
210
211
212
# File 'lib/rack/oauth2/server.rb', line 203

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



215
216
217
# File 'lib/rack/oauth2/server.rb', line 215

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



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

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:



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

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)


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

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:



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

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

.get_issuer(identifier) ⇒ Issuer

Returns an Issuer from it’s identifier.

Parameters:

  • identifier (String)

    the Issuer’s identifier

Returns:



153
154
155
# File 'lib/rack/oauth2/server.rb', line 153

def get_issuer(identifier)
  Issuer.from_identifier(identifier)
end

.list_access_tokens(identity) ⇒ Array<AccessToken>

Returns all AccessTokens for an identity.

Parameters:

  • identity (String)

    Identity, e.g. user ID, account ID

Returns:



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

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.



197
198
199
# File 'lib/rack/oauth2/server.rb', line 197

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.



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

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

.register_issuer(args) ⇒ Object

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

an existing Issuer

Examples:

Registering new Issuer

Server.register_issuer :hmac_secret=>"foo", :notes=>"Company A"

Migration using configuration file

config = YAML.load_file(Rails.root + "config/oauth.yml")
Server.register_issuer config["id"],
:hmac_secret=>"bar", :notes=>"Company A"

Parameters:

  • args (Hash)

    Arguments for registering Issuer

Options Hash (args):

  • :identifier (String)

    Issuer identifier. Use this to update

  • :hmac_secret (String)

    The HMAC secret for this Issuer

  • :public_key (String)

    The RSA public key (in PEM format) for this Issuer

  • :notes (Array)

    Free form text, for internal use.



141
142
143
144
145
146
147
# File 'lib/rack/oauth2/server.rb', line 141

def register_issuer(args)
  if args[:identifier] && (issuer = get_issuer(args[:identifier]))
    issuer.update(args)
  else
    Issuer.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



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

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



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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/rack/oauth2/server.rb', line 217

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