diff --git a/connectors/centreonPerlLibs/src/centreon/common/centreonvault.pm b/connectors/centreonPerlLibs/src/centreon/common/centreonvault.pm index a4445400e1..c9dde678de 100644 --- a/connectors/centreonPerlLibs/src/centreon/common/centreonvault.pm +++ b/connectors/centreonPerlLibs/src/centreon/common/centreonvault.pm @@ -35,7 +35,7 @@ sub new { my $self = bless \%options, $class; # mandatory options: # - logger: logger object - # - config_file: path of a JSON vault config file + # - config_file: either path of a JSON vault config file or the configuration as a perl hash. $self->{enabled} = 1; $self->{crypted_credentials} = 1; @@ -53,15 +53,19 @@ sub init { $self->check_options() or return undef; - # check if the following information is available - $self->{logger}->writeLogDebug("Reading Vault configuration from file " . $self->{config_file} . "."); - $self->{vault_config} = parse_json_file( 'json_file' => $self->{config_file} ); - if (defined($self->{vault_config}->{error_message})) { - $self->{logger}->writeLogError("Error while parsing " . $self->{config_file} . ": " - . $self->{vault_config}->{error_message}); - return undef; + # for unit test purpose, if the config is given as an hash, we don't try to read the config file. + if (ref $self->{config_file} eq ref {}) { + $self->{vault_config} = $self->{config_file}; + } else { + # check if the following information is available + $self->{logger}->writeLogDebug("Reading Vault configuration from file " . $self->{config_file} . "."); + $self->{vault_config} = parse_json_file('json_file' => $self->{config_file}); + if (defined($self->{vault_config}->{error_message})) { + $self->{logger}->writeLogError("Error while parsing " . $self->{config_file} . ": " + . $self->{vault_config}->{error_message}); + return undef; + } } - $self->check_configuration() or return undef; $self->{logger}->writeLogDebug("Vault configuration read. Name: " . $self->{vault_config}->{name} @@ -84,7 +88,7 @@ sub check_options { $self->{logger}->writeLogError("No config file given to the constructor. Centreonvault cannot be used."); return undef; } - if ( ! -f $self->{config_file} ) { + if ( ! -f $self->{config_file} and ref $self->{config_file} ne ref {}) { $self->{logger}->writeLogError("The given configuration file " . $self->{config_file} . " does not exist. Centreonvault cannot be used."); return undef; @@ -209,7 +213,7 @@ sub authenticate { $self->{curl_easy}->setopt(CURLOPT_POST, 1); $self->{curl_easy}->setopt(CURLOPT_POSTFIELDS, $post_data); $self->{curl_easy}->setopt(CURLOPT_POSTFIELDSIZE, length($post_data)); - $self->{curl_easy}->setopt(CURLOPT_WRITEDATA(), \$auth_result_json); + $self->{curl_easy}->setopt(CURLOPT_WRITEDATA(), \$self->{auth_result_json}); eval { $self->{curl_easy}->perform(); @@ -221,9 +225,9 @@ sub authenticate { $self->{logger}->writeLogInfo("Authentication to the vault passed." ); - my $auth_result_obj = transform_json_to_object($auth_result_json); + my $auth_result_obj = transform_json_to_object($self->{auth_result_json}); if (defined($auth_result_obj->{error_message})) { - $self->{logger}->writeLogError("Error while decoding JSON '$auth_result_json'. Message: " + $self->{logger}->writeLogError("Error while decoding JSON '$self->{auth_result_json}'. Message: " . $auth_result_obj->{error_message}); return undef; } @@ -240,7 +244,7 @@ sub authenticate { 'token' => $auth_result_obj->{auth}->{client_token}, 'expiration_epoch' => $expiration_epoch }; - + print("authent passed, token : $self->{auth}->{expiration_epoch}\n"); $self->{logger}->writeLogInfo("Authenticating worked. Token valid until " . localtime($self->{auth}->{expiration_epoch})); @@ -302,7 +306,7 @@ sub get_secret { # request_id # the result is a json string, convert it into an object - my $get_result_obj = centreon::vmware::common::transform_json_to_object($get_result_json); + my $get_result_obj = transform_json_to_object($get_result_json); if (defined($get_result_obj->{error_message})) { $self->{logger}->writeLogError("Error while decoding JSON '$get_result_json'. Message: " . $get_result_obj->{error_message}); @@ -330,7 +334,7 @@ sub transform_json_to_object { $json_as_object = decode_json($json_data); }; if ($@) { - return ('error_message' => "Could not decode JSON from '$json_data'. Reason: " . $@); + return ({'error_message' => "Could not decode JSON from '$json_data'. Reason: " . $@}); }; return($json_as_object); } @@ -341,10 +345,6 @@ sub parse_json_file { my $fh; my $json_data = ''; - if ( !defined($options{json_file}) ) { - return ('error_message' => "parse_json_file: json_file option is mandatory"); - } - my $json_file = $options{json_file}; open($fh, '<', $json_file) or return ('error_message' => "parse_json_file: Cannot open " . $json_file); diff --git a/connectors/centreonPerlLibs/t/centreonvault.t b/connectors/centreonPerlLibs/t/centreonvault.t index a2584b7fb7..9cf8aaa8de 100644 --- a/connectors/centreonPerlLibs/t/centreonvault.t +++ b/connectors/centreonPerlLibs/t/centreonvault.t @@ -3,41 +3,147 @@ use strict; use warnings; use Test2::V0; use Test2::Plugin::NoWarnings echo => 1; +use Test2::Tools::Compare qw{is like match}; +use Net::Curl::Easy qw(:constants); +use Data::Dumper; use FindBin; -use lib qw($FindBin::RealBin/../src); +use lib "$FindBin::RealBin/../src"; use centreon::common::centreonvault; use centreon::common::logger; +use JSON::XS; + my $vault; my $global_logger = centreon::common::logger->new(); -my @test_data = ( - {'logger' => undef, 'config_file' => undef, 'test' => '$error_message =~ /FATAL: No logger given to the constructor/'}, - {'logger' => $global_logger, 'config_file' => undef, 'test' => '$vault->{enabled} == 0'}, - {'logger' => $global_logger, 'config_file' => 'does_not_exist.json', 'test' => '$vault->{enabled} == 0'} -); - -for my $i (0..$#test_data) { - my $logger = $test_data[$i]->{logger}; - my $config_file = $test_data[$i]->{config_file}; - my $test = $test_data[$i]->{test}; - - use Data::Dumper; - #print("Test $i with logger " . Dumper($logger) ."\n"); - eval { - $vault = centreon::script::centreonvault->new( - ( - 'logger' => $logger, - 'config_file' => $config_file - ) - ); - }; - - my $error_message = defined($@) ? $@ : ''; - print("Test $i with vault " . Dumper($vault) ."\n"); - - ok (eval($test), "TEST CASE $i FAILED: '$test' with error message: '" . $error_message . "'" ); +#$global_logger->file_mode("/dev/null"); +# this is an exemple of configuration for vault, the decrypted role_id/secret_id both are "String-to-encrypt" +my $default_app_secret = 'SGVsbG8gd29ybGQsIGRvZywgY2F0LCBwdXBwaWVzLgo='; +my $vault_config_hash = { + "name" => "default", + "url" => "localhost", + "port" => 443, + "root_path" => "path", + "role_id" => "4vOkzIaIJ7yxGWmysGVYY9sYHDyDM1nEv1++jSx9eAHpj83J6aIjE5SPvvpF6kBu3JeFga7o6DDS2yC7jVPAwXsWiur+KUOQncPq0JtjiFojr9YkrO8x1w1dmQFq/RqYV/S/kUare8z6r6+RnAxwsA==", + "secret_id" => "4vOkzIaIJ7yxGWmysGVYY9sYHDyDM1nEv1++jSx9eAHpj83J6aIjE5SPvvpF6kBu3JeFga7o6DDS2yC7jVPAwXsWiur+KUOQncPq0JtjiFojr9YkrO8x1w1dmQFq/RqYV/S/kUare8z6r6+RnAxwsA==", + "salt" => "U2FsdA==" }; # for now the salt is not used, it will be used to check the data where correctly decrypted. + +sub test_new { + my @test_data = ( + { 'logger' => undef, 'config_file' => undef, 'test' => '$error_message =~ /FATAL: No logger given to the constructor/' }, + { 'logger' => $global_logger, 'config_file' => undef, 'test' => '$vault->{enabled} == 0' }, + { 'logger' => $global_logger, 'config_file' => 'does_not_exist.json', 'test' => '$vault->{enabled} == 0' } + ); + + for my $i (0 .. $#test_data) { + my $logger = $test_data[$i]->{logger}; + my $config_file = $test_data[$i]->{config_file}; + my $test = $test_data[$i]->{test}; + + + #print("Test $i with logger " . Dumper($logger) ."\n"); + eval { + $vault = centreon::common::centreonvault->new( + ( + 'logger' => $logger, + 'config_file' => $config_file + ) + ); + }; + my $error_message = defined($@) ? $@ : ''; + ok(eval($test), "'$test' should be true"); + } +} + +sub test_decrypt { + my $old_app_secret = $ENV{'APP_SECRET'}; + $ENV{'APP_SECRET'} = $default_app_secret; + $vault = centreon::common::centreonvault->new( + ( + 'logger' => $global_logger, + 'config_file' => $vault_config_hash + ) + ); + + is($vault->extract_and_decrypt(('data' => $vault_config_hash->{secret_id})), 'String-to-encrypt', 'extract_and_decrypt() worked'); + # I encrypted the string "String-to-encrypt" from the C++ implementation, and set it to secret_id and role_id + # the key to decrypt is set as an environment variable. + # the salt can used to encrypt again the data, so the script can be sure the decryption worked correctly, but this function is not implemented yet. + $ENV{'APP_SECRET'} = $old_app_secret; +} + +sub test_transform_json_to_object { + my $tests_cases = [ + { + json => '{"int": 12, "string": "A String with space", "array" : ["array-key", "string"]}', + result => { "int" => 12, "string" => "A String with space", "array" => [ "array-key", "string" ] }, + detail => "simple json can be decoded as a perl object" + }, + { + json => '"int": 12, "string": "A String with space", "array" : ["array-key", "string"]}', + result => { "error_message" => match(qr/^Could not decode JSON from/) }, + detail => "invalid json should generate an error" + }, + { + json => '', + result => { "error_message" => match(qr/^Could not decode JSON from.*'. Reason:/) }, + detail => "empty json" + }, + { + json => 'abcdef', + result => { "error_message" => match(qr/^Could not decode JSON from/) }, + detail => "simple string json" + }, + { + json => '{}', + result => {}, + detail => "empty json brace should make an empty object" + }, + ]; + + for my $test (@$tests_cases) { + is(centreon::common::centreonvault::transform_json_to_object($test->{json}), $test->{result}, $test->{detail}); + } + } -done_testing(); +sub test_authenticate { + no warnings 'prototype'; + $vault = centreon::common::centreonvault->new( + ( + 'logger' => $global_logger, + 'config_file' => $vault_config_hash + ) + ); + print "opt : " . CURLOPT_WRITEDATA . "\n"; + + my $mock = mock 'Net::Curl::Easy'; + /* @TODO: we can find the link to the variable in the setopt when the opt number is the good one + we can either set the value (the effective mock action) in the setopt or in the perform, but the perform will force use to store that value somewhere temporaly. + */ + $mock->override('perform' => sub($) { + my $self = shift; + $vault->{auth_result_json} = '{"auth":{"lease_duration":"13455", "client_token":"ImAToken"}}'; + }, + 'setopt' => sub($$$) { + if ($_[1] == 10001) { + $_[2] = '{"auth":{"lease_duration":"13455", "client_token":"ImAToken"}}'; + } + print "opt : " . Dumper(@_) . "\n"; + } + ); + + $vault->authenticate(); + print $vault->{auth}->{token}; +} + +sub main { + #test_new(); + #test_decrypt(); + #test_transform_json_to_object(); + test_authenticate(); + + done_testing(); +} +&main; \ No newline at end of file diff --git a/connectors/centreonPerlLibs/t/exemple-file.json b/connectors/centreonPerlLibs/t/exemple-file.json new file mode 100644 index 0000000000..13174fd07c --- /dev/null +++ b/connectors/centreonPerlLibs/t/exemple-file.json @@ -0,0 +1,7 @@ +{ + "int": 12, + "string": "A String with space", + "array" : [ + "array-key", "string" + ] +} \ No newline at end of file