ruby-keychain 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,17 @@
1
+ # @markup markdown
2
+
1
3
  A set of ruby bindings for the OS X keychain, written using ffi
2
4
 
5
+ Installation
6
+ ============
7
+
8
+ gem install ruby-keychain
9
+
10
+ or in your gemfile,
11
+
12
+ gem 'ruby-keychain', :require => 'keychain'
13
+
14
+
3
15
  Introduction
4
16
  ============
5
17
 
@@ -13,12 +25,12 @@ Most operations will act on either the default keychain, or the default keychain
13
25
 
14
26
  Keychain.default #the default keychain, usually /Users/<username>/Library/Keychains/<username>.keychain
15
27
  Keychain.open(path) #opens a keychain file
16
- Keychain.create(path, password) #creates a new keychain at the specified path
17
-
28
+ Keychain.create(path, password) # creates a new keychain at the specified path, with the specified password
29
+ # omit the password to make the keychain prompt the user
18
30
 
19
31
 
20
32
  Searching for Keychain Items
21
- =========
33
+ =============================
22
34
 
23
35
  The top level constant `Keychain` as well as individual keychain objects have two methods `internet_passwords` and `generic_passwords` that return scope like objects. You can do
24
36
 
data/lib/keychain.rb CHANGED
@@ -14,17 +14,22 @@ module Keychain
14
14
  # See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainCreate
15
15
  # @param [String] path The path to the keychain file to create
16
16
  # If it is not absolute it is interpreted relative to ~/Library/Keychains
17
- # @param [String] password The password to use for the keychain
17
+ # @param [optional, String] password The password to use for the keychain. if not supplied, the user will be prompted for a password
18
18
  # @return [Keychain::Keychain] a keychain object representing the newly created keychain
19
19
 
20
- def create(path, password)
21
- password = password.encode(Encoding::UTF_8)
20
+ def create(path, password=nil)
22
21
  path = path.encode(Encoding::UTF_8)
23
-
24
22
  out_buffer = FFI::MemoryPointer.new(:pointer)
25
- status = Sec.SecKeychainCreate(path, password.bytesize, FFI::MemoryPointer.from_string(password), 0,
23
+
24
+ if password
25
+ password = password.encode(Encoding::UTF_8)
26
+ status = Sec.SecKeychainCreate(path, password.bytesize, FFI::MemoryPointer.from_string(password), 0,
26
27
  nil, out_buffer)
27
28
 
29
+ else
30
+ status = Sec.SecKeychainCreate(path, 0, nil, 1, nil, out_buffer)
31
+ end
32
+
28
33
  Sec.check_osstatus(status)
29
34
  Keychain.new(out_buffer.read_pointer).release_on_gc
30
35
  end
@@ -68,5 +73,26 @@ module Keychain
68
73
  def generic_passwords
69
74
  Scope.new(Sec::Classes::GENERIC)
70
75
  end
76
+
77
+ # sets whether user interaction is allowed
78
+ # If false then operations that would require user interaction (for example prompting the user for a password to unlock a keychain)
79
+ # will raise InteractionNotAllowedError
80
+ # @param [Boolean] value
81
+ def user_interaction_allowed= value
82
+ status = Sec.SecKeychainSetUserInteractionAllowed( value ? 1 : 0)
83
+ Sec.check_osstatus(status)
84
+ value
85
+ end
86
+
87
+ # Returns whether user interaction is allowed
88
+ # If false then operations that would require user interaction (for example prompting the user for a password to unlock a keychain)
89
+ # will raise InteractionNotAllowedError
90
+ # @return whether interaction is allowed
91
+ def user_interaction_allowed?
92
+ out_buffer = FFI::MemoryPointer.new(:uchar)
93
+ status = Sec.SecKeychainGetUserInteractionAllowed(out_buffer)
94
+ Sec.check_osstatus(status)
95
+ out_buffer.read_uchar.nonzero?
96
+ end
71
97
  end
72
98
  end
@@ -31,4 +31,7 @@ class Keychain::AuthFailedError < Keychain::Error; end
31
31
  class Keychain::UserCancelledError < Keychain::Error; end
32
32
  # Raised when an action fails because the underlying keychain
33
33
  # does not exist
34
- class Keychain::NoSuchKeychainError < Keychain::Error; end
34
+ class Keychain::NoSuchKeychainError < Keychain::Error; end
35
+ # Raised when an action would rewuire user interaction but user interaction
36
+ # is not allowed. See Keychain.user_interaction_allowed=
37
+ class Keychain::InteractionNotAllowedError < Keychain::Error; end
@@ -24,6 +24,9 @@ module Sec
24
24
 
25
25
  attach_function 'SecKeychainGetStatus', [:keychainref, :pointer], :osstatus
26
26
 
27
+ attach_function 'SecKeychainSetUserInteractionAllowed', [:uchar], :osstatus
28
+
29
+ attach_function 'SecKeychainGetUserInteractionAllowed', [:pointer], :osstatus
27
30
  enum :keychainStatus, [
28
31
  :kSecUnlockStateStatus, 1,
29
32
  :kSecReadPermStatus, 2,
@@ -5,7 +5,7 @@ class Keychain::Scope
5
5
  def initialize(kind, keychain=nil)
6
6
  @kind = kind
7
7
  @limit = nil
8
- @keychains = [keychain]
8
+ @keychains = [keychain].compact
9
9
  @conditions = {}
10
10
  end
11
11
 
data/lib/keychain/sec.rb CHANGED
@@ -11,7 +11,8 @@ module Sec
11
11
  :errSecDuplicateItem, -25299,
12
12
  :errSecAuthFailed, -25293,
13
13
  :errSecNoSuchKeychain, -25294,
14
- :errCancelled, -128
14
+ :errCancelled, -128,
15
+ :errSecInteractionNotAllowed, -25308
15
16
  ]
16
17
 
17
18
  attach_variable 'kSecClassInternetPassword', :pointer
@@ -155,6 +156,8 @@ module Sec
155
156
  raise Keychain::AuthFailedError.new(result)
156
157
  when Sec.enum_value(:errSecNoSuchKeychain)
157
158
  raise Keychain::NoSuchKeychainError.new(result)
159
+ when Sec.enum_value(:errSecInteractionNotAllowed)
160
+ raise Keychain::InteractionNotAllowedError.new(result)
158
161
  else
159
162
  raise Keychain::Error.new(result)
160
163
  end
@@ -1,4 +1,4 @@
1
1
  module Keychain
2
2
  # The current version string
3
- VERSION = '0.1.0'
3
+ VERSION = '0.1.1'
4
4
  end
@@ -1,6 +1,20 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Keychain do
4
+
5
+ describe 'user interaction' do
6
+ it 'should be true by default' do
7
+ Keychain.user_interaction_allowed?.should be_true
8
+ end
9
+
10
+ it 'should be changeable' do
11
+ Keychain.user_interaction_allowed = false
12
+ Keychain.user_interaction_allowed?.should be_false
13
+ Keychain.user_interaction_allowed = true
14
+ Keychain.user_interaction_allowed?.should be_true
15
+ end
16
+ end
17
+
4
18
  describe 'default' do
5
19
  it "should return the login keychain" do
6
20
  Keychain.default.path.should == File.expand_path(File.join(ENV['HOME'], 'Library','Keychains', 'login.keychain'))
@@ -14,7 +28,7 @@ describe Keychain do
14
28
  end
15
29
  end
16
30
 
17
- describe 'new' do
31
+ describe 'create' do
18
32
  it 'should create the keychain' do
19
33
  begin
20
34
  keychain = Keychain.create(File.join(Dir.tmpdir, "other_keychain_spec_#{Time.now.to_i}_#{Time.now.usec}_#{rand(1000)}.keychain"),
@@ -24,6 +38,14 @@ describe Keychain do
24
38
  keychain.delete
25
39
  end
26
40
  end
41
+
42
+ context 'no password supplied' do
43
+ #we have to stub this out as it would trigger a dialog box prompting for a password
44
+ it 'should create a keychain by prompting the user' do
45
+ Sec.should_receive('SecKeychainCreate').with('akeychain', 0, nil, 1, nil,kind_of(FFI::Pointer)).and_return(0)
46
+ Keychain.create('akeychain')
47
+ end
48
+ end
27
49
  end
28
50
 
29
51
  describe 'exists?' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-keychain
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-03 00:00:00.000000000 Z
12
+ date: 2012-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi
@@ -124,7 +124,7 @@ files:
124
124
  - spec/keychain_item_spec.rb
125
125
  - spec/keychain_spec.rb
126
126
  - spec/spec_helper.rb
127
- - README
127
+ - README.markdown
128
128
  - LICENSE
129
129
  homepage: http://github.com/fcheung/keychain
130
130
  licenses: