Skip to content

Commit

Permalink
Merge pull request #11 from mikamai/strong_customer_auth
Browse files Browse the repository at this point in the history
Strong customer authentication interface
  • Loading branch information
sharKy25 authored Sep 4, 2019
2 parents fdf8b49 + f8d902e commit 3f74c24
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 30 deletions.
4 changes: 0 additions & 4 deletions lib/access/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ module Figo
class Access < Base
@dump_attributes = []

def initialize(session, json)
super(session, json)
end

# Figo ID of the access
# @return [String]
attr_accessor :id
Expand Down
49 changes: 49 additions & 0 deletions lib/access_method/model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require_relative '../base.rb'

module Figo
class AccessMethod < Base
@dump_attributes = %i[
id in_psd2_scope type supported_account_types configurable_consent
requires_account_identifiers customer_authentication_flows advice
credentials
]

def initialize(hash)
hash.keys.each do |key|
send("#{key}=", hash[key])
end
end

# @return [String] figo ID of the provider access method.
attr_accessor :id

# @return [String] Indicates whether payment accounts
# falling into the scope of the PSD2 are accessed via this method.
attr_accessor :in_psd2_scope

# @return [String] Enum: "API" "FINTS" "SCRAPING"
attr_accessor :type

# @return [Array] array of strings
# Enum: "Giro account" "Savings account" "Daily savings account"
# "Credit card" "Loan account" "PayPal" "Depot" "Unknown"
attr_accessor :supported_account_types

# @return [Boolean] Indicates whether consent configuration may be provided
attr_accessor :configurable_consent

# @return [Boolean] Indicates whether account identifiers have to be provided to connect this financial source.
attr_accessor :requires_account_identifiers

# @return [Array] array of strings Enum: "EMBEDDED_1FA" "EMBEDDED_2FA" "REDIRECT" "DECOUPLED"
attr_accessor :customer_authentication_flows

# @return [String] Any advice useful to instruct the user on what data to provide.
attr_accessor :advice

# @return [Object]
attr_accessor :credentials
end
end
52 changes: 38 additions & 14 deletions lib/bank/model.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,49 @@
# frozen_string_literal: true

require_relative '../base.rb'
require_relative '../access_method/model'
require_relative '../base'

module Figo
# Object representing a bank, i.e. an connection to a bank
class Bank < Base
@dump_attributes = [:sepa_creditor_id]
@dump_attributes = %i[
id name icon supported country language
access_methods bank_code bic
]

def initialize(session, json)
super(session, json)
def initialize(hash)
hash.keys.each do |key|
send("#{key}=", hash[key])
end
end

# Internal figo Connect bank ID
# @return [String]
attr_accessor :bank_id
# @return [String] figo ID of financial service provider.
attr_accessor :id

# @return [String] Name of the financial service provider.
attr_accessor :name

# @return [Object]
attr_accessor :icon

# @return [Boolean] Indicates if access to financial source is supported by the figo API.
attr_accessor :supported

# @return [String] Country in which the financial service provider operates.
attr_accessor :country

# @return [Object]
attr_accessor :language

# @return [Array] List of access methods available for this catalog item.
attr_reader :access_methods
def access_methods=(array)
@access_methods = array.map { |hash| Figo::AccessMethod.new hash }
end

# SEPA direct debit creditor ID
# @return [String]
attr_accessor :sepa_creditor_id
# @return [Object]
attr_accessor :bank_code

# This flag indicates whether the user has chosen to save the PIN on the figo Connect server
# @return [Boolean]
attr_accessor :save_pin
# @return [Object]
attr_accessor :bic
end
end
2 changes: 1 addition & 1 deletion lib/catalog/api_call.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def self.included(klass)
# @params objects [Enum] "banks" or "services", decide what is included in the api response.
# If not specified, it returns both
# @return [Catalog] modified bank object returned by server
def get_supported_payment_services(q: nil, country: nil, objects: nil)
def list_complete_catalog(q: nil, country: nil, objects: nil)
list_catalog(q, country, complete_path("#{@prefix}/catalog", objects))
end

Expand Down
15 changes: 13 additions & 2 deletions lib/catalog/model.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
# frozen_string_literal: true

require_relative '../base.rb'
require_relative '../bank/model'
require_relative '../base'

module Figo
# Object representing a Catalog of Banks and Services
class Catalog < Base
@dump_attributes = %i[banks services]

def initialize(session, hash)
hash.keys.each do |key|
send("#{key}=", hash[key])
end
end

# List of banks
# @return [Array]
attr_accessor :banks
attr_reader :banks
def banks=(array)
@banks = array.map { |hash| Figo::Bank.new(hash) }
end

# List of services
# @return [Array]
Expand Down
4 changes: 4 additions & 0 deletions lib/figo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ class Session
require_relative './payment/model.rb'
require_relative './payment/api_call.rb'

require_relative './strong_customer_authentication/auth_method/model.rb'
require_relative './strong_customer_authentication/synchronization_challenge/model.rb'
require_relative './strong_customer_authentication/api_call.rb'

require_relative './synchronization_status/model.rb'
require_relative './synchronization_status/api_call.rb'

Expand Down
8 changes: 5 additions & 3 deletions lib/helpers/https.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'net/http/persistent'

module Figo
# HTTPS class with certificate authentication and enhanced error handling.
class HTTPS < Net::HTTP::Persistent
Expand All @@ -20,8 +21,8 @@ def request(uri, req = nil, &block)
when Net::HTTPSuccess
return response
when Net::HTTPBadRequest
hash = JSON.parse(response.body)
raise Error.new(hash['error'], hash['error']['description'])
parsed_response = JSON.parse(response.body)
raise Error.new(parsed_response['error'], parsed_response['error']['description'])
when Net::HTTPUnauthorized
raise Error.new('unauthorized', 'Missing, invalid or expired access token.')
when Net::HTTPForbidden
Expand All @@ -33,7 +34,8 @@ def request(uri, req = nil, &block)
when Net::HTTPServiceUnavailable
raise Error.new('service_unavailable', 'Exceeded rate limit.')
else
raise Error.new('internal_server_error', 'We are very sorry, but something went wrong.')
parsed_response = JSON.parse(response.body)
raise Error.new(parsed_response['error'], parsed_response['error']['description'])
end
end
end
Expand Down
70 changes: 70 additions & 0 deletions lib/strong_customer_authentication/api_call.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

module Figo
# List synchronization challenges
#
# @param access_id [String] figo ID of the provider access.
# @param sync_id [String] figo ID of synchronization operation.
# @return [Array] an array of `SynchronizationChallenge` objects
def list_synchronization_challenges(access_id, sync_id)
path = "/rest/accesses/#{access_id}/syncs/#{sync_id}/challenges"
query_api_object SynchronizationChallenge, path, nil, 'GET', 'synchronization_challenges'
end

# Get synchronization challenge
#
# @param access_id [String] figo ID of the provider access.
# @param sync_id [String] ID of the access to be retrieved.
# @param challenge_id [String] ID of the access to be retrieved.
# @return [Object] a SynchronizationChallenge object
def get_synchronization_challenge(access_id, sync_id, challenge_id)
path = "/rest/accesses/#{access_id}/syncs/#{sync_id}/challenges/#{challenge_id}"
query_api_object SynchronizationChallenge, path, nil, 'GET'
end

# Solve synchronization challenge
#
# @param access_id [String] figo ID of the provider access.
# @param sync_id [String] ID of the access to be retrieved.
# @param challenge_id [String] ID of the access to be retrieved.
# @return [Object]
def solve_synchronization_challenge(access_id, sync_id, challenge_id)
path = "/rest/accesses/#{access_id}/syncs/#{sync_id}/challenges/#{challenge_id}/response"
query_api path, nil, 'POST'
end

# List payment challenges
#
# @param account_id [String] figo ID of account.
# @param payment_id [String] figo ID of the payment.
# @param init_d [String] figo ID of the payment initation.
# @return [Array] an array of `SynchronizationChallenge` objects
def list_payment_challenges(account_id, payment_id, init_d)
path = "/rest/accounts/#{account_id}/payments/#{payment_id}/init/#{init_id}/challenges"
query_api_object SynchronizationChallenge, path, nil, 'GET', 'synchronization_challenges'
end

# Get payment challenge
#
# @param account_id [String] figo ID of account.
# @param payment_id [String] figo ID of the payment.
# @param init_d [String] figo ID of the payment initation.
# @param challenge_id [String] figo ID of the challenge.
# @return [Object] a SynchronizationChallenge object
def get_payment_challenge(account_id, payment_id, init_d, challenge_id)
path = "/rest/accounts/#{account_id}/payments/#{payment_id}/init/#{init_id}/challenges"
query_api_object SynchronizationChallenge, path, nil, 'GET'
end

# Solve payment challenge
#
# @param account_id [String] figo ID of account.
# @param payment_id [String] figo ID of the payment.
# @param init_d [String] figo ID of the payment initation.
# @param challenge_id [String] figo ID of the challenge.
# @return [Object]
def solve_payment_challenge(account_id, payment_id, init_d, challenge_id)
path = "/rest/accounts/#{account_id}/payments/#{payment_id}/init/#{init_id}/challenges/#{challenge_id}"
query_api path, nil, 'POST'
end
end
27 changes: 27 additions & 0 deletions lib/strong_customer_authentication/auth_method/model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require_relative '../../base.rb'
module Figo
# Object representing the bank server synchronization status
class AuthMethod < Base
@dump_attributes = %i[
id medium_name type additional_info
]

# figo ID of TAN scheme.
# @return [String](TANSchemeID)
attr_accessor :id

# Description of the medium used to generate the authentication response.
# @return [String]
attr_accessor :medium_name

# Type of authentication method.
# @return [String]
attr_accessor :type

# Additional information on the authentication method as key/value pairs.
# @return [String]
attr_accessor :additional_info
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

require_relative '../../base.rb'
module Figo
# Object representing the bank server synchronization status
class SynchronizationChallenge < Base
@dump_attributes = %i[
id created_at type auth_methods format version data
additional_info label input_format max_length
min_length location message
]

# figo ID of the challenge.
# @return [String]
attr_accessor :id

# Time at which the challenge was created.
# @return [String]<ISO 8601>
attr_accessor :created_at

# Figo ID of the challenge.
# @return [String]
attr_accessor :type

# Array of objects (AuthMethod)
# @return [Array<AuthMethod>]
attr_reader :auth_methods
def auth_methods=(hash)
AuhtMethodhash.new(hash.delete_if { |_, v| v.nil? })
end

# Indicates how the data field should be interpreted.
# @return [String] (Enum: "PHOTO" "HHD" "TEXT" "HTML")
attr_accessor :format

# The version of the used challenge type. Left empty if it does not apply.
# @return [String]
attr_accessor :version

# The format of the data is specified in the format field.
# @return [String]
attr_accessor :data

# Provides additional information text to be displayed to the end-user.
# @return [String]
attr_accessor :additional_info

# To be used as label for the user input field in UIs.
# @return [String]
attr_accessor :label

# The expected input format or type for the response to the challenge.
# @return [String]
attr_accessor :input_format

# Maximum length of the response to be provided to the challenge.
# @return [Integer]
attr_accessor :max_length

# Maximum length of the response to be provided to the challenge.
# @return [Integer]
attr_accessor :min_length

# The URI to which the end user is redirected in OAuth cases.
# @return [String]
attr_accessor :location

# Instructional text to be displayed to the end-user.
# @return [String]
attr_accessor :message
end
end
6 changes: 3 additions & 3 deletions test/test_account_sync_and_setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ class FigoTest < MiniTest::Spec
## Account Setup & Synchronization
# Retrieve List of Supported Banks, Credit Cards, other Payment Services
def test_retrieve_list_of_supported_baks_cards_services
assert_nil figo_session.get_supported_payment_services('DE', 'whatever')
assert_nil figo_session.list_complete_catalog('DE', 'whatever')
end

# Retrieve List of Supported Credit Cards and other Payment Services
def test_retrieve_list_of_supported_cards_services
assert_nil figo_session.get_supported_payment_services('DE', 'services')
assert_nil figo_session.list_complete_catalog('DE', 'services')
end

# Retrieve List of all Supported Banks
def test_retreive_list_of_all_supported_banks
assert_raises(Figo::Error) { figo_session.get_supported_payment_services('DE', 'banks') }
assert_raises(Figo::Error) { figo_session.list_complete_catalog('DE', 'banks') }
end

# Retrieve Login Settings for a Bank or Service
Expand Down
Loading

0 comments on commit 3f74c24

Please sign in to comment.