the oauth plugin

This commit is contained in:
HoneyryderChuck 2023-05-20 02:17:41 +02:00
parent af38476a14
commit 5655c602c7
5 changed files with 227 additions and 2 deletions

View File

@ -22,7 +22,7 @@ module HTTPX
module OptionsMethods
def option_digest(value)
raise TypeError, ":digest must be a Digest" unless value.is_a?(Authentication::Digest)
raise TypeError, ":digest must be a #{Authentication::Digest}" unless value.is_a?(Authentication::Digest)
value
end

170
lib/httpx/plugins/oauth.rb Normal file
View File

@ -0,0 +1,170 @@
# frozen_string_literal: true
module HTTPX
module Plugins
#
# https://gitlab.com/os85/httpx/wikis/OAuth
#
module OAuth
class << self
def load_dependencies(_klass)
require_relative "authentication/basic"
end
end
SUPPORTED_GRANT_TYPES = %w[client_credentials refresh_token].freeze
SUPPORTED_AUTH_METHODS = %w[client_secret_basic client_secret_post].freeze
class OAuthSession
attr_reader :token_endpoint_auth_method, :grant_type, :client_id, :client_secret, :access_token, :refresh_token, :scope
def initialize(
issuer:,
client_id:,
client_secret:,
access_token: nil,
refresh_token: nil,
scope: nil,
token_endpoint: nil,
response_type: nil,
grant_type: nil,
token_endpoint_auth_method: "client_secret_basic"
)
@issuer = URI(issuer)
@client_id = client_id
@client_secret = client_secret
@token_endpoint = URI(token_endpoint) if token_endpoint
@response_type = response_type
@scope = case scope
when String
scope.split
when Array
scope
end
@access_token = access_token
@refresh_token = refresh_token
@token_endpoint_auth_method = String(token_endpoint_auth_method)
@grant_type = grant_type || (@refresh_token ? "refresh_token" : "client_credentials")
unless 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
def token_endpoint
@token_endpoint || "#{@issuer}/token"
end
def load(http)
return unless @token_endpoint && @token_endpoint_auth_method && @grant_type && @scope
metadata = http.get("#{issuer}/.well-known/oauth-authorization-server").raise_for_status.json
@token_endpoint = metadata["token_endpoint"]
@scope = metadata["scopes_supported"]
@grant_type = Array(metadata["grant_types_supported"]).find { |gr| SUPPORTED_GRANT_TYPES.include?(gr) }
@token_endpoint_auth_method = Array(metadata["token_endpoint_auth_methods_supported"]).find do |am|
SUPPORTED_AUTH_METHODS.include?(am)
end
end
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
end
module OptionsMethods
def option_oauth_session(value)
case value
when Hash
OAuthSession.new(**value)
when OAuthSession
value
else
raise TypeError, ":oauth_session must be a #{OAuthSession}"
end
end
end
module InstanceMethods
def oauth_authentication(**args)
with(oauth_session: OAuthSession.new(**args))
end
def with_access_token
oauth_session = @options.oauth_session
oauth_session.load(self)
grant_type = oauth_session.grant_type
headers = {}
form_post = { "grant_type" => grant_type, "scope" => Array(oauth_session.scope).join(" ") }.compact
# auth
case oauth_session.token_endpoint_auth_method
when "client_secret_basic"
headers["authorization"] = Authentication::Basic.new(oauth_session.client_id, oauth_session.client_secret).authenticate
when "client_secret_post"
form_post["client_id"] = oauth_session.client_id
form_post["client_secret"] = oauth_session.client_secret
end
case grant_type
when "client_credentials"
# do nothing
when "refresh_token"
form_post["refresh_token"] = oauth_session.refresh_token
end
token_request = build_request("POST", oauth_session.token_endpoint, headers: headers, form: form_post)
token_request.headers.delete("authorization") unless oauth_session.token_endpoint_auth_method == "client_secret_basic"
token_response = request(token_request)
token_response.raise_for_status
payload = token_response.json
access_token = payload["access_token"]
refresh_token = payload["refresh_token"]
with(oauth_session: oauth_session.merge(access_token: access_token, refresh_token: refresh_token))
end
def build_request(*, _)
request = super
return request if request.headers.key?("authorization")
oauth_session = @options.oauth_session
return request unless oauth_session && oauth_session.access_token
request.headers["authorization"] = "Bearer #{oauth_session.access_token}"
request
end
end
end
register_plugin :oauth, OAuth
end
end

View File

@ -34,6 +34,7 @@ module HTTPX
| (:grpc, ?options) -> Plugins::grpcSession
| (:response_cache, ?options) -> Plugins::sessionResponseCache
| (:circuit_breaker, ?options) -> Plugins::sessionCircuitBreaker
| (:oauth, ?options) -> Plugins::sessionOAuth
| (Symbol | Module, ?options) { (Class) -> void } -> Session
| (Symbol | Module, ?options) -> Session

54
sig/plugins/oauth.rbs Normal file
View File

@ -0,0 +1,54 @@
module HTTPX
module Plugins
#
# https://gitlab.com/os85/httpx/wikis/OAuth
#
module OAuth
def self.load_dependencies: (singleton(Session) klass) -> void
type grant_type = "client_credentials" | "refresh_token"
type token_auth_method = "client_secret_basic" | "client_secret_post"
SUPPORTED_GRANT_TYPES: ::Array[grant_type]
SUPPORTED_AUTH_METHODS: ::Array[token_auth_method]
class OAuthSession
attr_reader token_endpoint_auth_method: token_auth_method
attr_reader grant_type: grant_type
attr_reader client_id: String
attr_reader client_secret: String
attr_reader access_token: String?
attr_reader refresh_token: String?
attr_reader scope: Array[String]?
def initialize: (issuer: uri, client_id: String, client_secret: String, ?access_token: String?, ?refresh_token: String?, ?scope: (Array[String] | String)?, ?token_endpoint: String?, ?response_type: String?, ?grant_type: String?, ?token_endpoint_auth_method: ::String) -> void
def token_endpoint: () -> String
def load: (Session http) -> void
def merge: (instance | Hash[untyped, untyped] other) -> instance
end
interface _AwsSdkOptions
def oauth_session: () -> OAuthSession?
end
module InstanceMethods
def oauth_authentication: (**untyped args) -> instance
def with_access_token: () -> instance
end
end
type sessionOAuth = Session & OAuth::InstanceMethods
end
end

View File

@ -14,7 +14,7 @@ module Requests
assert opts.oauth_session.grant_type == "client_credentials"
assert opts.oauth_session.token_endpoint.to_s == "#{server.origin}/token"
assert opts.oauth_session.token_endpoint_auth_method == "client_secret_basic"
assert opts.oauth_session.scope == "all"
assert opts.oauth_session.scope == %w[all]
opts = HTTPX.plugin(:oauth).oauth_authentication(
issuer: "https://smthelse",