blob: 196b1efcf1903c893d438dfc12706bfef9a79d55 [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "generator/generator_factory.h"
#include <memory>
#include <string>
#include <utility>
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/notification.h"
#include "async/async_context.h"
#include "claim/generator.h"
#include "claim/manifest_box.h"
#include "claim/manifest_box_assembler.h"
#include "claim/signer.h"
#include "crypto/hash.h"
#include "generator/generator.h"
#include "generator/generator_options.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "proto/generator.pb.h"
#include "proto/generator_info.pb.h"
#include "salt/salt_generator.h"
#include "testing/proto_test_utils.h"
namespace credentio {
namespace {
using ::absl_testing::StatusIs;
using ::credentio_testing::ParseTextProtoOrDie;
using ::testing::HasSubstr;
using ::testing::NotNull;
class FakeClaimSigner : public ClaimSigner {
public:
void Sign(absl::string_view claim, Options options,
absl::AnyInvocable<void(absl::StatusOr<SignResult>) &&> cb)
const override {
std::move(cb)(SignResult{.signature = "fake_sig"});
}
};
class TrackingManifestBoxAssembler : public ManifestBoxAssembler {
public:
explicit TrackingManifestBoxAssembler(std::string name, int* call_count)
: name_(std::move(name)), call_count_(call_count) {}
absl::StatusOr<ManifestBox> Assemble(
const GenerateManifestStoreRequest& request) const override {
(*call_count_)++;
return absl::InternalError(name_);
}
private:
std::string name_;
int* call_count_;
};
class CountingSaltGenerator : public SaltGenerator {
public:
absl::StatusOr<std::string> Generate() const override {
++call_count_;
return "0123456789abcdef";
}
int call_count() const { return call_count_; }
private:
mutable int call_count_ = 0;
};
TEST(GeneratorFactoryTest, ValidateOptionsRejectsNullSigningClaimSigner) {
FakeClaimSigner template_signer;
GeneratorOptions options;
options.signing_claim_signer = nullptr;
options.template_claim_signer = &template_signer;
EXPECT_THAT(CreateGenerator(std::move(options)),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("signing_claim_signer is required")));
}
TEST(GeneratorFactoryTest, ValidateOptionsRejectsNullTemplateClaimSigner) {
FakeClaimSigner signing_signer;
GeneratorOptions options;
options.signing_claim_signer = &signing_signer;
options.template_claim_signer = nullptr;
EXPECT_THAT(CreateGenerator(std::move(options)),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("template_claim_signer is required")));
}
TEST(GeneratorFactoryTest, ValidateOptionsRejectsNullUuidGenerator)
__attribute__((no_sanitize("nullability"))) {
FakeClaimSigner signing_signer;
FakeClaimSigner template_signer;
GeneratorOptions options;
options.signing_claim_signer = &signing_signer;
options.template_claim_signer = &template_signer;
options.uuid_generator = nullptr;
EXPECT_THAT(CreateGenerator(std::move(options)),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("uuid_generator is required")));
}
TEST(GeneratorFactoryTest, ValidateOptionsRejectsNullHashFactory)
__attribute__((no_sanitize("nullability"))) {
FakeClaimSigner signing_signer;
FakeClaimSigner template_signer;
GeneratorOptions options;
options.signing_claim_signer = &signing_signer;
options.template_claim_signer = &template_signer;
options.hash_factory = nullptr;
EXPECT_THAT(CreateGenerator(std::move(options)),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("hash_factory is required")));
}
TEST(GeneratorFactoryTest, ValidateOptionsRejectsNullSaltGenerator)
__attribute__((no_sanitize("nullability"))) {
FakeClaimSigner signing_signer;
FakeClaimSigner template_signer;
GeneratorOptions options;
options.signing_claim_signer = &signing_signer;
options.template_claim_signer = &template_signer;
options.salt_generator = nullptr;
EXPECT_THAT(CreateGenerator(std::move(options)),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("salt_generator is required")));
}
TEST(GeneratorFactoryTest, ValidateOptionsRejectsNullManifestAssemblerFactory) {
FakeClaimSigner signing_signer;
FakeClaimSigner template_signer;
GeneratorOptions options;
options.signing_claim_signer = &signing_signer;
options.template_claim_signer = &template_signer;
options.manifest_assembler_factory = nullptr;
EXPECT_THAT(CreateGenerator(std::move(options)),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("manifest_assembler_factory is required")));
}
TEST(GeneratorFactoryTest, CreateGeneratorSuccess) {
FakeClaimSigner signing_signer;
FakeClaimSigner template_signer;
GeneratorOptions options;
options.generator_info.set_name("TestGenerator");
options.generator_info.set_spec_version("2.2.0");
options.signing_claim_signer = &signing_signer;
options.template_claim_signer = &template_signer;
auto generator_or = CreateGenerator(std::move(options));
ABSL_ASSERT_OK(generator_or);
EXPECT_THAT(*generator_or, NotNull());
}
TEST(GeneratorFactoryTest, DualModeGeneratorDelegation) {
FakeClaimSigner signing_signer;
FakeClaimSigner template_signer;
int signing_assemble_count = 0;
int template_assemble_count = 0;
GeneratorOptions options;
options.generator_info.set_name("TestGenerator");
options.generator_info.set_spec_version("2.2.0");
options.signing_claim_signer = &signing_signer;
options.template_claim_signer = &template_signer;
options.manifest_assembler_factory =
[&](const CreateManifestAssemblerOptions& opts)
-> absl::StatusOr<std::unique_ptr<ManifestBoxAssembler>> {
if (dynamic_cast<const TemplatedHasherFactory*>(opts.hash_factory) !=
nullptr) {
return std::make_unique<TrackingManifestBoxAssembler>(
"template_assembler", &template_assemble_count);
}
return std::make_unique<TrackingManifestBoxAssembler>(
"signing_assembler", &signing_assemble_count);
};
auto generator_or = CreateGenerator(std::move(options));
ABSL_ASSERT_OK(generator_or);
auto generator = std::move(*generator_or);
// GenerateManifestStore should delegate to the signing generator.
GenerateManifestStoreRequest gen_request;
absl::Notification gen_done;
absl::Status gen_status;
generator->GenerateManifestStore(
gen_request, AsyncContext(),
[&](absl::StatusOr<GenerateManifestStoreResponse> res) {
gen_status = res.status();
gen_done.Notify();
});
gen_done.WaitForNotification();
EXPECT_THAT(gen_status, StatusIs(absl::StatusCode::kInternal,
HasSubstr("signing_assembler")));
EXPECT_EQ(signing_assemble_count, 1);
EXPECT_EQ(template_assemble_count, 0);
// GenerateManifestStoreTemplate should delegate to the template generator.
absl::Notification template_done;
absl::Status template_status;
generator->GenerateManifestStoreTemplate(
gen_request, AsyncContext(),
[&](absl::StatusOr<ManifestStoreTemplate> res) {
template_status = res.status();
template_done.Notify();
});
template_done.WaitForNotification();
EXPECT_THAT(template_status, StatusIs(absl::StatusCode::kInternal,
HasSubstr("template_assembler")));
EXPECT_EQ(signing_assemble_count, 1);
EXPECT_EQ(template_assemble_count, 1);
// FinalizeManifestStore should delegate to the signing generator.
FinalizeManifestStoreRequest fin_request;
absl::Notification fin_done;
absl::Status fin_status;
generator->FinalizeManifestStore(
fin_request, AsyncContext(),
[&](absl::StatusOr<FinalizeManifestStoreResponse> res) {
fin_status = res.status();
fin_done.Notify();
});
fin_done.WaitForNotification();
EXPECT_THAT(fin_status, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("generator_context")));
}
TEST(GeneratorFactoryTest, SaltGeneratorUsedBySigningAndTemplateGenerators) {
FakeClaimSigner signing_signer;
FakeClaimSigner template_signer;
CountingSaltGenerator salt_generator;
GeneratorOptions options;
options.generator_info.set_name("TestGenerator");
options.generator_info.set_spec_version("2.2.0");
options.signing_claim_signer = &signing_signer;
options.template_claim_signer = &template_signer;
options.salt_generator = &salt_generator;
absl::StatusOr<std::unique_ptr<ManifestStoreGenerator>> generator =
CreateGenerator(std::move(options));
ABSL_ASSERT_OK(generator);
GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb(
hard_binding {
box_hash {
alg: "sha256"
boxes { names: "some name" hash: "some hash" }
}
}
actions {
created {
digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture"
}
}
)pb");
absl::Notification gen_done;
(*generator)
->GenerateManifestStore(
request, AsyncContext(),
[&](absl::StatusOr<GenerateManifestStoreResponse> res) {
gen_done.Notify();
});
gen_done.WaitForNotification();
// One salt each for the hard binding and actions assertions.
EXPECT_EQ(salt_generator.call_count(), 2);
absl::Notification template_done;
(*generator)
->GenerateManifestStoreTemplate(
request, AsyncContext(),
[&](absl::StatusOr<ManifestStoreTemplate> res) {
template_done.Notify();
});
template_done.WaitForNotification();
EXPECT_EQ(salt_generator.call_count(), 4);
}
} // namespace
} // namespace credentio