Class: HTTPX::Plugins::OAuth::OAuthSession

Inherits:
Object
  • Object
show all
Defined in:
lib/httpx/plugins/oauth.rb,
sig/plugins/oauth.rbs

Overview

Implements the bulk of functionality and maintains the state associated with the management of the the lifecycle of an OAuth session.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(issuer:, client_id:, client_secret:, access_token: nil, refresh_token: nil, scope: nil, audience: nil, token_endpoint: nil, grant_type: nil, token_endpoint_auth_method: nil) ⇒ OAuthSession

Returns a new instance of OAuthSession.

Raises:



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/httpx/plugins/oauth.rb', line 37

def initialize(
  issuer:,
  client_id:,
  client_secret:,
  access_token: nil,
  refresh_token: nil,
  scope: nil,
  audience: nil,
  token_endpoint: nil,
  grant_type: nil,
  token_endpoint_auth_method: nil
)
  @issuer = URI(issuer)
  @client_id = client_id
  @client_secret = client_secret
  @token_endpoint = URI(token_endpoint) if token_endpoint
  @scope = case scope
           when String
             scope.split
           when Array
             scope
  end
  @audience = audience
  @access_token = access_token
  @refresh_token = refresh_token
  @token_endpoint_auth_method = String(token_endpoint_auth_method) if token_endpoint_auth_method
  @grant_type = grant_type || (@refresh_token ? "refresh_token" : "client_credentials")
  @expires_at = nil
  @token_mon = Monitor.new

  unless @token_endpoint_auth_method.nil? || SUPPORTED_AUTH_METHODS.include?(@token_endpoint_auth_method)
    raise Error, "#{@token_endpoint_auth_method} is not a supported auth method"
  end

  return if SUPPORTED_GRANT_TYPES.include?(@grant_type)

  raise Error, "#{@grant_type} is not a supported grant type"
end

Instance Attribute Details

#access_tokenString? (readonly)

Returns the value of attribute access_token.

Returns:

  • (String, nil)


90
91
92
93
94
95
96
97
98
# File 'lib/httpx/plugins/oauth.rb', line 90

def access_token
  @token_mon.synchronize do
    if (expires_at = @expires_at) && expires_at < Time.now.to_i
      reset!
    end

    @access_token
  end
end

#refresh_tokenString? (readonly)

Returns the value of attribute refresh_token.

Returns:

  • (String, nil)


189
190
191
# File 'lib/httpx/plugins/oauth.rb', line 189

def refresh_token
  @token_mon.synchronize { @refresh_token }
end

Instance Method Details

#expires_atObject



86
87
88
# File 'lib/httpx/plugins/oauth.rb', line 86

def expires_at
  @token_mon.synchronize { @expires_at }
end

#fetch_access_token(http) ⇒ String

when not available, it uses the http object to request new access and refresh tokens.

Parameters:

  • http (sessionOAuth)

Returns:

  • (String)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/httpx/plugins/oauth.rb', line 107

def fetch_access_token(http)
  return access_token if access_token

  load(http)

  # always prefer refresh token grant if a refresh token is available
  grant_type = @refresh_token ? "refresh_token" : @grant_type

  headers = {} # : Hash[String ,String]
  form_post = {
    "grant_type" => @grant_type,
    "scope" => Array(@scope).join(" "),
    "audience" => @audience,
  }.compact

  # auth
  case token_endpoint_auth_method
  when "client_secret_post"
    form_post["client_id"] = @client_id
    form_post["client_secret"] = @client_secret
  when "client_secret_basic"
    headers["authorization"] = Authentication::Basic.new(@client_id, @client_secret).authenticate
  end

  case grant_type
  when "client_credentials"
    # do nothing
  when "refresh_token"
    ref_token = refresh_token

    raise Error, "cannot use the `\"refresh_token\"` grant type without a refresh token" unless ref_token

    form_post["refresh_token"] = ref_token
  end

  # POST /token
  token_request = http.build_request("POST", token_endpoint, headers: headers, form: form_post)

  token_request.headers.delete("authorization") unless token_endpoint_auth_method == "client_secret_basic"

  token_response = http.skip_auth_header { http.request(token_request) }

  begin
    token_response.raise_for_status
  rescue HTTPError => e
    @refresh_token = nil if e.response.status == 401 && (grant_type == "refresh_token")
    raise e
  end

  payload = token_response.json

  @token_mon.synchronize do
    @refresh_token = payload.fetch("refresh_token", @refresh_token)
    if (expires_in = payload["expires_in"])
      @expires_at = Time.now.to_i + Integer(expires_in)
    end
    @access_token = payload["access_token"]
  end
end

#load(http) ⇒ void

This method returns an undefined value.

uses http to fetch for the oauth server metadata.

Parameters:

  • http (sessionOAuth)


194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/httpx/plugins/oauth.rb', line 194

def load(http)
  return if @grant_type && @scope

   = http.skip_auth_header { http.get("#{@issuer}/.well-known/oauth-authorization-server").raise_for_status.json }

  @token_mon.synchronize do
    @token_endpoint = ["token_endpoint"]
    @scope = ["scopes_supported"]
    @grant_type = Array(["grant_types_supported"]).find { |gr| SUPPORTED_GRANT_TYPES.include?(gr) }
    @token_endpoint_auth_method = Array(["token_endpoint_auth_methods_supported"]).find do |am|
      SUPPORTED_AUTH_METHODS.include?(am)
    end
  end
  nil
end

#merge(other) ⇒ instance

TODO: remove this after deprecating the :oauth_session option

Parameters:

  • other (instance, Hash[untyped, untyped])

Returns:

  • (instance)


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/httpx/plugins/oauth.rb', line 168

def merge(other)
  obj = dup

  case other
  when OAuthSession
    other.instance_variables.each do |ivar|
      val = other.instance_variable_get(ivar)
      next unless val

      obj.instance_variable_set(ivar, val)
    end
  when Hash
    other.each do |k, v|
      obj.instance_variable_set(:"@#{k}", v) if obj.instance_variable_defined?(:"@#{k}")
    end
  end
  obj
end

#reset!void

This method returns an undefined value.



100
101
102
103
104
# File 'lib/httpx/plugins/oauth.rb', line 100

def reset!
  @token_mon.synchronize do
    @access_token = @expires_at = nil
  end
end

#token_endpointuri

returns the URL where to request access and refresh tokens from.

Returns:

  • (uri)


77
78
79
# File 'lib/httpx/plugins/oauth.rb', line 77

def token_endpoint
  @token_endpoint || "#{@issuer}/token"
end

#token_endpoint_auth_methodString

returns the oauth-documented authorization method to use when requesting a token.

Returns:

  • (String)


82
83
84
# File 'lib/httpx/plugins/oauth.rb', line 82

def token_endpoint_auth_method
  @token_endpoint_auth_method || "client_secret_basic"
end