Skip to content

Commit

Permalink
Merge pull request #1 from gberzins/grantless-requests
Browse files Browse the repository at this point in the history
add possibility for grantless requests
  • Loading branch information
gberzins committed Aug 14, 2023
2 parents 18a2613 + d3757b3 commit 001788d
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 22 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ require 'fulfillment-outbound-api-model'
Rails.cache.write("SPAPI-TOKEN-#{access_token_key}", token[:access_token], expires_in: token[:expires_in] - 60)
end
config.get_access_token = -> (access_token_key) { Rails.cache.read("SPAPI-TOKEN-#{access_token_key}") }

# optional lambdas for caching grantless LWA access token instead of requesting it each time, e.g.:
config.save_grantless_access_token = -> (access_token_key, token) do
Rails.cache.write("SPAPI-TOKEN-#{access_token_key}", token[:access_token], expires_in: token[:expires_in] - 60)
end
config.get_grantless_access_token = -> (access_token_key) { Rails.cache.read("SPAPI-TOKEN-#{access_token_key}") }
end

begin
Expand Down
86 changes: 65 additions & 21 deletions lib/sp_api_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,64 @@ def initialize(config = SpConfiguration.default)
alias_method :super_call_api, :call_api
def call_api(http_method, path, opts = {})
unsigned_request = build_request(http_method, path, opts)
aws_headers = auth_headers(http_method, unsigned_request.url, unsigned_request.encoded_body)
aws_headers = auth_headers(http_method, path, unsigned_request.url, unsigned_request.encoded_body)
signed_opts = opts.merge(:header_params => aws_headers.merge(opts[:header_params] || {}))
super(http_method, path, signed_opts)
end

private

def retrieve_token(grantless, get_token_method, save_token_method, token_key)
return request_lwa_access_token(grantless)[:access_token] unless config.public_send(get_token_method)

stored_token = config.public_send(get_token_method).call(config.public_send(token_key))
stored_token || store_and_return_new_token(grantless, save_token_method, token_key)
end

def store_and_return_new_token(grantless, save_token_method, token_key)
new_token = request_lwa_access_token(grantless)
config.public_send(save_token_method).call(config.public_send(token_key), new_token) if config.public_send(save_token_method)
new_token[:access_token]
end

def retrieve_lwa_grantless_access_token
retrieve_token(
true,
:get_grantless_access_token,
:save_grantless_access_token,
:grantless_access_token_key)
end

def retrieve_lwa_access_token
return request_lwa_access_token[:access_token] unless config.get_access_token
stored_token = config.get_access_token.call(config.access_token_key)
if stored_token.nil?
new_token = request_lwa_access_token
config.save_access_token.call(config.access_token_key, new_token) if config.save_access_token
return new_token[:access_token]
else
return stored_token
end
retrieve_token(
false,
:get_access_token,
:save_access_token,
:access_token_key)
end

def request_lwa_access_token
def request_lwa_access_token(grantless)
newself = self.dup
newself.config = config.dup
newself.config.host = 'api.amazon.com'

form_params = {
client_id: config.client_id,
client_secret: config.client_secret,
grant_type: grantless ? 'client_credentials' : 'refresh_token'
}

if grantless
form_params[:scope] = 'sellingpartnerapi::notifications'
else
form_params[:refresh_token] = config.refresh_token
end

data, status_code, headers = newself.super_call_api(:POST, '/auth/o2/token',
:header_params => {
'Content-Type' => 'application/x-www-form-urlencoded'
},
:form_params => {
grant_type: 'refresh_token',
refresh_token: config.refresh_token,
client_id: config.client_id,
client_secret: config.client_secret
},
:form_params => form_params,
:return_type => 'Object')

unless data && data[:access_token]
Expand All @@ -73,10 +97,30 @@ def signed_request_headers(http_method, url, body)
signer.sign_request(http_method: http_method.to_s, url: url, body: body).headers
end

def auth_headers(http_method, url, body)
signed_request_headers(http_method, url, body).merge({
'x-amz-access-token' => retrieve_lwa_access_token
})
def auth_headers(http_method, path, url, body)
access_token = if grantless_request?(http_method, path)
retrieve_lwa_grantless_access_token
else
retrieve_lwa_access_token
end

signed_request_headers(http_method, url, body).merge({ 'x-amz-access-token' => access_token })
end

def grantless_request?(http_method, path)
case http_method
when :POST
path == '/notifications/v1/destinations'
when :GET
%w[/notifications/v1/destinations /authorization/v1/authorizationCode].include?(path) ||
!!path.match(%r{^/notifications/v1/subscriptions/[^/]+/[^/]+$}) ||
!!path.match(%r{^/notifications/v1/destinations/[^/]+$})
when :DELETE
!!path.match(%r{^/notifications/v1/destinations/[^/]+$}) ||
!!path.match(%r{^/notifications/v2/subscriptions/[^/]+/[^/]+$})
else
false
end
end
end
end
7 changes: 6 additions & 1 deletion lib/sp_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ module AmzSpApi
class SpConfiguration < Configuration
attr_accessor :refresh_token, :client_id, :client_secret, :sandbox, :region,
:aws_access_key_id, :aws_secret_access_key, :credentials_provider, # either access key or credentials_provider for AWS Signer, e.g. Aws::STS::Client
:save_access_token, :get_access_token # optional lambdas for storing and retrieving token
:save_access_token, :get_access_token, # optional lambdas for storing and retrieving token
:save_grantless_access_token, :get_grantless_access_token # optional lambdas for storing and retrieving grantless token

# from https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md#selling-partner-api-endpoints
AWS_REGION_MAP = {
Expand Down Expand Up @@ -33,6 +34,10 @@ def access_token_key
Digest::MD5.hexdigest("#{client_id} #{refresh_token}")
end

def grantless_access_token_key
Digest::MD5.hexdigest("#{client_id} #{region} grantless")
end

def self.default
@@default ||= SpConfiguration.new
end
Expand Down

0 comments on commit 001788d

Please sign in to comment.