git.fiddlerwoaroof.com
token_test.cc
7684972a
 /* Copyright (C) CJ Affiliate
  *
  * You may use, distribute and modify this code under  the
  * terms of the  GNU General Public License  version 2  or
  * later.
  *
  * You should have received a copy of the license with this
  * file. If not, you will find a copy in the "LICENSE" file
  * at https://github.com/cjdev/dual-control.
  */
 
bef74c28
 #include <memory>
8ab94c71
 #include <cstring>
69be5e4e
 #include <pwd.h>
8ab94c71
 #include <cstdio>
69be5e4e
 #include <sys/stat.h>
4bf199d2
 #include <fstream>
215ea751
 #include <sstream>
69be5e4e
 
de34378f
 #include "token.h"
1c7f8bf0
 #include "test_util.h"
bef74c28
 #include "user.h"
215ea751
 #include "sys_fstream.h"
194e6869
 #include "generator.h"
b6eb15ba
 #include "base32.h"
c1e18905
 #include "typealiases.h"
8ddb4fe9
 
0e218820
 class fake_user : public user_ifc
 {
 private:
     std::string home_directory_;
 public:
8ddb4fe9
     fake_user() {}
bb783d31
     fake_user (const std::string &home_directory) :
         home_directory_ (home_directory)
0e218820
     {
     }
af3a4cd4
     std::string home_directory() const override
0e218820
     {
         return home_directory_;
     }
bef74c28
 };
 
d10906ee
 class mock_write_fstreams: public fstreams_ifc
 {
 private:
     mutable std::string captured_filename_;
     mutable std::shared_ptr<std::ostringstream> capture_stream_;
 public:
b6eb15ba
     pstream open_fstream (const std::string &file_path) const override
     {
a120158c
         pstream result = std::make_shared<std::istringstream> ("");
b6eb15ba
         std::string badinator (1, '\0');
         result->read (&badinator[0], badinator.size());
         return result;
     }
d10906ee
     postream open_ofstream (const std::string &file_path,
fefe31f5
                             std::ios_base::openmode mode) const override
d10906ee
     {
         captured_filename_ = file_path;
         capture_stream_ = std::make_shared<std::ostringstream>();
         return capture_stream_;
     }
     std::string captured_filename()
     {
         return captured_filename_;
     }
     std::string captured_written()
     {
         return capture_stream_->str();
     }
af3a4cd4
 };
 
bc6c3d35
 class fake_fstreams : public fstreams_ifc
 {
 private:
     std::string expected_file_path_;
     std::string file_contents_;
 public:
     fake_fstreams (const std::string &expected_file_path,
                    const std::string &file_contents)
         : expected_file_path_ (expected_file_path),
           file_contents_ (file_contents) {}
af3a4cd4
     pstream open_fstream (const std::string &file_path) const override
bc6c3d35
     {
         if (file_path == expected_file_path_) {
             return fstreams::pstream (new std::istringstream (file_contents_));
         } else {
             return fstreams_ifc::open_fstream (file_path);
215ea751
         }
bc6c3d35
 
     }
215ea751
 };
 
b6eb15ba
 class fake_rand : public random_source_ifc
 {
 public:
     fake_rand ()
     {}
 
     std::vector<uint8_t> get_random_bytes (int length) const override
     {
         return {};
     }
 };
 
 class fake_rand_with_specified_result : public random_source_ifc
 {
 private:
     octet_vector expected_bytes_;
 public:
     fake_rand_with_specified_result (octet_vector expected_bytes)
         : expected_bytes_ (expected_bytes)
     {}
 
     std::vector<uint8_t> get_random_bytes (int length) const override
     {
         return expected_bytes_;
     }
 };
 
194e6869
 class fake_totp_generator : public token_generator_ifc
 {
 private:
     std::string expected_token = "<unspecified>";
e57144d3
     std::string key = "<unset>";
194e6869
 public:
43889d2c
     fake_totp_generator (std::string expected_token = "<unspecified>") :
         expected_token (expected_token)
     {}
40fd6c82
     std::string generate_token (const octet_vector &key) const override
8c62e61c
     {
194e6869
         return expected_token;
     }
 };
 
0e218820
 int reads_from_the_right_file ()
 {
cb403154
     //given
bb783d31
     std::string home_directory = "/somedir";
215ea751
     // hardcoded file name is .dual_control in the user's home directory
     std::string token_file = home_directory + "/.dual_control";
c895ed8a
     std::string token ("AAAAAAAAAAAAAAAA");
43889d2c
 
8c62e61c
     fstreams test_streams (fstreams::delegate (new fake_fstreams (token_file,
                            token)));
     totp_generator generator (totp_generator::delegate (new
                               fake_totp_generator (token)));
a120158c
     random_source fake_rand (random_source::delegate (new class fake_rand));
215ea751
 
     //file_reader test_file_reader (file_reader::delegate (new fake_file_reader));
bb783d31
     user test_user (user::delegate (new fake_user (home_directory)));
b6eb15ba
     tokens supplier (tokens::create (test_streams, generator, fake_rand));
cb403154
 
     //when
0e218820
     std::string actual = supplier.token (test_user);
69be5e4e
 
     //then
215ea751
     check (actual == token, "token does not match");
69be5e4e
     succeed();
 }
 
43794015
 int returns_empty_string_if_file_open_fail()
 {
8ddb4fe9
     //given
215ea751
     std::string home_directory = "/somedir";
     // hardcoded file name is .dual_control in the user's home directory
     std::string token_file = home_directory + "/.not_dual_control";
bc6c3d35
     fstreams test_streams (fstreams::delegate (new fake_fstreams (token_file,
                            "654321")));
8c62e61c
     totp_generator generator (totp_generator::delegate (new
                               fake_totp_generator ()));
a120158c
     random_source fake_rand (random_source::delegate (new class fake_rand));
194e6869
 
215ea751
     user test_user (user::delegate (new fake_user (home_directory)));
b6eb15ba
     tokens supplier (tokens::create (test_streams, generator, fake_rand));
215ea751
 
8ddb4fe9
     //when
     std::string actual = supplier.token (test_user);
 
     //then
43794015
     check (actual == "", "should have returned empty string");
8ddb4fe9
     succeed();
 }
 
c05e07a6
 int returns_empty_string_if_file_too_short()
 {
     //given
     std::string home_directory = "/somedir";
     // hardcoded file name is .dual_control in the user's home directory
     std::string token_file = home_directory + "/.dual_control";
     // we want a 40-byte key, so we need a 64-byte base32-encoded file.
c895ed8a
     std::string token ("AAAAAAAAAAAAAAA");
c05e07a6
 
8c62e61c
     fstreams test_streams (fstreams::delegate (new fake_fstreams (token_file,
                            token)));
     totp_generator generator (totp_generator::delegate (new
                               fake_totp_generator (token)));
a120158c
     random_source fake_rand (random_source::delegate (new class fake_rand));
c05e07a6
 
     //file_reader test_file_reader (file_reader::delegate (new fake_file_reader));
     user test_user (user::delegate (new fake_user (home_directory)));
b6eb15ba
     tokens supplier (tokens::create (test_streams, generator, fake_rand));
c05e07a6
 
     //when
     std::string actual = supplier.token (test_user);
 
     //then
     check (actual == "", "should have returned empty string");
     succeed();
 }
 
b6eb15ba
 int writes_the_key ()
d10906ee
 {
af3a4cd4
     // given
d10906ee
     std::string home_directory ("/somedir");
     user test_user (user::delegate (new fake_user (home_directory)));
     mock_write_fstreams *mockfs (new mock_write_fstreams);
     fstreams test_streams{fstreams::delegate (mockfs)};
8c62e61c
     totp_generator generator (totp_generator::delegate (new
                               fake_totp_generator ()));
d10906ee
     std::string token ("token");
a120158c
     random_source fake_rand (random_source::delegate (new class fake_rand));
b6eb15ba
     tokens tokens (tokens::create (test_streams, generator, fake_rand));
af3a4cd4
 
     //when
decf9568
     tokens.save (test_user, token);
af3a4cd4
 
d10906ee
     // then
af3a4cd4
     std::ostringstream temp;
     temp << token << std::endl;
     std::string expected_written = temp.str();
d10906ee
     std::string expected_filename (home_directory + "/.dual_control");
8c62e61c
     check (mockfs->captured_filename() == expected_filename,
            "filename does not match");
     check (mockfs->captured_written() == expected_written,
            "token does not match");
d10906ee
     succeed();
af3a4cd4
 }
 
b6eb15ba
 int ensure_key_creates_key_file_if_not_exists ()
 {
     // given
     std::string home_directory ("/somedir");
     user test_user (user::delegate (new fake_user (home_directory)));
     mock_write_fstreams *mockfs (new mock_write_fstreams);
 
     fstreams test_streams{fstreams::delegate (mockfs)};
 
a120158c
     totp_generator generator (totp_generator::delegate (new
                               fake_totp_generator ()));
b6eb15ba
 
     std::vector<uint8_t> random_bytes {4,2,4, 2,4,  2,4,2, 4,2};
a120158c
     random_source fake_rand (random_source::delegate (new
                              fake_rand_with_specified_result (random_bytes)));
b6eb15ba
     tokens tokens (tokens::create (test_streams, generator, fake_rand));
 
     //when
     std::string actual = tokens.ensure_key (test_user);
 
     base32 codec;
a120158c
     octet_vector actual_decoded = codec.decode (actual);
b6eb15ba
     // then
     std::string expected_filename (home_directory + "/.dual_control");
     check (mockfs->captured_filename() == expected_filename,
            "filename does not match");
     check (random_bytes == actual_decoded,
            "key does not match");
     succeed();
 }
 
 int ensure_key_reads_key_file_if_exists ()
 {
     //given
     std::string home_directory ("/somedir");
     std::string key_file = home_directory + "/.dual_control";
     std::string key ("AAAAAAAAAAAAAAAA");
     user test_user (user::delegate (new fake_user (home_directory)));
     fstreams test_streams (fstreams::delegate (new fake_fstreams (key_file,
                            key)));
a120158c
     totp_generator generator (totp_generator::delegate (new
                               fake_totp_generator ()));
     random_source fake_rand (random_source::delegate (new class fake_rand));
b6eb15ba
     tokens tokens (tokens::create (test_streams, generator, fake_rand));
 
     //when
     std::string actual = tokens.ensure_key (test_user);
     //then
a120158c
     check (actual == key, "read key does not match given key");
b6eb15ba
     succeed();
 }
 
d4fb1155
 int generate_key_uses_random_source ()
 {
     // given
     user test_user (user::delegate (new fake_user ("/nowhere")));
     fstreams test_streams{fstreams::delegate (new fake_fstreams ("<>", "<>"))};
a120158c
     totp_generator generator (totp_generator::delegate (new
                               fake_totp_generator ()));
d4fb1155
 
     std::vector<uint8_t> random_bytes1 {4,2,4, 2,4,  2,4,2, 4,2};
a120158c
     random_source fake_rand1 (random_source::delegate (new
                               fake_rand_with_specified_result (random_bytes1)));
d4fb1155
     tokens tokens1 (tokens::create (test_streams, generator, fake_rand1));
 
     std::vector<uint8_t> random_bytes2 {1,2,1, 2,1,  1,2,1, 2,1};
a120158c
     random_source fake_rand2 (random_source::delegate (new
                               fake_rand_with_specified_result (random_bytes2)));
d4fb1155
     tokens tokens2 (tokens::create (test_streams, generator, fake_rand2));
 
     //when
     std::string first_key = tokens1.generate_key();
     std::string second_key = tokens2.generate_key();
 
     // then
a120158c
     check (first_key != second_key,
            "keys generated from differing random data should not match");
d4fb1155
     succeed();
 }
 
0e218820
 int run_tests()
 {
43889d2c
     test (reads_from_the_right_file);
     test (returns_empty_string_if_file_open_fail);
c05e07a6
     test (returns_empty_string_if_file_too_short);
b6eb15ba
     test (ensure_key_creates_key_file_if_not_exists);
     test (ensure_key_reads_key_file_if_exists);
d4fb1155
     test (generate_key_uses_random_source);
de34378f
     succeed();
 }
 
0e218820
 int main (int argc, char *argv[])
 {
bef74c28
     return !run_tests();
de34378f
 }
0d8b9a17