Class: Aspera::OAuth::Factory
- Inherits:
-
Object
- Object
- Aspera::OAuth::Factory
- Includes:
- Singleton
- Defined in:
- lib/aspera/oauth/factory.rb
Overview
Factory to create tokens and manage their cache
Constant Summary collapse
- TOKEN_FIELD =
'access_token'
Instance Attribute Summary collapse
-
#parameters ⇒ Object
readonly
Returns the value of attribute parameters.
Class Method Summary collapse
-
.bearer_auth?(authorization) ⇒ Boolean
Check if the authorization contains a bearer token.
-
.bearer_authorization(token) ⇒ String
Format a token for use in Authorization header.
-
.bearer_token(authorization) ⇒ String
Extract only token from Authorization (remove scheme).
-
.cache_id(url, creator_class, *params) ⇒ String
Generate a unique cache id for a token creator.
-
.class_to_id(creator_class) ⇒ Symbol
Convert a class name to snake_case symbol.
-
.instance ⇒ Factory
Returns the singleton instance of Factory.
Instance Method Summary collapse
-
#create(**parameters) ⇒ Object
Create a token creator instance for the specified grant method.
-
#decode_token(token) ⇒ Hash?
Decode a token using all registered decoders.
-
#flush_tokens ⇒ void
Delete all existing tokens in cache.
-
#get_token_info(id) ⇒ Hash
Get token information from cache.
-
#persist_mgr ⇒ Object
Get or initialize the persistence manager.
-
#persist_mgr=(manager) ⇒ Object
Set the persistence manager for token caching.
-
#persisted_tokens ⇒ Array<Hash>
Retrieve all persisted tokens with their decoded information.
-
#register_decoder(method) ⇒ void
Register a bearer token decoder for inspecting token properties.
-
#register_token_creator(creator_class) ⇒ void
Register a token creation method.
Instance Attribute Details
#parameters ⇒ Object (readonly)
Returns the value of attribute parameters.
86 87 88 |
# File 'lib/aspera/oauth/factory.rb', line 86 def parameters @parameters end |
Class Method Details
.bearer_auth?(authorization) ⇒ Boolean
Check if the authorization contains a bearer token
36 37 38 |
# File 'lib/aspera/oauth/factory.rb', line 36 def bearer_auth?() return .start_with?(SPACE_BEARER_AUTH_SCHEME) end |
.bearer_authorization(token) ⇒ String
Format a token for use in Authorization header
29 30 31 |
# File 'lib/aspera/oauth/factory.rb', line 29 def (token) return "#{SPACE_BEARER_AUTH_SCHEME}#{token}" end |
.bearer_token(authorization) ⇒ String
Extract only token from Authorization (remove scheme)
43 44 45 46 |
# File 'lib/aspera/oauth/factory.rb', line 43 def bearer_token() Aspera.assert(bearer_auth?()){'not a bearer token, wrong prefix scheme'} return .delete_prefix(SPACE_BEARER_AUTH_SCHEME) end |
.cache_id(url, creator_class, *params) ⇒ String
Generate a unique cache id for a token creator
53 54 55 |
# File 'lib/aspera/oauth/factory.rb', line 53 def cache_id(url, creator_class, *params) return IdGenerator.from_list(PERSIST_CATEGORY_TOKEN, url, Factory.class_to_id(creator_class), params) end |
.class_to_id(creator_class) ⇒ Symbol
Convert a class name to snake_case symbol
60 61 62 |
# File 'lib/aspera/oauth/factory.rb', line 60 def class_to_id(creator_class) return creator_class.name.split('::').last.capital_to_snake.to_sym end |
.instance ⇒ Factory
Returns the singleton instance of Factory
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 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 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/aspera/oauth/factory.rb', line 14 class Factory include Singleton # prefix for persistency of tokens (simplify garbage collect) PERSIST_CATEGORY_TOKEN = 'token' # prefix for bearer authorization when in header SPACE_BEARER_AUTH_SCHEME = 'Bearer ' TOKEN_FIELD = 'access_token' private_constant :PERSIST_CATEGORY_TOKEN, :SPACE_BEARER_AUTH_SCHEME class << self # Format a token for use in Authorization header # @param token [String] The token alone # @return [String] Value suitable for Authorization header def (token) return "#{SPACE_BEARER_AUTH_SCHEME}#{token}" end # Check if the authorization contains a bearer token # @param authorization [String] The authorization header value # @return [Boolean] true if the authorization contains a bearer token, i.e. auth scheme is bearer def bearer_auth?() return .start_with?(SPACE_BEARER_AUTH_SCHEME) end # Extract only token from Authorization (remove scheme) # @param authorization [String] The authorization header value # @return [String] The bearer token without the scheme prefix def bearer_token() Aspera.assert(bearer_auth?()){'not a bearer token, wrong prefix scheme'} return .delete_prefix(SPACE_BEARER_AUTH_SCHEME) end # Generate a unique cache id for a token creator # @param url [String] Base URL of the OAuth server # @param creator_class [Class] Class of the token creator # @param params [Array] List of parameters (can be nested) to uniquely identify the token # @return [String] a unique cache identifier def cache_id(url, creator_class, *params) return IdGenerator.from_list(PERSIST_CATEGORY_TOKEN, url, Factory.class_to_id(creator_class), params) end # Convert a class name to snake_case symbol # @param creator_class [Class] The class to convert # @return [Symbol] snake_case version of class name def class_to_id(creator_class) return creator_class.name.split('::').last.capital_to_snake.to_sym end end private # Initialize the factory with default parameters and empty collections def initialize # persistency manager @persist = nil # token creation methods @token_type_classes = {} # list of lambda @decoders = [] # default parameters, others can be added by handlers @parameters = { # tokens older than this duration in sec. will be discarded from cache token_cache_max_age: 1800, # tokens valid for less than this duration in sec. will be regenerated token_refresh_threshold: 120 } end public attr_reader :parameters # Set the persistence manager for token caching # @param manager [Object] The persistence manager instance def persist_mgr=(manager) @persist = manager # cleanup expired tokens @persist.garbage_collect(PERSIST_CATEGORY_TOKEN, @parameters[:token_cache_max_age]) end # Get or initialize the persistence manager # @return [Object] The persistence manager instance def persist_mgr if @persist.nil? # use OAuth::Factory.instance.persist_mgr=PersistencyFolder.new) Log.log.debug('Not using persistency') # create NULL persistency class @persist = Class.new do def get(_x); nil; end; def delete(_x); nil; end; def put(_x, _y); nil; end; def garbage_collect(_x, _y); nil; end # rubocop:disable Style/Semicolon end.new end return @persist end # Delete all existing tokens in cache # @return [void] def flush_tokens persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN) end # Retrieve all persisted tokens with their decoded information # @return [Array<Hash>] Array of token information hashes def persisted_tokens data = persist_mgr.current_items(PERSIST_CATEGORY_TOKEN) data.each.map do |k, v| info = {id: k} info.merge!(JSON.parse(v)) rescue nil d = decode_token(info.delete(TOKEN_FIELD)) info.merge(d) if d info end end # Get token information from cache # @param id [String] identifier of token # @return [Hash] token internal information , including Date object for `expiration_date` def get_token_info(id) token_raw_string = persist_mgr.get(id) return if token_raw_string.nil? token_data = JSON.parse(token_raw_string) Aspera.assert_type(token_data, Hash) decoded_token = decode_token(token_data[TOKEN_FIELD]) info = {data: token_data} if decoded_token.is_a?(Hash) info[:decoded] = decoded_token # TODO: move date decoding to token decoder ? expiration_date = if decoded_token['expires_at'].is_a?(String) then Time.parse(decoded_token['expires_at']).to_time elsif decoded_token['exp'].is_a?(Integer) then Time.at(decoded_token['exp']) end unless expiration_date.nil? info[:expiration] = expiration_date info[:ttl_sec] = expiration_date - Time.now info[:expired] = info[:ttl_sec] < @parameters[:token_refresh_threshold] end end Log.dump(:token_info, info) return info end # Register a bearer token decoder for inspecting token properties # @param method [Proc] The decoder lambda/proc to register # @return [void] def register_decoder(method) @decoders.push(method) end # Decode a token using all registered decoders # @param token [String] The token to decode # @return [Hash, nil] Decoded token data or nil if no decoder succeeded def decode_token(token) @decoders.each do |decoder| result = decoder.call(token) rescue nil return result unless result.nil? end return end # Register a token creation method # @param creator_class [Class] The token creator class to register # @return [void] def register_token_creator(creator_class) Aspera.assert_type(creator_class, Class) id = Factory.class_to_id(creator_class) Log.log.debug{"registering creator for #{id}"} @token_type_classes[id] = creator_class end # Create a token creator instance for the specified grant method # @param parameters [Hash] Parameters including :grant_method and creator-specific options # @return [Object] An instance of the registered token creator class def create(**parameters) Aspera.assert_type(parameters, Hash) id = parameters[:grant_method] Aspera.assert(@token_type_classes.key?(id)){"token grant method unknown: '#{id}'"} create_parameters = parameters.reject{ |k, _v| k.eql?(:grant_method)} @token_type_classes[id].new(**create_parameters) end end |
Instance Method Details
#create(**parameters) ⇒ Object
Create a token creator instance for the specified grant method
187 188 189 190 191 192 193 |
# File 'lib/aspera/oauth/factory.rb', line 187 def create(**parameters) Aspera.assert_type(parameters, Hash) id = parameters[:grant_method] Aspera.assert(@token_type_classes.key?(id)){"token grant method unknown: '#{id}'"} create_parameters = parameters.reject{ |k, _v| k.eql?(:grant_method)} @token_type_classes[id].new(**create_parameters) end |
#decode_token(token) ⇒ Hash?
Decode a token using all registered decoders
166 167 168 169 170 171 172 |
# File 'lib/aspera/oauth/factory.rb', line 166 def decode_token(token) @decoders.each do |decoder| result = decoder.call(token) rescue nil return result unless result.nil? end return end |
#flush_tokens ⇒ void
This method returns an undefined value.
Delete all existing tokens in cache
112 113 114 |
# File 'lib/aspera/oauth/factory.rb', line 112 def flush_tokens persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN) end |
#get_token_info(id) ⇒ Hash
Get token information from cache
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/aspera/oauth/factory.rb', line 132 def get_token_info(id) token_raw_string = persist_mgr.get(id) return if token_raw_string.nil? token_data = JSON.parse(token_raw_string) Aspera.assert_type(token_data, Hash) decoded_token = decode_token(token_data[TOKEN_FIELD]) info = {data: token_data} if decoded_token.is_a?(Hash) info[:decoded] = decoded_token # TODO: move date decoding to token decoder ? expiration_date = if decoded_token['expires_at'].is_a?(String) then Time.parse(decoded_token['expires_at']).to_time elsif decoded_token['exp'].is_a?(Integer) then Time.at(decoded_token['exp']) end unless expiration_date.nil? info[:expiration] = expiration_date info[:ttl_sec] = expiration_date - Time.now info[:expired] = info[:ttl_sec] < @parameters[:token_refresh_threshold] end end Log.dump(:token_info, info) return info end |
#persist_mgr ⇒ Object
Get or initialize the persistence manager
98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/aspera/oauth/factory.rb', line 98 def persist_mgr if @persist.nil? # use OAuth::Factory.instance.persist_mgr=PersistencyFolder.new) Log.log.debug('Not using persistency') # create NULL persistency class @persist = Class.new do def get(_x); nil; end; def delete(_x); nil; end; def put(_x, _y); nil; end; def garbage_collect(_x, _y); nil; end # rubocop:disable Style/Semicolon end.new end return @persist end |
#persist_mgr=(manager) ⇒ Object
Set the persistence manager for token caching
90 91 92 93 94 |
# File 'lib/aspera/oauth/factory.rb', line 90 def persist_mgr=(manager) @persist = manager # cleanup expired tokens @persist.garbage_collect(PERSIST_CATEGORY_TOKEN, @parameters[:token_cache_max_age]) end |
#persisted_tokens ⇒ Array<Hash>
Retrieve all persisted tokens with their decoded information
118 119 120 121 122 123 124 125 126 127 |
# File 'lib/aspera/oauth/factory.rb', line 118 def persisted_tokens data = persist_mgr.current_items(PERSIST_CATEGORY_TOKEN) data.each.map do |k, v| info = {id: k} info.merge!(JSON.parse(v)) rescue nil d = decode_token(info.delete(TOKEN_FIELD)) info.merge(d) if d info end end |
#register_decoder(method) ⇒ void
This method returns an undefined value.
Register a bearer token decoder for inspecting token properties
159 160 161 |
# File 'lib/aspera/oauth/factory.rb', line 159 def register_decoder(method) @decoders.push(method) end |
#register_token_creator(creator_class) ⇒ void
This method returns an undefined value.
Register a token creation method
177 178 179 180 181 182 |
# File 'lib/aspera/oauth/factory.rb', line 177 def register_token_creator(creator_class) Aspera.assert_type(creator_class, Class) id = Factory.class_to_id(creator_class) Log.log.debug{"registering creator for #{id}"} @token_type_classes[id] = creator_class end |