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

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, options = Options.new, &authenticator) ⇒ Server

Returns a new instance of Server.



153
154
155
156
157
158
159
160
161
# File 'lib/rack/oauth2/server.rb', line 153

def initialize(app, options = Options.new, &authenticator)
  @app = app
  @options = 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

Class Attribute Details

.databaseObject

A Mongo::DB object.



12
13
14
# File 'lib/rack/oauth2/models.rb', line 12

def database
  @database
end

Instance Attribute Details

#optionsObject (readonly)

See Also:



164
165
166
# File 'lib/rack/oauth2/server.rb', line 164

def options
  @options
end

Class Method Details

.access_grant(identity, client_id, scope = nil, expires = 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 (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 = nil)
  client = get_client(client_id) or fail "No such client"
  AccessGrant.create(identity, client, scope || client.scope, nil, expires).code
end

.create_indexes(&block) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
# File 'lib/rack/oauth2/models.rb', line 30

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

.get_access_token(token) ⇒ AccessToken

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

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

def get_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:



115
116
117
# File 'lib/rack/oauth2/server.rb', line 115

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.



15
16
17
18
19
20
21
22
# File 'lib/rack/oauth2/models.rb', line 15

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

.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.



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

.secure_randomObject

Long, random and hexy.



25
26
27
# File 'lib/rack/oauth2/models.rb', line 25

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

.token_for(identity, client_id, scope = 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.

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

Returns:

  • (String)

    Access token



106
107
108
109
# File 'lib/rack/oauth2/server.rb', line 106

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

Instance Method Details

#call(env) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
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
# File 'lib/rack/oauth2/server.rb', line 166

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

  begin
    # Use options.database if specified.
    org_database, Server.database = Server.database, options.database || Server.database
    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"]
    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")
        # 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
  ensure
    Server.database = org_database
  end
end