Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5fa101d
test
dawei-nava Jun 2, 2023
e545e13
LG-9449: refresh token
dawei-nava Jun 6, 2023
f3e5bf5
LG-9449: WIP
dawei-nava Jun 9, 2023
5a1781a
LG-9449: WIP
dawei-nava Jun 9, 2023
d00cfd9
LG-9449: refactor
dawei-nava Jun 9, 2023
e2e8f33
LG-9449: format
dawei-nava Jun 9, 2023
ff929d9
LG-9449: cleanup and test
dawei-nava Jun 9, 2023
4658860
LG-9449: clean up.
dawei-nava Jun 9, 2023
7aacbf1
LG-9449: job config and test.
dawei-nava Jun 9, 2023
2cf8f4d
LG-9449: remove test change
dawei-nava Jun 9, 2023
a6e7d20
LG-9449: lint and new event.
dawei-nava Jun 9, 2023
63f186f
LG-9449: cleanup geocoder
dawei-nava Jun 9, 2023
6ae550a
LG-9449: fix test mock
dawei-nava Jun 10, 2023
ce98bac
LG-9449: minor refactor and cleanup.
dawei-nava Jun 12, 2023
d3bbac4
LG-9449: test fixes.
dawei-nava Jun 12, 2023
442226b
LG-9449: address comment
dawei-nava Jun 12, 2023
0e22646
LG-9449: address comment
dawei-nava Jun 13, 2023
ec58a58
LG-9449: address comment
dawei-nava Jun 14, 2023
a8909fc
LG-9449: code climate
dawei-nava Jun 14, 2023
a23c729
LG-9449: minor change.
dawei-nava Jun 14, 2023
b149e7c
LG-9449: job refactor and comment work.
dawei-nava Jun 14, 2023
ce95006
LG-9449: address comment on test.
dawei-nava Jun 14, 2023
cd870c1
LG-9449: address comment, refactor and test.
dawei-nava Jun 16, 2023
a0893ae
LG-9449: address comment
dawei-nava Jun 16, 2023
ff854e4
LG-9449: address comment
dawei-nava Jun 16, 2023
4e5a983
LG-9449: address comment
dawei-nava Jun 16, 2023
c10fa1d
LG-9449: address comment
dawei-nava Jun 16, 2023
20285f4
LG-9449: lint issue.
dawei-nava Jun 16, 2023
3084e4d
LG-9449: exception type.
dawei-nava Jun 16, 2023
2db8c4f
LG-9449: address comments.
dawei-nava Jun 17, 2023
d706c7d
LG-9449: customized error.
dawei-nava Jun 17, 2023
159e529
LG-9449: Remove unused suggest method and tests
NavaTim Jun 20, 2023
a993ac6
LG-9449: Remove puts usage
NavaTim Jun 21, 2023
19f3fe4
LG-9449: wip - Refactor token caching strategy into more easily verif…
NavaTim Jun 22, 2023
9ab42d5
LG-9449: wip - Break out token refresh strategies into classes
NavaTim Jun 23, 2023
2145d05
LG-9449: wip - Refactor to improve naming
NavaTim Jun 23, 2023
a86f45b
LG-9449: wip - Remove fixtures associated with unused suggest functio…
NavaTim Jun 23, 2023
e4acf5a
LG-9449: wip - Lint fixes
NavaTim Jun 23, 2023
5715ea2
LG-9449: wip - Extract and refactor retry logic
NavaTim Jun 23, 2023
7ece934
LG-9449: wip - Start integrating updated strategy with geocoder and cron
NavaTim Jun 24, 2023
1e8970a
LG-9449: wip - Improve docs and organization; split out job enablemen…
NavaTim Jun 26, 2023
feaf472
LG-9449: wip - Remove cache wrapper; improve analytics event description
NavaTim Jun 27, 2023
ada5ec6
Merge branch 'main' of https://github.com/18F/identity-idp into tbrad…
NavaTim Jun 27, 2023
4ace622
LG-9449: wip - Fixes from initial testing
NavaTim Jun 27, 2023
65cf2f7
LG-9449: wip - Fix recursive loop
NavaTim Jun 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/jobs/arcgis_token_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class ArcgisTokenJob < ApplicationJob
queue_as :default

def perform
analytics.idv_arcgis_token_job_started
token_keeper.refresh_token
return true
ensure
analytics.idv_arcgis_token_job_completed
end

private

def token_keeper
ArcgisApi::TokenKeeper.new
end

def analytics
@analytics ||= Analytics.new(
user: AnonymousUser.new,
request: nil,
session: {},
sp: nil,
)
end
end
50 changes: 50 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,56 @@ def idv_arcgis_request_failure(
)
end

# Tracks if request to get auth token from ArcGIS fails
# @param [String] exception_class
# @param [String] exception_message
# @param [Boolean] response_body_present
# @param [Hash] response_body
# @param [Integer] response_status_code
# @param [Integer,nil] retry_count
# @param [Integer,nil] retry_max
# @param [Float,nil] will_retry_in
def idv_arcgis_token_failure(
exception_class:,
exception_message:,
response_body_present:,
response_body:,
response_status_code:,
retry_count: nil,
retry_max: nil,
will_retry_in: nil,
**extra
)
track_event(
'Request ArcGIS Token: request failed',
exception_class: exception_class,
exception_message: exception_message,
response_body_present: response_body_present,
response_body: response_body,
response_status_code: response_status_code,
retry_count: retry_count,
retry_max: retry_max,
will_retry_in: will_retry_in,
**extra,
)
end

# Track when ArcGIS auth token refresh job completed
def idv_arcgis_token_job_completed(**extra)
track_event(
'ArcgisTokenJob: Completed',
**extra,
)
end

# Track when ArcGIS auth token refresh job started
def idv_arcgis_token_job_started(**extra)
track_event(
'ArcgisTokenJob: Started',
**extra,
)
end

# @param [String] step the step that the user was on when they clicked cancel
# @param [Idv::ProofingComponentsLogging] proofing_components User's current proofing components
# The user confirmed their choice to cancel going through IDV
Expand Down
97 changes: 97 additions & 0 deletions app/services/arcgis_api/auth/authentication.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
module ArcgisApi::Auth
# Authenticate with the ArcGIS API
class Authentication
def initialize(analytics: nil)
@analytics = analytics || Analytics.new(
user: AnonymousUser.new,
request: nil,
session: {},
sp: nil,
)
end

# Makes a request to retrieve a new token
# it expires after 1 hour
# @return [ArcgisApi::Auth::Token] Auth token
def retrieve_token
token, expires = request_token.fetch_values('token', 'expires')
expires_at = Time.zone.at(expires / 1000).to_f
return ArcgisApi::Auth::Token.new(token: token, expires_at: expires_at)
end

private

# Makes HTTP request to authentication endpoint and
# returns the token and when it expires (1 hour).
# @return [Hash] API response
def request_token
body = {
username: arcgis_api_username,
password: arcgis_api_password,
referer: domain_name,
f: 'json',
}

connection.post(
arcgis_api_generate_token_url, URI.encode_www_form(body)
) do |req|
req.options.context = { service_name: 'arcgis_token' }
end.body
end

def connection
::Faraday.new do |conn|
ArcgisApi::Faraday::Configuration.setup(conn)
ArcgisApi::Faraday::Configuration.add_retry(conn) do |**args|
log_retry(**args)
end
yield conn if block_given?
end
end

# @param [Faraday::Env] env Request environment
# @param [Faraday::Options] options middleware options
# @param [Integer] retry_count how many retries have already occured (starts at 0)
# @param [Exception] exception exception that triggered the retry,
# will be the synthetic `Faraday::RetriableResponse` if the
# retry was triggered by something other than an exception.
# @param [Float] will_retry_in retry_block is called *before* the retry
# delay, actual retry will happen in will_retry_in number of
# seconds.
def log_retry(env:, options:, retry_count:, exception:, will_retry_in:)
resp_body = env.body.then do |body|
if body.is_a?(String)
JSON.parse(body)
else
body
end
rescue
body
end

http_status = env.status
api_status_code = resp_body.is_a?(Hash) ? resp_body.dig('error', 'code') : http_status
analytics.idv_arcgis_token_failure(
exception_class: exception.class.name,
exception_message: exception.message,
response_body_present: resp_body.present?,
response_body: resp_body,
response_status_code: http_status,
api_status_code: api_status_code,

# Include retry-specific data
retry_count:,
retry_max: options.max,
will_retry_in:,
)
end

attr_accessor :analytics

delegate :arcgis_api_username,
:arcgis_api_password,
:domain_name,
:arcgis_api_generate_token_url,
to: :"IdentityConfig.store"
end
end
34 changes: 34 additions & 0 deletions app/services/arcgis_api/auth/cache/raw_token_cache.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module ArcgisApi::Auth::Cache
class RawTokenCache
# @param [ActiveSupport::Cache::Store] cache
# @param [String] cache_key
def initialize(cache: nil, cache_key: nil)
@cache = cache || Rails.cache
@cache_key = cache_key || default_api_token_cache_key
end

# @return [Object,nil] Cached auth token
def token
raw_token = cache.read(cache_key)
raw_token || nil
end

# @param [Object,nil] cache_value the value to write to cache
# @param [Number] expires_at the hard expiration time in unix time (milliseconds)
def save_token(cache_value, expires_at)
cache.write(cache_key, cache_value, expires_at)
end

private

def default_api_token_cache_key
"#{arcgis_api_token_cache_key_prefix}:#{URI(arcgis_api_generate_token_url).host}"
end

delegate :arcgis_api_token_cache_key_prefix,
:arcgis_api_generate_token_url,
to: :"IdentityConfig.store"

attr_accessor :cache, :cache_key
end
end
8 changes: 8 additions & 0 deletions app/services/arcgis_api/auth/cache/token_cache_info_writer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module ArcgisApi::Auth::Cache
class TokenCacheInfoWriter < TokenCacheWriter
# @param [ArcgisApi::Auth::Token] cache_value the value to write to cache
def save_token(cache_value)
token_cache.save_token(cache_value, expires_at: cache_value.expires_at)
end
end
end
8 changes: 8 additions & 0 deletions app/services/arcgis_api/auth/cache/token_cache_raw_writer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module ArcgisApi::Auth::Cache
class TokenCacheRawWriter < TokenCacheWriter
# @param [ArcgisApi::Auth::Token] cache_value the value to write to cache
def save_token(cache_value)
token_cache.save_token(cache_value.token, expires_at: cache_value.expires_at)
end
end
end
31 changes: 31 additions & 0 deletions app/services/arcgis_api/auth/cache/token_cache_reader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module ArcgisApi::Auth::Cache
class TokenCacheReader
# @param [ArcgisApi::Auth::Cache::RawTokenCache] token_cache
def initialize(token_cache: nil)
@token_cache = token_cache || ArcgisApi::Auth::Cache::RawTokenCache.new
end

# @return [String,nil] auth token
def token
token_entry&.token
end

# Fetch, wrap, and return cache entry for ArcGIS API token
# @return [ArcgisApi::Auth::Token,nil] token, or nil if not present in cache
def token_entry
cache_entry = token_cache.token

if cache_entry.is_a?(String)
ArcgisApi::Auth::Token.new(
token: cache_entry,
)
elsif cache_entry.is_a?(ArcgisApi::Auth::Token)
cache_entry
end
end

protected

attr_accessor :token_cache
end
end
6 changes: 6 additions & 0 deletions app/services/arcgis_api/auth/cache/token_cache_writer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module ArcgisApi::Auth::Cache
class TokenCacheWriter < TokenCacheReader
# @param [ArcgisApi::Auth::Token] cache_value the value to write to cache
def save_token(cache_value); end
end
end
13 changes: 13 additions & 0 deletions app/services/arcgis_api/auth/refresh/always_refresh_strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module ArcgisApi::Auth::Refresh
# Always refreshes the token
class AlwaysRefreshStrategy < RefreshStrategy
# @param [ArcgisApi::Auth::Authentication] auth
# @param [ArcgisApi::Auth::Cache::TokenCacheWriter] cache
# @return [ArcgisApi::Auth::Token]
def call(auth:, cache:)
token_entry = auth.retrieve_token
cache.save_token(token_entry)
token_entry
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module ArcgisApi::Auth::Refresh
# Does not attempt to refresh a token when it's expired
class FetchWithoutRefreshStrategy < RefreshStrategy
# rubocop:disable Lint/UnusedMethodArgument
# @param [ArcgisApi::Auth::Authentication] auth
# @param [ArcgisApi::Auth::Cache::TokenCacheWriter] cache
# @return [ArcgisApi::Auth::Token,nil]
def call(auth:, cache:)
cache.token_entry
end
# rubocop:enable Lint/UnusedMethodArgument
end
end
13 changes: 13 additions & 0 deletions app/services/arcgis_api/auth/refresh/noop_refresh_strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module ArcgisApi::Auth::Refresh
# Does nothing, returns nil
class NoopRefreshStrategy < RefreshStrategy
# rubocop:disable Lint/UnusedMethodArgument
# @param [ArcgisApi::Auth::Authentication] auth
# @param [ArcgisApi::Auth::Cache::TokenCacheWriter] cache
# @return [nil]
def call(auth:, cache:)
nil
end
# rubocop:enable Lint/UnusedMethodArgument
end
end
16 changes: 16 additions & 0 deletions app/services/arcgis_api/auth/refresh/on_expire_refresh_strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module ArcgisApi::Auth::Refresh
# Refreshes a token when it's expired
class OnExpireRefreshStrategy < RefreshStrategy
# @param [ArcgisApi::Auth::Authentication] auth
# @param [ArcgisApi::Auth::Cache::TokenCacheWriter] cache
# @return [ArcgisApi::Auth::Token]
def call(auth:, cache:)
token_entry = cache.token_entry
if token_entry.nil? || token_entry.expired?
token_entry = auth.retrieve_token
cache.save_token(token_entry)
end
token_entry
end
end
end
9 changes: 9 additions & 0 deletions app/services/arcgis_api/auth/refresh/refresh_strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module ArcgisApi::Auth::Refresh
# Refreshes a token when it's expired
class RefreshStrategy
# @param [ArcgisApi::Auth::Authentication] auth
# @param [ArcgisApi::Auth::Cache::TokenCacheWriter] cache
# @return [ArcgisApi::Auth::Token,nil]
def call(auth:, cache:); end
end
end
Loading