diff --git a/modules/Auth/lib/AuthHandler.cpp b/modules/Auth/lib/AuthHandler.cpp index d5a871ab0..3a67ec4d8 100644 --- a/modules/Auth/lib/AuthHandler.cpp +++ b/modules/Auth/lib/AuthHandler.cpp @@ -192,12 +192,13 @@ TokenHandlingResult AuthHandler::handle_token(const ProvidedIdToken& provided_to return TokenHandlingResult::NO_CONNECTOR_AVAILABLE; } + types::authorization::ValidationResult validation_result = {types::authorization::AuthorizationStatus::Unknown}; if (!validation_results.empty()) { bool authorized = false; int i = 0; // iterate over validation results while (i < validation_results.size() && !authorized && !referenced_connectors.empty()) { - auto validation_result = validation_results.at(i); + validation_result = validation_results.at(i); if (validation_result.authorization_status == AuthorizationStatus::Accepted) { if (this->equals_master_pass_group_id(validation_result.parent_id_token)) { @@ -254,6 +255,16 @@ TokenHandlingResult AuthHandler::handle_token(const ProvidedIdToken& provided_to return TokenHandlingResult::ACCEPTED; } else { EVLOG_debug << "id_token could not be validated by any validator"; + // in case the validation was not successful, we need to notify the evse and transmit the validation result. + // This is especially required for Plug&Charge with ISO15118 in order to allow the ISO15118 state machine to + // escape the Authorize loop. We do this for all connectors that were referenced + if (provided_token.connectors.has_value()) { + const auto connectors = provided_token.connectors.value(); + std::for_each(connectors.begin(), connectors.end(), + [this, provided_token, validation_result](int32_t connector) { + this->notify_evse(connector, provided_token, validation_result); + }); + } return TokenHandlingResult::REJECTED; } } else { diff --git a/modules/Auth/tests/auth_tests.cpp b/modules/Auth/tests/auth_tests.cpp index 5068d6def..4a0ddfb6e 100644 --- a/modules/Auth/tests/auth_tests.cpp +++ b/modules/Auth/tests/auth_tests.cpp @@ -76,6 +76,7 @@ class AuthTest : public ::testing::Test { protected: std::unique_ptr auth_handler; std::unique_ptr auth_receiver; + testing::MockFunction send_callback_mock; StrictMock> mock_publish_token_validation_status_callback; @@ -93,6 +94,8 @@ class AuthTest : public ::testing::Test { << provided_token.id_token.value; if (validation_result.authorization_status == AuthorizationStatus::Accepted) { this->auth_receiver->authorize(evse_index); + } else { + this->auth_receiver->deauthorize(evse_index); } }); this->auth_handler->register_withdraw_authorization_callback([this](int32_t evse_index) { @@ -452,18 +455,18 @@ TEST_F(AuthTest, test_two_plugins_with_invalid_rfid) { Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted)); EXPECT_CALL(mock_publish_token_validation_status_callback, Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Rejected)); - - std::thread t3([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); }); - std::thread t4([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); }); - t1.join(); t2.join(); + std::thread t3([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); }); t3.join(); - t4.join(); ASSERT_TRUE(result1 == TokenHandlingResult::ACCEPTED); - ASSERT_TRUE(result2 == TokenHandlingResult::REJECTED); ASSERT_TRUE(this->auth_receiver->get_authorization(0)); + + std::thread t4([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); }); + t4.join(); + + ASSERT_TRUE(result2 == TokenHandlingResult::REJECTED); ASSERT_FALSE(this->auth_receiver->get_authorization(1)); } @@ -1105,6 +1108,33 @@ TEST_F(AuthTest, test_plug_and_charge) { ASSERT_FALSE(this->auth_receiver->get_authorization(1)); } +/// \brief Test PlugAndCharge +TEST_F(AuthTest, test_plug_and_charge_rejected) { + + // put authorization on evse so that we can check later if it was removed + this->auth_receiver->authorize(0); + ASSERT_TRUE(this->auth_receiver->get_authorization(0)); + + const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected); + this->auth_handler->handle_session_event(1, session_event); + + ProvidedIdToken provided_token; + provided_token.id_token = {INVALID_TOKEN, types::authorization::IdTokenType::eMAID}; + provided_token.authorization_type = types::authorization::AuthorizationType::PlugAndCharge; + provided_token.certificate.emplace("TestCertificate"); + provided_token.connectors = {1, 2}; + + EXPECT_CALL(mock_publish_token_validation_status_callback, + Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing)); + EXPECT_CALL(mock_publish_token_validation_status_callback, + Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Rejected)); + + const auto result = this->auth_handler->on_token(provided_token); + ASSERT_TRUE(result == TokenHandlingResult::REJECTED); + ASSERT_FALSE(this->auth_receiver->get_authorization(0)); + ASSERT_FALSE(this->auth_receiver->get_authorization(1)); +} + /// \brief Test empty intersection of referenced connectors in provided token and in validation result TEST_F(AuthTest, test_empty_intersection) { diff --git a/modules/EvseV2G/charger/ISO15118_chargerImpl.cpp b/modules/EvseV2G/charger/ISO15118_chargerImpl.cpp index 825cfa159..571625a0e 100644 --- a/modules/EvseV2G/charger/ISO15118_chargerImpl.cpp +++ b/modules/EvseV2G/charger/ISO15118_chargerImpl.cpp @@ -223,12 +223,10 @@ void ISO15118_chargerImpl::handle_authorization_response( } } else if (v2g_ctx->session.iso_selected_payment_option == iso1paymentOptionType_Contract) { v2g_ctx->session.certificate_status = certificate_status; + v2g_ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = (uint8_t)iso1EVSEProcessingType_Finished; - if (authorization_status == types::authorization::AuthorizationStatus::Accepted && - certificate_status == types::authorization::CertificateStatus::Accepted) { - v2g_ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = (uint8_t)iso1EVSEProcessingType_Finished; - } else { - v2g_ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = (uint8_t)iso1EVSEProcessingType_Ongoing; + if (authorization_status != types::authorization::AuthorizationStatus::Accepted) { + v2g_ctx->session.authorization_rejected = true; } } } diff --git a/modules/EvseV2G/iso_server.cpp b/modules/EvseV2G/iso_server.cpp index 100118de1..8afb82b13 100644 --- a/modules/EvseV2G/iso_server.cpp +++ b/modules/EvseV2G/iso_server.cpp @@ -1389,6 +1389,8 @@ static enum v2g_event handle_iso_authorization(struct v2g_connection* conn) { conn->ctx->session.auth_start_timeout = getmonotonictime(); res->ResponseCode = iso1responseCodeType_FAILED; } + } else if (conn->ctx->session.authorization_rejected == true) { + res->ResponseCode = iso1responseCodeType_FAILED; } error_out: diff --git a/modules/EvseV2G/v2g.hpp b/modules/EvseV2G/v2g.hpp index 065e33cc5..df7281bd1 100644 --- a/modules/EvseV2G/v2g.hpp +++ b/modules/EvseV2G/v2g.hpp @@ -323,6 +323,7 @@ struct v2g_context { uint8_t gen_challenge[16]; // for PnC bool verify_contract_cert_chain; // for PnC types::authorization::CertificateStatus certificate_status; // for PnC + bool authorization_rejected; // for PnC struct { bool valid_crt; diff --git a/modules/EvseV2G/v2g_ctx.cpp b/modules/EvseV2G/v2g_ctx.cpp index d15db3bc7..1233b1e71 100644 --- a/modules/EvseV2G/v2g_ctx.cpp +++ b/modules/EvseV2G/v2g_ctx.cpp @@ -284,6 +284,8 @@ void v2g_ctx_init_charging_values(struct v2g_context* const ctx) { } memset(ctx->session.gen_challenge, 0, sizeof(ctx->session.gen_challenge)); + ctx->session.authorization_rejected = false; + initialize_once = true; }