diff --git a/app/controllers/api/v1/registrant/base_controller.rb b/app/controllers/api/v1/registrant/base_controller.rb new file mode 100644 index 000000000..bc5fa21d7 --- /dev/null +++ b/app/controllers/api/v1/registrant/base_controller.rb @@ -0,0 +1,38 @@ +require 'rails5_api_controller_backport' +require 'auth_token/auth_token_decryptor' + +module Api + module V1 + module Registrant + class BaseController < ActionController::API + before_action :authenticate + + rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception| + error = {} + error[parameter_missing_exception.param] = ['parameter is required'] + response = { errors: [error] } + render json: response, status: :unprocessable_entity + end + + private + + def bearer_token + pattern = /^Bearer / + header = request.headers['Authorization'] + header.gsub(pattern, '') if header&.match(pattern) + end + + def authenticate + decryptor = AuthTokenDecryptor.create_with_defaults(bearer_token) + decryptor.decrypt_token + + if decryptor.valid? + sign_in decryptor.user + else + render json: { errors: ['Not authorized'] }, status: :unauthorized + end + end + end + end + end +end diff --git a/app/controllers/api/v1/registrant/domains_controller.rb b/app/controllers/api/v1/registrant/domains_controller.rb index be755b55c..96d767d38 100644 --- a/app/controllers/api/v1/registrant/domains_controller.rb +++ b/app/controllers/api/v1/registrant/domains_controller.rb @@ -3,10 +3,15 @@ require 'rails5_api_controller_backport' module Api module V1 module Registrant - class DomainsController < ActionController::API + class DomainsController < BaseController + def index + @domains = associated_domains(current_user) + render json: @domains + end def show - @domain = Domain.find_by(uuid: params[:uuid]) + domain_pool = associated_domains(current_user) + @domain = domain_pool.find_by(uuid: params[:uuid]) if @domain render json: @domain @@ -14,6 +19,17 @@ module Api render json: { errors: ["Domain not found"] }, status: :not_found end end + + private + + def associated_domains(user) + country_code, ident = user.registrant_ident.split('-') + + BusinessRegistryCache.fetch_associated_domains(ident, country_code) + rescue Soap::Arireg::NotAvailableError => error + Rails.logger.fatal("[EXCEPTION] #{error.to_s}") + user.domains + end end end end diff --git a/lib/auth_token/auth_token_creator.rb b/lib/auth_token/auth_token_creator.rb new file mode 100644 index 000000000..9fff8e5cd --- /dev/null +++ b/lib/auth_token/auth_token_creator.rb @@ -0,0 +1,41 @@ +class AuthTokenCreator + DEFAULT_VALIDITY = 2.hours + + attr_reader :user + attr_reader :key + attr_reader :expires_at + + def self.create_with_defaults(user) + new(user, Rails.application.config.secret_key_base, Time.now + DEFAULT_VALIDITY) + end + + def initialize(user, key, expires_at) + @user = user + @key = key + @expires_at = expires_at.utc.strftime('%F %T %Z') + end + + def hashable + { + user_ident: user.registrant_ident, + user_username: user.username, + expires_at: expires_at, + }.to_json + end + + def encrypted_token + encryptor = OpenSSL::Cipher::AES.new(256, :CBC) + encryptor.encrypt + encryptor.key = key + encrypted_bytes = encryptor.update(hashable) + encryptor.final + Base64.urlsafe_encode64(encrypted_bytes) + end + + def token_in_hash + { + access_token: encrypted_token, + expires_at: expires_at, + type: 'Bearer', + } + end +end diff --git a/lib/auth_token/auth_token_decryptor.rb b/lib/auth_token/auth_token_decryptor.rb new file mode 100644 index 000000000..be6bd99cd --- /dev/null +++ b/lib/auth_token/auth_token_decryptor.rb @@ -0,0 +1,43 @@ +class AuthTokenDecryptor + attr_reader :decrypted_data + attr_reader :token + attr_reader :key + attr_reader :user + + def self.create_with_defaults(token) + new(token, Rails.application.config.secret_key_base) + end + + def initialize(token, key) + @token = token + @key = key + end + + def decrypt_token + decipher = OpenSSL::Cipher::AES.new(256, :CBC) + decipher.decrypt + decipher.key = key + + base64_decoded = Base64.urlsafe_decode64(token.to_s) + plain = decipher.update(base64_decoded) + decipher.final + + @decrypted_data = JSON.parse(plain, symbolize_names: true) + rescue OpenSSL::Cipher::CipherError, ArgumentError + false + end + + def valid? + decrypted_data && valid_user? && still_valid? + end + + private + + def valid_user? + @user = RegistrantUser.find_by(registrant_ident: decrypted_data[:user_ident]) + @user&.username == decrypted_data[:user_username] + end + + def still_valid? + decrypted_data[:expires_at] > Time.now + end +end diff --git a/test/integration/api/registrant/registrant_api_domains_test.rb b/test/integration/api/registrant/registrant_api_domains_test.rb index bcf11cfc0..c892f088b 100644 --- a/test/integration/api/registrant/registrant_api_domains_test.rb +++ b/test/integration/api/registrant/registrant_api_domains_test.rb @@ -5,16 +5,25 @@ class RegistrantApiDomainsTest < ActionDispatch::IntegrationTest def setup super + @original_registry_time = Setting.days_to_keep_business_registry_cache + Setting.days_to_keep_business_registry_cache = 1 + travel_to Time.zone.parse('2010-07-05') + @domain = domains(:hospital) @registrant = @domain.registrant + @user = users(:registrant) + @auth_headers = { 'HTTP_AUTHORIZATION' => auth_token } end def teardown super + + Setting.days_to_keep_business_registry_cache = @original_registry_time + travel_back end def test_get_domain_details_by_uuid - get '/api/v1/registrant/domains/5edda1a5-3548-41ee-8b65-6d60daf85a37' + get '/api/v1/registrant/domains/5edda1a5-3548-41ee-8b65-6d60daf85a37', {}, @auth_headers assert_equal(200, response.status) domain = JSON.parse(response.body, symbolize_names: true) @@ -22,7 +31,7 @@ class RegistrantApiDomainsTest < ActionDispatch::IntegrationTest end def test_get_non_existent_domain_details_by_uuid - get '/api/v1/registrant/domains/random-uuid' + get '/api/v1/registrant/domains/random-uuid', {}, @auth_headers assert_equal(404, response.status) response_json = JSON.parse(response.body, symbolize_names: true) @@ -32,6 +41,10 @@ class RegistrantApiDomainsTest < ActionDispatch::IntegrationTest def test_root_returns_domain_list get '/api/v1/registrant/domains', {}, @auth_headers assert_equal(200, response.status) + + response_json = JSON.parse(response.body, symbolize_names: true) + array_of_domain_names = response_json.map { |x| x[:name] } + assert(array_of_domain_names.include?('hospital.test')) end def test_root_returns_401_without_authorization