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

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

Overview

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

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 Method Details

#access_tokenObject



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

#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) ⇒ Object

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



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

#merge(other) ⇒ Object

TODO: remove this after deprecating the ‘:oauth_session` option



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!Object



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_endpointObject

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



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

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

#token_endpoint_auth_methodObject

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



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

def token_endpoint_auth_method
  @token_endpoint_auth_method || "client_secret_basic"
end