Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth: Notify Evse also when authorization is rejected #698

Merged
merged 5 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion modules/Auth/lib/AuthHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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 {
Expand Down
42 changes: 36 additions & 6 deletions modules/Auth/tests/auth_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class AuthTest : public ::testing::Test {
protected:
std::unique_ptr<AuthHandler> auth_handler;
std::unique_ptr<FakeAuthReceiver> auth_receiver;
testing::MockFunction<bool(json message)> send_callback_mock;
StrictMock<MockFunction<void(const ProvidedIdToken& token, TokenValidationStatus status)>>
mock_publish_token_validation_status_callback;

Expand All @@ -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) {
Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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) {

Expand Down
8 changes: 3 additions & 5 deletions modules/EvseV2G/charger/ISO15118_chargerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions modules/EvseV2G/iso_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions modules/EvseV2G/v2g.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions modules/EvseV2G/v2g_ctx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading