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/utils.rb,
lib/rack/oauth2/server/admin.rb,
lib/rack/oauth2/server/helper.rb,
lib/rack/oauth2/server/errors.rb,
lib/rack/oauth2/models/client.rb,
lib/rack/oauth2/server/railtie.rb,
lib/rack/oauth2/server/practice.rb,
lib/rack/oauth2/models/auth_request.rb,
lib/rack/oauth2/models/access_grant.rb,
lib/rack/oauth2/models/access_token.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

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

- (Server) initialize(app, options = nil, &authenticator)

Returns a new instance of Server



166
167
168
169
170
171
172
173
174
# File 'lib/rack/oauth2/server.rb', line 166

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
end

Instance Attribute Details

- (Object) options (readonly)

Options specific for this handle. @see Options



177
178
179
# File 'lib/rack/oauth2/server.rb', line 177

def options
  @options
end

Class Method Details

+ (String) access_grant(identity, client_id, scope = nil, expires_in = nil)

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



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

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

+ (Object) create_indexes(&block)



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

+ (Object) database

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

+ (AccessToken) get_access_token(token)

Returns AccessToken from token.

Parameters:

  • token (String)

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

Returns:



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

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

+ (AuthReqeust) get_auth_request(authorization)

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

+ (Client) get_client(client_id)

Returns Client from client identifier.

Parameters:

  • client_id (String)

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

Returns:



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

def get_client(client_id)
  Client.find(client_id)
end

+ (Array<AccessToken>) list_access_tokens(identity)

Returns all AccessTokens for an identity.

Parameters:

  • identity (String)

    Identity, e.g. user ID, account ID

Returns:



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

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

+ (Object) new_instance(klass, fields)

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

+ (Object) options

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



160
161
162
# File 'lib/rack/oauth2/server.rb', line 160

def self.options
  @options
end

+ (Object) register(args)

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.



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

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

+ (Object) secure_random

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

+ (String) token_for(identity, client_id, scope = nil, expires_in = nil)

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



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

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

- (Object) call(env)



179
180
181
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
# File 'lib/rack/oauth2/server.rb', line 179

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]["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