diff --git a/app/models/api_entreprise_token.rb b/app/models/api_entreprise_token.rb index ff0dbf49e..a8d18a3c5 100644 --- a/app/models/api_entreprise_token.rb +++ b/app/models/api_entreprise_token.rb @@ -1,16 +1,18 @@ class APIEntrepriseToken - attr_reader :token + TokenError = Class.new(StandardError) def initialize(token) @token = token end - def roles - decoded_token["roles"] if token.present? + def token + raise TokenError, I18n.t("api_entreprise.errors.missing_token") if @token.blank? + + @token end def expired? - Time.zone.now.to_i >= decoded_token["exp"] if token.present? + decoded_token.key?("exp") && decoded_token["exp"] <= Time.zone.now.to_i end def role?(role) @@ -19,7 +21,14 @@ class APIEntrepriseToken private + def roles + Array(decoded_token["roles"]) + end + def decoded_token - JWT.decode(token, nil, false)[0] + @decoded_token ||= {} + @decoded_token[token] ||= JWT.decode(token, nil, false)[0] + rescue JWT::DecodeError => e + raise TokenError, e.message end end diff --git a/config/locales/api_entreprise.en.yml b/config/locales/api_entreprise.en.yml new file mode 100644 index 000000000..ee815c16e --- /dev/null +++ b/config/locales/api_entreprise.en.yml @@ -0,0 +1,4 @@ +en: + api_entreprise: + errors: + missing_token: "the API Entreprise token cannot be blank" diff --git a/config/locales/api_entreprise.fr.yml b/config/locales/api_entreprise.fr.yml new file mode 100644 index 000000000..f8d81a523 --- /dev/null +++ b/config/locales/api_entreprise.fr.yml @@ -0,0 +1,4 @@ +fr: + api_entreprise: + errors: + missing_token: "le jeton API Entreprise ne peut ĂȘtre vide" diff --git a/spec/models/api_entreprise_token_spec.rb b/spec/models/api_entreprise_token_spec.rb new file mode 100644 index 000000000..1480f7da3 --- /dev/null +++ b/spec/models/api_entreprise_token_spec.rb @@ -0,0 +1,120 @@ +describe APIEntrepriseToken, type: :model do + let(:api_entreprise_token) { APIEntrepriseToken.new(token) } + + describe "#token" do + subject { api_entreprise_token.token } + + context "without token" do + let(:token) { nil } + + it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) } + end + + context "with a blank token" do + let(:token) { "" } + + it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) } + end + + context "with an invalid token" do + let(:token) { "NOT-A-VALID-TOKEN" } + + it { expect(subject).to equal(token) } + end + + context "with a valid token" do + let(:token) { "eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiI2NjRkZWEyMS02YWFlLTQwZmYtYWM0Mi1kZmQ3ZGE4YjQ3NmUiLCJqdGkiOiJhcGktZW50cmVwcmlzZS1zdGFnaW5nIiwicm9sZXMiOlsiY2VydGlmaWNhdF9jbmV0cCIsInByb2J0cCIsImV0YWJsaXNzZW1lbnRzIiwicHJpdmlsZWdlcyIsInVwdGltZSIsImF0dGVzdGF0aW9uc19hZ2VmaXBoIiwiYWN0ZXNfaW5waSIsImJpbGFuc19pbnBpIiwiYWlkZXNfY292aWRfZWZmZWN0aWZzIiwiY2VydGlmaWNhdF9yZ2VfYWRlbWUiLCJhdHRlc3RhdGlvbnNfc29jaWFsZXMiLCJlbnRyZXByaXNlX2FydGlzYW5hbGUiLCJmbnRwX2NhcnRlX3BybyIsImNvbnZlbnRpb25zX2NvbGxlY3RpdmVzIiwiZXh0cmFpdHNfcmNzIiwiZXh0cmFpdF9jb3VydF9pbnBpIiwiY2VydGlmaWNhdF9hZ2VuY2VfYmlvIiwibXNhX2NvdGlzYXRpb25zIiwiZG9jdW1lbnRzX2Fzc29jaWF0aW9uIiwiZW9yaV9kb3VhbmVzIiwiYXNzb2NpYXRpb25zIiwiYmlsYW5zX2VudHJlcHJpc2VfYmRmIiwiZW50cmVwcmlzZXMiLCJxdWFsaWJhdCIsImNlcnRpZmljYXRfb3BxaWJpIiwiZW50cmVwcmlzZSIsImV0YWJsaXNzZW1lbnQiXSwic3ViIjoic3RhZ2luZyBkZXZlbG9wbWVudCIsImlhdCI6MTY0MTMwNDcxNCwidmVyc2lvbiI6IjEuMCIsImV4cCI6MTY4ODQ3NTUxNH0.xID66pIlMnBR5_6nG-GidFBzK4Tuuy5ZsWfkMEVB_Ek" } + + it { expect(subject).to equal(token) } + end + end + + describe "#role?" do + subject { api_entreprise_token.role?(role) } + + context "without token" do + let(:token) { nil } + let(:role) { "actes_inpi" } + + it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) } + end + + context "with a blank token" do + let(:token) { "" } + let(:role) { "actes_inpi" } + + it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) } + end + + context "with an invalid token" do + let(:token) { "NOT-A-VALID-TOKEN" } + let(:role) { "actes_inpi" } + + it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) } + end + + context "with a valid token" do + let(:token) { "eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiI2NjRkZWEyMS02YWFlLTQwZmYtYWM0Mi1kZmQ3ZGE4YjQ3NmUiLCJqdGkiOiJhcGktZW50cmVwcmlzZS1zdGFnaW5nIiwicm9sZXMiOlsiY2VydGlmaWNhdF9jbmV0cCIsInByb2J0cCIsImV0YWJsaXNzZW1lbnRzIiwicHJpdmlsZWdlcyIsInVwdGltZSIsImF0dGVzdGF0aW9uc19hZ2VmaXBoIiwiYWN0ZXNfaW5waSIsImJpbGFuc19pbnBpIiwiYWlkZXNfY292aWRfZWZmZWN0aWZzIiwiY2VydGlmaWNhdF9yZ2VfYWRlbWUiLCJhdHRlc3RhdGlvbnNfc29jaWFsZXMiLCJlbnRyZXByaXNlX2FydGlzYW5hbGUiLCJmbnRwX2NhcnRlX3BybyIsImNvbnZlbnRpb25zX2NvbGxlY3RpdmVzIiwiZXh0cmFpdHNfcmNzIiwiZXh0cmFpdF9jb3VydF9pbnBpIiwiY2VydGlmaWNhdF9hZ2VuY2VfYmlvIiwibXNhX2NvdGlzYXRpb25zIiwiZG9jdW1lbnRzX2Fzc29jaWF0aW9uIiwiZW9yaV9kb3VhbmVzIiwiYXNzb2NpYXRpb25zIiwiYmlsYW5zX2VudHJlcHJpc2VfYmRmIiwiZW50cmVwcmlzZXMiLCJxdWFsaWJhdCIsImNlcnRpZmljYXRfb3BxaWJpIiwiZW50cmVwcmlzZSIsImV0YWJsaXNzZW1lbnQiXSwic3ViIjoic3RhZ2luZyBkZXZlbG9wbWVudCIsImlhdCI6MTY0MTMwNDcxNCwidmVyc2lvbiI6IjEuMCIsImV4cCI6MTY4ODQ3NTUxNH0.xID66pIlMnBR5_6nG-GidFBzK4Tuuy5ZsWfkMEVB_Ek" } + + context "but an unfetchable role" do + let(:role) { "NOT-A-ROLE" } + + it { expect(subject).to be_falsey } + end + + context "and a fetchable role" do + let(:role) { "actes_inpi" } + + it { expect(subject).to be_truthy } + end + end + end + + describe "#expired?" do + subject { api_entreprise_token.expired? } + + context "without token" do + let(:token) { nil } + + it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) } + end + + context "with a blank token" do + let(:token) { "" } + + it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) } + end + + context "with an invalid token" do + let(:token) { "NOT-A-VALID-TOKEN" } + + it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) } + end + + context "with a valid not expiring token" do + # never expire + let(:token) { "eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiI2NjRkZWEyMS02YWFlLTQwZmYtYWM0Mi1kZmQ3ZGE4YjQ3NmUiLCJqdGkiOiJhcGktZW50cmVwcmlzZS1zdGFnaW5nIiwicm9sZXMiOlsiY2VydGlmaWNhdF9jbmV0cCIsInByb2J0cCIsImV0YWJsaXNzZW1lbnRzIiwicHJpdmlsZWdlcyIsInVwdGltZSIsImF0dGVzdGF0aW9uc19hZ2VmaXBoIiwiYWN0ZXNfaW5waSIsImJpbGFuc19pbnBpIiwiYWlkZXNfY292aWRfZWZmZWN0aWZzIiwiY2VydGlmaWNhdF9yZ2VfYWRlbWUiLCJhdHRlc3RhdGlvbnNfc29jaWFsZXMiLCJlbnRyZXByaXNlX2FydGlzYW5hbGUiLCJmbnRwX2NhcnRlX3BybyIsImNvbnZlbnRpb25zX2NvbGxlY3RpdmVzIiwiZXh0cmFpdHNfcmNzIiwiZXh0cmFpdF9jb3VydF9pbnBpIiwiY2VydGlmaWNhdF9hZ2VuY2VfYmlvIiwibXNhX2NvdGlzYXRpb25zIiwiZG9jdW1lbnRzX2Fzc29jaWF0aW9uIiwiZW9yaV9kb3VhbmVzIiwiYXNzb2NpYXRpb25zIiwiYmlsYW5zX2VudHJlcHJpc2VfYmRmIiwiZW50cmVwcmlzZXMiLCJxdWFsaWJhdCIsImNlcnRpZmljYXRfb3BxaWJpIiwiZW50cmVwcmlzZSIsImV0YWJsaXNzZW1lbnQiXSwic3ViIjoic3RhZ2luZyBkZXZlbG9wbWVudCIsImlhdCI6MTY0MTMwNDcxNCwidmVyc2lvbiI6IjEuMCJ9.6GvMpHhPXmRuY06YMym-kp_67tQhgHxDys3YIH58ws8" } + + it { expect(subject).to be_falsey } + end + + context "with a valid expiring token" do + include ActiveSupport::Testing::TimeHelpers + + # expire on Jul 4, 2023 14:58:34 + let(:token) { "eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiI2NjRkZWEyMS02YWFlLTQwZmYtYWM0Mi1kZmQ3ZGE4YjQ3NmUiLCJqdGkiOiJhcGktZW50cmVwcmlzZS1zdGFnaW5nIiwicm9sZXMiOlsiY2VydGlmaWNhdF9jbmV0cCIsInByb2J0cCIsImV0YWJsaXNzZW1lbnRzIiwicHJpdmlsZWdlcyIsInVwdGltZSIsImF0dGVzdGF0aW9uc19hZ2VmaXBoIiwiYWN0ZXNfaW5waSIsImJpbGFuc19pbnBpIiwiYWlkZXNfY292aWRfZWZmZWN0aWZzIiwiY2VydGlmaWNhdF9yZ2VfYWRlbWUiLCJhdHRlc3RhdGlvbnNfc29jaWFsZXMiLCJlbnRyZXByaXNlX2FydGlzYW5hbGUiLCJmbnRwX2NhcnRlX3BybyIsImNvbnZlbnRpb25zX2NvbGxlY3RpdmVzIiwiZXh0cmFpdHNfcmNzIiwiZXh0cmFpdF9jb3VydF9pbnBpIiwiY2VydGlmaWNhdF9hZ2VuY2VfYmlvIiwibXNhX2NvdGlzYXRpb25zIiwiZG9jdW1lbnRzX2Fzc29jaWF0aW9uIiwiZW9yaV9kb3VhbmVzIiwiYXNzb2NpYXRpb25zIiwiYmlsYW5zX2VudHJlcHJpc2VfYmRmIiwiZW50cmVwcmlzZXMiLCJxdWFsaWJhdCIsImNlcnRpZmljYXRfb3BxaWJpIiwiZW50cmVwcmlzZSIsImV0YWJsaXNzZW1lbnQiXSwic3ViIjoic3RhZ2luZyBkZXZlbG9wbWVudCIsImlhdCI6MTY0MTMwNDcxNCwidmVyc2lvbiI6IjEuMCIsImV4cCI6MTY4ODQ3NTUxNH0.xID66pIlMnBR5_6nG-GidFBzK4Tuuy5ZsWfkMEVB_Ek" } + + it "must be false when token has not expired yet" do + travel_to Time.zone.local(2021) do + expect(subject).to be_falsey + end + end + + it "must be true when token has expired" do + travel_to Time.zone.local(2025) do + expect(subject).to be_truthy + end + end + end + end +end