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 = Options.new, &authenticator) ⇒ Server

Returns a new instance of Server.



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

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

Instance Attribute Details

#optionsObject (readonly)

See Also:



166
167
168
# File 'lib/rack/oauth2/server.rb', line 166

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



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

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

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


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

def get_auth_request(authorization)
  AuthRequest.find_by_code(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)
  p client_id
  Client.find_by_code(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:



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

def list_access_tokens(identity)
  AccessToken.from_identity(identity)
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

.secure_randomObject

Long, random and hexy.



15
16
17
# File 'lib/rack/oauth2/models.rb', line 15

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



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

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



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

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
    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]
        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
end