Commits

Marcin Kasperski  committed 8d10d28

Preliminary implementation

  • Participants
  • Parent commits ddb486c

Comments (0)

Files changed (8)

File lib/Passwd/Keyring/OSXKeychain.pm

 
 use warnings;
 use strict;
-#use parent 'Keyring';
 
-require DynaLoader;
-#require AutoLoader;
-
-use base 'DynaLoader';
+use IPC::System::Simple qw(capturex systemx);
 
 =head1 NAME
 
 
 our $VERSION = '0.2502';
 
-bootstrap Passwd::Keyring::OSXKeychain $VERSION;
+=head1 WARNING
+
+I do not have Mac. I wrote the library mimicking actions
+of some python libraries and tested using mocks, but help
+of somebody able to test it on true Mac is really needed.
 
 =head1 SYNOPSIS
 
   different password set, a few apps may share the same group if they
   need to use the same passwords set)
 
+(OSXKeychain-specific)
+
+- security_prog - location of security program (/usr/bin/security by
+  default, possibility to overwrite is mostly needed for testing)
+
+- keychain - keychain to use (if not default)
+
 =cut
 
 sub new {
     my $self = {
         app => $opts{app} || 'Passwd::Keyring',
         group => $opts{group} || 'Passwd::Keyring unclassified passwords',
+        security => $opts{security_prog} || '/usr/bin/security',
+        keychain => $opts{keychain},
     };
     bless $self;
 
-    # TODO: catch and rethrow exceptions
-    my $name = Passwd::Keyring::OSXKeychain::_get_default_keyring_name();
-    croak ("OSXKeychain Keyring seems unavailable") unless $name;
+    unless( -x $self->{security_prog} ) {
+        croak("OSXKeychain not available: security program $self->{security_prog} is missing");
+    }
+    if($self->{keychain}) {
+        # Add .keychain suffix if missing
+        $self->{keychain} .= '.keychain' 
+          unless $self->{keychain} =~ /\.keychain$/;
+    } 
+
+    # Some test operation
+    my $reply = capturex($self->_make_sys_args(
+        "-q",
+        "show-keychain-info"));
 
     return $self;
 }
 
-=head2 set_password(username, password, domain)
+# Prepares args by prefixing with command and suffixing with keychain
+# if specified
+sub _make_sys_args {
+    my ($self, @args) = @_;
+    unshift @args, $self->{security_prog};
+    push @args, $self->{keychain} if $self->{keychain};
+    return @args;
+}
 
-Sets (stores) password identified by given domain for given user 
+=head2 set_password(username, password, realm)
+
+Sets (stores) password identified by given realm for given user 
 
 =cut
 
 sub set_password {
-    my ($self, $user_name, $user_password, $domain) = @_;
-    Passwd::Keyring::OSXKeychain::_set_password($user_name, $user_password, $domain,
-                                          $self->{app}, $self->{group});
+    my ($self, $user_name, $user_password, $realm) = @_;
+
+    # TODO: maybe use -l (label) instead of -D
+    systemx($self->_make_sys_args(
+        "-q", # quiet
+        "add-generic-password",
+        "-a", $user_name,
+        "-s", $realm,
+        "-D", $self->{group}, # "kind", can be used to match so let be
+        "-w", $user_password,
+        "-j", $self->{app}, # comment
+        "-A", # any app can access
+        "-U", # allow update
+        ));
 }
 
-=head2 get_password($user_name, $domain)
+sub _parse_password_from_find_output {
+    my ($text) = @_;
+
+}
+
+=head2 get_password($user_name, $realm)
 
 Reads previously stored password for given user in given app.
 If such password can not be found, returns undef.
 =cut
 
 sub get_password {
-    my ($self, $user_name, $domain) = @_;
-    my $pwd = Passwd::Keyring::OSXKeychain::_get_password($user_name, $domain,
-                                                    $self->{app}, $self->{group});
-    #return undef if (!defined($pwd)) or $pwd eq "";
-    return $pwd;
+    my ($self, $user_name, $realm) = @_;
+
+    my $reply = capturex($self->_make_sys_args(
+        "-q", # quiet
+        "find-generic-password",
+        "-a", $user_name,
+        "-s", $realm,
+        "-D", $self->{group}, # "kind", can be used to match so let be
+        "-g", # display the password
+        ));
+
+    return _parse_password_from_find_output($reply);
 }
 
-=head2 clear_password($user_name, $domain)
+=head2 clear_password($user_name, $realm)
 
 Removes given password (if present)
 
 =cut
 
 sub clear_password {
-    my ($self, $user_name, $domain) = @_;
-    return Passwd::Keyring::OSXKeychain::_clear_password(
-        $user_name, $domain, $self->{app}, $self->{group});
+    my ($self, $user_name, $realm) = @_;
+
+    my $reply = systemx($self->_make_sys_args(
+        "delete-generic-password",
+        "-a", $user_name,
+        "-s", $realm,
+        "-D", $self->{group}, # "kind", can be used to match so let be
+        ));
+
 }
 
 =head2 is_persistent

File t/01-set-and-get.t

 my $USER = 'John';
 my $PASSWORD = 'verysecret';
 
-$ring->set_password($USER, $PASSWORD, 'my@@domain');
+$ring->set_password($USER, $PASSWORD, 'my@@realm');
 
 ok( 1, "set_password works" );
 
-ok( $ring->get_password($USER, 'my@@domain') eq $PASSWORD, "get recovers");
+ok( $ring->get_password($USER, 'my@@realm') eq $PASSWORD, "get recovers");
 
-ok( $ring->clear_password($USER, 'my@@domain') eq 1, "clear_password removed one password" );
+ok( $ring->clear_password($USER, 'my@@realm') eq 1, "clear_password removed one password" );
 
-ok( !defined($ring->get_password($USER, 'my@@domain')), "no password after clear");
+ok( !defined($ring->get_password($USER, 'my@@realm')), "no password after clear");
 
-ok( $ring->clear_password($USER, 'my@@domain') eq 0, "clear_password again has nothing to clear" );
+ok( $ring->clear_password($USER, 'my@@realm') eq 0, "clear_password again has nothing to clear" );
 
-ok( $ring->clear_password("Non user", 'my@@domain') eq 0, "clear_password for unknown user has nothing to clear" );
-ok( $ring->clear_password("$USER", 'non domain') eq 0, "clear_password for unknown domain has nothing to clear" );
+ok( $ring->clear_password("Non user", 'my@@realm') eq 0, "clear_password for unknown user has nothing to clear" );
+ok( $ring->clear_password("$USER", 'non realm') eq 0, "clear_password for unknown realm has nothing to clear" );

File t/03-many-sets-and-gets.t

 
 use Passwd::Keyring::OSXKeychain;
 
-my $PSEUDO_DOMAIN = 'my@@domain';
-my $OTHER_DOMAIN = 'other domain';
+my $SOME_REALM = 'my@@realm';
+my $OTHER_REALM = 'other realm';
 
 my $ring = Passwd::Keyring::OSXKeychain->new(app=>"Passwd::Keyring::OSXKeychain", group=>"Unit tests");
 
 ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain',   'new() works' );
 
-$ring->set_password("Paul", "secret-Paul", $PSEUDO_DOMAIN);
-$ring->set_password("Gregory", "secret-Greg", $PSEUDO_DOMAIN);#
-$ring->set_password("Paul", "secret-Paul2", $OTHER_DOMAIN);
-$ring->set_password("Duke", "secret-Duke", $PSEUDO_DOMAIN);
+$ring->set_password("Paul", "secret-Paul", $SOME_REALM);
+$ring->set_password("Gregory", "secret-Greg", $SOME_REALM);#
+$ring->set_password("Paul", "secret-Paul2", $OTHER_REALM);
+$ring->set_password("Duke", "secret-Duke", $SOME_REALM);
 
 ok( 1, "set_password works" );
 
-ok( $ring->get_password("Paul", $PSEUDO_DOMAIN) eq 'secret-Paul', "get works");
+ok( $ring->get_password("Paul", $SOME_REALM) eq 'secret-Paul', "get works");
 
-ok( $ring->get_password("Gregory", $PSEUDO_DOMAIN) eq 'secret-Greg', "get works");
+ok( $ring->get_password("Gregory", $SOME_REALM) eq 'secret-Greg', "get works");
 
-ok( $ring->get_password("Paul", $OTHER_DOMAIN) eq 'secret-Paul2', "get works");
+ok( $ring->get_password("Paul", $OTHER_REALM) eq 'secret-Paul2', "get works");
 
-ok( $ring->get_password("Duke", $PSEUDO_DOMAIN) eq 'secret-Duke', "get works");
+ok( $ring->get_password("Duke", $SOME_REALM) eq 'secret-Duke', "get works");
 
-ok( $ring->clear_password("Paul", $PSEUDO_DOMAIN) eq 1, "clear_password removed 1");
+ok( $ring->clear_password("Paul", $SOME_REALM) eq 1, "clear_password removed 1");
 
-ok( ! defined($ring->get_password("Paul", $PSEUDO_DOMAIN)), "get works");
+ok( ! defined($ring->get_password("Paul", $SOME_REALM)), "get works");
 
-ok( $ring->get_password("Gregory", $PSEUDO_DOMAIN) eq 'secret-Greg', "get works");
+ok( $ring->get_password("Gregory", $SOME_REALM) eq 'secret-Greg', "get works");
 
-ok( $ring->get_password("Paul", $OTHER_DOMAIN) eq 'secret-Paul2', "get works");
+ok( $ring->get_password("Paul", $OTHER_REALM) eq 'secret-Paul2', "get works");
 
-ok( $ring->get_password("Duke", $PSEUDO_DOMAIN) eq 'secret-Duke', "get works");
+ok( $ring->get_password("Duke", $SOME_REALM) eq 'secret-Duke', "get works");
 
 
 # Note: cleanup is performed by test 04, we test passing data to

File t/04-recovering-in-sep-prog.t

 
 use Passwd::Keyring::OSXKeychain;
 
-my $PSEUDO_DOMAIN = 'my@@domain';
-my $OTHER_DOMAIN = 'other domain';
+my $SOME_REALM = 'my@@realm';
+my $OTHER_REALM = 'other realm';
 
 my $ring = Passwd::Keyring::OSXKeychain->new(app=>"Passwd::Keyring::OSXKeychain", group=>"Unit tests");
 
 ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain',   'new() works' );
 
-ok( ! defined($ring->get_password("Paul", $PSEUDO_DOMAIN)), "get works");
+ok( ! defined($ring->get_password("Paul", $SOME_REALM)), "get works");
 
-ok( $ring->get_password("Gregory", $PSEUDO_DOMAIN) eq 'secret-Greg', "get works");
+ok( $ring->get_password("Gregory", $SOME_REALM) eq 'secret-Greg', "get works");
 
-ok( $ring->get_password("Paul", $OTHER_DOMAIN) eq 'secret-Paul2', "get works");
+ok( $ring->get_password("Paul", $OTHER_REALM) eq 'secret-Paul2', "get works");
 
-ok( $ring->get_password("Duke", $PSEUDO_DOMAIN) eq 'secret-Duke', "get works");
+ok( $ring->get_password("Duke", $SOME_REALM) eq 'secret-Duke', "get works");
 
-ok( $ring->clear_password("Gregory", $PSEUDO_DOMAIN) eq 1, "clear clears");
+ok( $ring->clear_password("Gregory", $SOME_REALM) eq 1, "clear clears");
 
-ok( ! defined($ring->get_password("Gregory", $PSEUDO_DOMAIN)), "clear cleared");
+ok( ! defined($ring->get_password("Gregory", $SOME_REALM)), "clear cleared");
 
-ok( $ring->get_password("Paul", $OTHER_DOMAIN) eq 'secret-Paul2', "get works");
+ok( $ring->get_password("Paul", $OTHER_REALM) eq 'secret-Paul2', "get works");
 
-ok( $ring->get_password("Duke", $PSEUDO_DOMAIN) eq 'secret-Duke', "get works");
+ok( $ring->get_password("Duke", $SOME_REALM) eq 'secret-Duke', "get works");
 
-ok( $ring->clear_password("Paul", $OTHER_DOMAIN) eq 1, "clear clears");
+ok( $ring->clear_password("Paul", $OTHER_REALM) eq 1, "clear clears");
 
-ok( $ring->clear_password("Duke", $PSEUDO_DOMAIN) eq 1, "clear clears");
+ok( $ring->clear_password("Duke", $SOME_REALM) eq 1, "clear clears");
 
-ok( ! defined($ring->get_password("Paul", $PSEUDO_DOMAIN)), "clear cleared");
-ok( ! defined($ring->get_password("Duke", $PSEUDO_DOMAIN)), "clear cleared");
+ok( ! defined($ring->get_password("Paul", $SOME_REALM)), "clear cleared");
+ok( ! defined($ring->get_password("Duke", $SOME_REALM)), "clear cleared");
 
 
 

File t/05-many-sets-and-gets-with-name.t

 
 use Passwd::Keyring::OSXKeychain;
 
-my $DOMAIN_A = 'my@@domain';
-my $DOMAIN_B = 'bum trala la';
-my $DOMAIN_C = 'other domain';
+my $REALM_A = 'my@@realm';
+my $REALM_B = 'bum trala la';
+my $REALM_C = 'other realm';
 
 my $USER1 = "Paul Anton";
 my $USER2 = "Gżegąź";
 
 ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain',   'new() works' );
 
-$ring->set_password($USER1, $PWD1, $DOMAIN_B);
-$ring->set_password($USER2, $PWD2, $DOMAIN_B);#
-$ring->set_password($USER1, $PWD1_ALT, $DOMAIN_C);
-$ring->set_password($USER4, $PWD4, $DOMAIN_B);
+$ring->set_password($USER1, $PWD1, $REALM_B);
+$ring->set_password($USER2, $PWD2, $REALM_B);#
+$ring->set_password($USER1, $PWD1_ALT, $REALM_C);
+$ring->set_password($USER4, $PWD4, $REALM_B);
 
 ok( 1, "set_password works" );
 
-ok( $ring->get_password($USER1, $DOMAIN_B) eq $PWD1, "get works");
+ok( $ring->get_password($USER1, $REALM_B) eq $PWD1, "get works");
 
-ok( $ring->get_password($USER2, $DOMAIN_B) eq $PWD2, "get works");
+ok( $ring->get_password($USER2, $REALM_B) eq $PWD2, "get works");
 
-ok( $ring->get_password($USER1, $DOMAIN_C) eq $PWD1_ALT, "get works");
+ok( $ring->get_password($USER1, $REALM_C) eq $PWD1_ALT, "get works");
 
-ok( $ring->get_password($USER4, $DOMAIN_B) eq $PWD4, "get works");
+ok( $ring->get_password($USER4, $REALM_B) eq $PWD4, "get works");
 
-$ring->clear_password($USER1, $DOMAIN_B);
+$ring->clear_password($USER1, $REALM_B);
 ok(1, "clear_password works");
 
-ok( ! defined($ring->get_password($USER1, $DOMAIN_A)), "get works");
+ok( ! defined($ring->get_password($USER1, $REALM_A)), "get works");
 
-ok( ! defined($ring->get_password($USER2, $DOMAIN_A)), "get works");
+ok( ! defined($ring->get_password($USER2, $REALM_A)), "get works");
 
-ok( $ring->get_password($USER2, $DOMAIN_B) eq $PWD2, "get works");
+ok( $ring->get_password($USER2, $REALM_B) eq $PWD2, "get works");
 
-ok( $ring->get_password($USER1, $DOMAIN_C) eq $PWD1_ALT, "get works");
+ok( $ring->get_password($USER1, $REALM_C) eq $PWD1_ALT, "get works");
 
-ok( $ring->get_password($USER4, $DOMAIN_B) eq $PWD4, "get works");
+ok( $ring->get_password($USER4, $REALM_B) eq $PWD4, "get works");
 
-ok( $ring->clear_password($USER2, $DOMAIN_B) eq 1, "clear clears");
+ok( $ring->clear_password($USER2, $REALM_B) eq 1, "clear clears");
 
-ok( ! defined($ring->get_password($USER2, $DOMAIN_A)), "clear cleared");
+ok( ! defined($ring->get_password($USER2, $REALM_A)), "clear cleared");
 
-ok( $ring->get_password($USER1, $DOMAIN_C) eq $PWD1_ALT, "get works");
+ok( $ring->get_password($USER1, $REALM_C) eq $PWD1_ALT, "get works");
 
-ok( $ring->get_password($USER4, $DOMAIN_B) eq $PWD4, "get works");
+ok( $ring->get_password($USER4, $REALM_B) eq $PWD4, "get works");
 
-ok( $ring->clear_password($USER1, $DOMAIN_C) eq 1, "clear clears");
+ok( $ring->clear_password($USER1, $REALM_C) eq 1, "clear clears");
 
-ok( $ring->clear_password($USER4, $DOMAIN_B) eq 1, "clear clears");
+ok( $ring->clear_password($USER4, $REALM_B) eq 1, "clear clears");
 
-ok( ! defined($ring->get_password($USER1, $DOMAIN_C)), "clear cleared");
-ok( ! defined($ring->get_password($USER4, $DOMAIN_B)), "clear cleared");
+ok( ! defined($ring->get_password($USER1, $REALM_C)), "clear cleared");
+ok( ! defined($ring->get_password($USER4, $REALM_B)), "clear cleared");
 
 
 

File t/06-recovering-with-app-change.t

 use Passwd::Keyring::OSXKeychain;
 
 my $USER = "Herakliusz";
-my $DOMAIN = "test domain";
+my $REALM = "test realm";
 my $PWD = "arcytajne haslo";
 my $PWD2 = "inny sekret";
 
 
     ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain',   'new() works' );
 
-    ok( ! defined($ring->get_password($USER, $DOMAIN)), "initially unset");
+    ok( ! defined($ring->get_password($USER, $REALM)), "initially unset");
 
-    $ring->set_password($USER, $PWD, $DOMAIN);
+    $ring->set_password($USER, $PWD, $REALM);
     ok(1, "set password");
 
-    ok( $ring->get_password($USER, $DOMAIN) eq $PWD, "normal get works");
+    ok( $ring->get_password($USER, $REALM) eq $PWD, "normal get works");
 
     push @cleanups, sub {
-        ok( $ring->clear_password($USER, $DOMAIN) eq 1, "clearing");
+        ok( $ring->clear_password($USER, $REALM) eq 1, "clearing");
     };
 }
 
 
     ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain', 'second new() works' );
 
-    ok( $ring->get_password($USER, $DOMAIN) eq $PWD, "get from another ring with the same data works");
+    ok( $ring->get_password($USER, $REALM) eq $PWD, "get from another ring with the same data works");
 }
 
 # Only app changes
 
     ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain', 'third new() works' );
 
-    ok( $ring->get_password($USER, $DOMAIN) eq $PWD, "get from another ring with changed app but same group works");
+    ok( $ring->get_password($USER, $REALM) eq $PWD, "get from another ring with changed app but same group works");
 }
 
 # Only group changes
 
     ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain', 'third new() works' );
 
-    ok( ! defined($ring->get_password($USER, $DOMAIN)), "changing group forces another password");
+    ok( ! defined($ring->get_password($USER, $REALM)), "changing group forces another password");
 
     # To test whether original won't be spoiled
-    $ring->set_password($USER, $PWD2, $DOMAIN);
+    $ring->set_password($USER, $PWD2, $REALM);
 
     push @cleanups, sub {
-        ok( $ring->clear_password($USER, $DOMAIN) eq 1, "clearing");
+        ok( $ring->clear_password($USER, $REALM) eq 1, "clearing");
     };
 }
 
 
     ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain', 'third new() works' );
 
-    ok( ! defined($ring->get_password($USER, $DOMAIN)), "changing group and app forces another password");
+    ok( ! defined($ring->get_password($USER, $REALM)), "changing group and app forces another password");
 
 }
 
 
     ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain', 'second new() works' );
 
-    ok( $ring->get_password($USER, $DOMAIN) eq $PWD, "get original after changes in other group works");
+    ok( $ring->get_password($USER, $REALM) eq $PWD, "get original after changes in other group works");
 }
 
 # Cleanup

File t/07-ugly-chars.t

 
 my $UGLY_NAME = "Joh ## no ^^ »ąćęłóśż«";
 my $UGLY_PWD =  "«tajne hasło»";
-my $UGLY_DOMAIN = '«do»–main';
+my $UGLY_REALM = '«do»–main';
 
 my $ring = Passwd::Keyring::OSXKeychain->new(app=>"Passwd::OSXKeychain::Keyring unit tests", group=>"Ugly chars");
 
 ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain',   'new() works' );
 
-$ring->set_password($UGLY_NAME, $UGLY_PWD, $UGLY_DOMAIN);
+$ring->set_password($UGLY_NAME, $UGLY_PWD, $UGLY_REALM);
 
 ok( 1, "set_password with ugly chars works" );
 
-ok( $ring->get_password($UGLY_NAME, $UGLY_DOMAIN) eq $UGLY_PWD, "get works with ugly characters");
+ok( $ring->get_password($UGLY_NAME, $UGLY_REALM) eq $UGLY_PWD, "get works with ugly characters");
 
-ok( $ring->clear_password($UGLY_NAME, $UGLY_DOMAIN) eq 1, "clear clears");
+ok( $ring->clear_password($UGLY_NAME, $UGLY_REALM) eq 1, "clear clears");
 

File t/08-verylong-params.t

 
 my $USER = "A" x 256;
 my $PWD =  "B" x 256;
-my $DOMAIN = 'C' x 256;
+my $REALM = 'C' x 256;
 
 my $ring = Passwd::Keyring::OSXKeychain->new(
     app=>$APP, group=>$GROUP);
 
 ok( defined($ring) && ref $ring eq 'Passwd::Keyring::OSXKeychain',   'new() works with long params' );
 
-$ring->set_password($USER, $PWD, $DOMAIN);
+$ring->set_password($USER, $PWD, $REALM);
 
 ok( 1, "set_password with long params works" );
 
-ok( $ring->get_password($USER, $DOMAIN) eq $PWD, "get_password with long params works");
+ok( $ring->get_password($USER, $REALM) eq $PWD, "get_password with long params works");
 
-ok( $ring->clear_password($USER, $DOMAIN) eq 1, "clear_password with long params works");
+ok( $ring->clear_password($USER, $REALM) eq 1, "clear_password with long params works");