keyring-manager 0.8.1

Cross-platform library for managing passwords
Documentation
#[cfg(all(target_os = "android", feature = "keyring_manager_android_tests"))]
mod android;
#[cfg(all(target_os = "ios", feature = "keyring_manager_ios_tests"))]
mod ios;

use super::*;

////////////////////////////////////////////////////////////////////
// Common test functions

pub static TEST_APPLICATION: &str = "rust-keyring-test";
pub static TEST_SERVICE: &str = "test.keychain-rs\tio";
pub static TEST_USER: &str = "user@keychain-rs.io";
pub static TEST_USER2: &str = "foo@bar\tcom";
pub static TEST_ASCII_PASSWORD: &str = "my_password";
pub static TEST_NON_ASCII_PASSWORD: &str = "大根";

fn do_roundtrip_and_delete(manager: &KeyringManager, value: &str) -> String {
    manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.set_value(value))
        .unwrap();
    let out = manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.get_value())
        .unwrap();
    manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.delete_value())
        .unwrap();
    out
}

pub fn exec_test_escaped_password_input(manager: &KeyringManager) {
    let pass = "";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = "\0";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = "\t";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = "\n";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = "\r";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = " ";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = "\\n ";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = "\\n\r ";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = ";";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = "$";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = "\\";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(out, pass);

    let pass = "*";
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(pass, out);
}

pub fn exec_test_add_ascii_password(manager: &KeyringManager) {
    let pass = TEST_ASCII_PASSWORD;
    manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.set_value(pass))
        .unwrap();
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(pass, out);
}

pub fn exec_test_round_trip_ascii_password(manager: &KeyringManager) {
    let pass = TEST_ASCII_PASSWORD;
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(pass, out);
    assert!(manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.delete_value())
        .is_err());
}

pub fn exec_test_add_non_ascii_password(manager: &KeyringManager) {
    let pass = TEST_NON_ASCII_PASSWORD;
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(pass, out);
}

pub fn exec_test_round_trip_non_ascii_password(manager: &KeyringManager) {
    let pass = TEST_NON_ASCII_PASSWORD;
    let out = do_roundtrip_and_delete(manager, pass);
    assert_eq!(pass, out);
    assert!(manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.delete_value())
        .is_err());
}

pub fn exec_test_multiple(manager: &KeyringManager) {
    let password_1 = "大根";
    let password_2 = "0xE5A4A7E6A0B9"; // Above in hex string

    manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.set_value(password_1))
        .unwrap();
    manager
        .with_keyring(TEST_SERVICE, TEST_USER2, |kr| kr.set_value(password_2))
        .unwrap();
    let out1 = manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.get_value())
        .unwrap();
    manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.delete_value())
        .unwrap();
    let out2 = manager
        .with_keyring(TEST_SERVICE, TEST_USER2, |kr| kr.get_value())
        .unwrap();
    manager
        .with_keyring(TEST_SERVICE, TEST_USER2, |kr| kr.delete_value())
        .unwrap();

    assert_eq!(password_1, out1);
    assert_eq!(password_2, out2);

    manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.set_value(password_1))
        .unwrap();
    manager
        .with_keyring(TEST_SERVICE, TEST_USER2, |kr| kr.set_value(password_2))
        .unwrap();
    let out2 = manager
        .with_keyring(TEST_SERVICE, TEST_USER2, |kr| kr.get_value())
        .unwrap();
    manager
        .with_keyring(TEST_SERVICE, TEST_USER2, |kr| kr.delete_value())
        .unwrap();
    let out1 = manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.get_value())
        .unwrap();
    manager
        .with_keyring(TEST_SERVICE, TEST_USER, |kr| kr.delete_value())
        .unwrap();

    assert_eq!(password_1, out1);
    assert_eq!(password_2, out2);
}

/////
cfg_if! {
    if #[cfg(all(not(target_os = "android"), not(target_os = "ios"), test))] {
        // Native, non-mobile test harness for PlatformKeyringManager

        use serial_test::serial;
        use directories::ProjectDirs;
        use std::path::PathBuf;
        use std::sync::LazyLock;

        fn get_cache_dir() -> PathBuf {
            if let Some(my_proj_dirs) = ProjectDirs::from("org", "Keyring", "KeyRingTests") {
                PathBuf::from(my_proj_dirs.cache_dir())
            } else {
                PathBuf::from("./")
            }
        }

        static PLATFORM_KEYRING_MANAGER: LazyLock<KeyringManager> =
            LazyLock::new(new_test_platform_keyring_manager);

        static INSECURE_KEYRING_MANAGER: LazyLock<KeyringManager> =
            LazyLock::new(new_test_insecure_keyring_manager);

        fn new_test_platform_keyring_manager() -> KeyringManager {
            KeyringManager::new_secure(TEST_APPLICATION).unwrap()
        }

        fn new_test_insecure_keyring_manager() -> KeyringManager {
            KeyringManager::new_insecure(
                TEST_APPLICATION,
                &get_cache_dir().join("test_insecure_keyring"),
            )
            .unwrap()
        }

        cfg_if! {
            if #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] {
                #[test]
                #[serial]
                fn test_platform_escaped_password_input() {
                    exec_test_escaped_password_input(&PLATFORM_KEYRING_MANAGER);
                }

                #[test]
                #[serial]
                fn test_platform_add_ascii_password() {
                    exec_test_add_ascii_password(&PLATFORM_KEYRING_MANAGER);
                }

                #[test]
                #[serial]
                fn test_platform_round_trip_ascii_password() {
                    exec_test_round_trip_ascii_password(&PLATFORM_KEYRING_MANAGER);
                }

                #[test]
                #[serial]
                fn test_platform_add_non_ascii_password() {
                    exec_test_add_non_ascii_password(&PLATFORM_KEYRING_MANAGER);
                }

                #[test]
                #[serial]
                fn test_platform_round_trip_non_ascii_password() {
                    exec_test_round_trip_non_ascii_password(&PLATFORM_KEYRING_MANAGER);
                }

                #[test]
                #[serial]
                fn test_platform_multiple() {
                    exec_test_multiple(&PLATFORM_KEYRING_MANAGER);
                }
            } else {
                #[test]
                #[serial]
                fn test_platform_no_secure() {
                    assert!(KeyringManager::new_secure(TEST_APPLICATION).is_err());
                }
            }
        }

        #[test]
        #[serial]
        fn test_insecure_escaped_password_input() {
            exec_test_escaped_password_input(&INSECURE_KEYRING_MANAGER);
        }

        #[test]
        #[serial]
        fn test_insecure_add_ascii_password() {
            exec_test_add_ascii_password(&INSECURE_KEYRING_MANAGER);
        }

        #[test]
        #[serial]
        fn test_insecure_round_trip_ascii_password() {
            exec_test_round_trip_ascii_password(&INSECURE_KEYRING_MANAGER);
        }

        #[test]
        #[serial]
        fn test_insecure_add_non_ascii_password() {
            exec_test_add_non_ascii_password(&INSECURE_KEYRING_MANAGER);
        }

        #[test]
        #[serial]
        fn test_insecure_round_trip_non_ascii_password() {
            exec_test_round_trip_non_ascii_password(&INSECURE_KEYRING_MANAGER);
        }

        #[test]
        #[serial]
        fn test_insecure_multiple() {
            exec_test_multiple(&INSECURE_KEYRING_MANAGER);
        }
    }
}