mr_keychain 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +6 -0
- data/.rspec +1 -0
- data/.rvmrc +2 -0
- data/.yardopts +4 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +67 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/lib/mr_keychain.rb +173 -0
- data/spec/keychain_spec.rb +227 -0
- data/spec/spec_helper.rb +12 -0
- metadata +166 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rvmrc
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
bluecloth (2.0.9)
|
5
|
+
diff-lcs (1.1.2)
|
6
|
+
git (1.2.5)
|
7
|
+
jeweler (1.5.2)
|
8
|
+
bundler (~> 1.0.0)
|
9
|
+
git (>= 1.2.5)
|
10
|
+
rake
|
11
|
+
rake (0.8.7)
|
12
|
+
rcov (0.9.9)
|
13
|
+
rspec (2.4.0)
|
14
|
+
rspec-core (~> 2.4.0)
|
15
|
+
rspec-expectations (~> 2.4.0)
|
16
|
+
rspec-mocks (~> 2.4.0)
|
17
|
+
rspec-core (2.4.0)
|
18
|
+
rspec-expectations (2.4.0)
|
19
|
+
diff-lcs (~> 1.1.2)
|
20
|
+
rspec-mocks (2.4.0)
|
21
|
+
yard (0.6.4)
|
22
|
+
|
23
|
+
PLATFORMS
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
bluecloth (~> 2.0.0)
|
28
|
+
bundler (~> 1.0.0)
|
29
|
+
jeweler (~> 1.5.2)
|
30
|
+
rcov
|
31
|
+
rspec (~> 2.4.0)
|
32
|
+
yard (~> 0.6.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Mark Rada
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
Keychain
|
2
|
+
========
|
3
|
+
|
4
|
+
A simple class for working with the Mac OS X keychain.
|
5
|
+
|
6
|
+
Reference
|
7
|
+
=========
|
8
|
+
|
9
|
+
To learn more about using the Keychain on OS X, see Apple's [Keychain Services Programming Guide](http://developer.apple.com/library/ios/#documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html) and the [Keychain Services Reference](http://developer.apple.com/library/mac/#documentation/Security/Reference/keychainservices/Reference/reference.html).
|
10
|
+
|
11
|
+
Tips
|
12
|
+
====
|
13
|
+
|
14
|
+
* You need to be careful about what key-value pairs you have stored in an item's attributes, they can sometimes mess up searches or cause unexpected failures when saving/updating a keychain item.
|
15
|
+
|
16
|
+
Example Usage
|
17
|
+
=============
|
18
|
+
|
19
|
+
# get an item
|
20
|
+
item = Keychain::Item.new
|
21
|
+
|
22
|
+
# add some search criteria, you need at least one, options are listed
|
23
|
+
# in the keychain services reference 'Attribute Item Keys and Values'
|
24
|
+
# section (link above)
|
25
|
+
item.attributes.merge!({
|
26
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
27
|
+
KSecAttrServer => 'github.com'
|
28
|
+
})
|
29
|
+
|
30
|
+
# work with the entry if it exists
|
31
|
+
if item.exists?
|
32
|
+
|
33
|
+
# cache all the metadata and print the account name (user name)
|
34
|
+
puts item.metadata![KSecAttrAccount]
|
35
|
+
|
36
|
+
# print the password (needs authorization)
|
37
|
+
puts item.password
|
38
|
+
|
39
|
+
# change the password and check it (BE CAREFUL)
|
40
|
+
puts item.password = 'test'
|
41
|
+
|
42
|
+
# change the user name and save to the keychain
|
43
|
+
# note how you do not need authorization to change the user name
|
44
|
+
item.update!({ KSecAttrAccount => 'test' })
|
45
|
+
puts item.metadata[KSecAttrAccount]
|
46
|
+
|
47
|
+
else
|
48
|
+
puts 'No such item exists, maybe you need different criteria?'
|
49
|
+
end
|
50
|
+
|
51
|
+
Contributing to keychain
|
52
|
+
========================
|
53
|
+
|
54
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
55
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
56
|
+
* Fork the project
|
57
|
+
* Start a feature/bugfix branch
|
58
|
+
* Commit and push until you are happy with your contribution
|
59
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
60
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
61
|
+
|
62
|
+
Copyright
|
63
|
+
=========
|
64
|
+
|
65
|
+
Copyright (c) 2011 Mark Rada. See LICENSE.txt for
|
66
|
+
further details.
|
67
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
gem.name = "mr_keychain"
|
15
|
+
gem.homepage = "http://github.com/ferrous26/keychain"
|
16
|
+
gem.license = "MIT"
|
17
|
+
gem.summary = %Q{Example code of how to use the Mac OS X keychain in MacRuby}
|
18
|
+
gem.description = %Q{Uses APIs new in Snow Leopard to create, read, and update keychain entries}
|
19
|
+
gem.email = "marada@uwaterloo.ca"
|
20
|
+
gem.authors = ["Mark Rada"]
|
21
|
+
end
|
22
|
+
Jeweler::RubygemsDotOrgTasks.new
|
23
|
+
|
24
|
+
require 'rspec/core'
|
25
|
+
require 'rspec/core/rake_task'
|
26
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
27
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'yard'
|
38
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/mr_keychain.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
framework 'Foundation'
|
2
|
+
|
3
|
+
# Classes, modules, methods, and constants relevant to working with the
|
4
|
+
# Mac OS X keychain.
|
5
|
+
module Keychain
|
6
|
+
|
7
|
+
# @note Methods only need a user's explicit authorization if they want the
|
8
|
+
# password data, metadata does not need permission. In these cases, the OS
|
9
|
+
# should present an alert asking to allow, deny, or always allow the script
|
10
|
+
# to access. You need to be careful when using 'always allow' if you are
|
11
|
+
# running this code from interactive ruby or the regular interpreter because
|
12
|
+
# you could accidentally allow any future script to not require permission
|
13
|
+
# to access any password in the keychain.
|
14
|
+
# Represents an entry in the login keychain.
|
15
|
+
#
|
16
|
+
# The big assumption that this class makes is that you only ever want
|
17
|
+
# to work with a single keychain item; whether it be searching for metadata,
|
18
|
+
# getting passwords, or adding a new entry.
|
19
|
+
#
|
20
|
+
# In order to be secure, this class will NEVER cache a password; any time
|
21
|
+
# that you change a password, it will be written to the keychain immeadiately.
|
22
|
+
|
23
|
+
class Item
|
24
|
+
|
25
|
+
# @return [Hash]
|
26
|
+
attr_accessor :attributes
|
27
|
+
|
28
|
+
# Direct access to the attributes hash of the keychain item.
|
29
|
+
def [] key
|
30
|
+
@attributes[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Direct access to the attributes hash of the keychain item.
|
34
|
+
def []= key, value
|
35
|
+
@attributes[key] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
# You should initialize objects of this class with the attributes relevant
|
39
|
+
# to the item you wish to work with, but you can add or remove attributes
|
40
|
+
# via accessors as well.
|
41
|
+
# @param [Hash] attributes
|
42
|
+
def initialize attributes = nil
|
43
|
+
@attributes = { KSecClass => KSecClassInternetPassword }
|
44
|
+
@attributes.merge! attributes if attributes
|
45
|
+
end
|
46
|
+
|
47
|
+
# @note This method asks only for the metadata and doesn't need authorization
|
48
|
+
# Returns true if there are any item matching the given attributes.
|
49
|
+
# @raise [KeychainException] for unexpected errors
|
50
|
+
# @return [true,false]
|
51
|
+
def exists?
|
52
|
+
result = Pointer.new :id
|
53
|
+
search = @attributes.merge({
|
54
|
+
KSecMatchLimit => KSecMatchLimitOne,
|
55
|
+
KSecReturnAttributes => true
|
56
|
+
})
|
57
|
+
|
58
|
+
case (error_code = SecItemCopyMatching(search, result))
|
59
|
+
when ErrSecSuccess then
|
60
|
+
true
|
61
|
+
when ErrSecItemNotFound then
|
62
|
+
false
|
63
|
+
else
|
64
|
+
message = SecCopyErrorMessageString(error_code, nil)
|
65
|
+
raise KeychainException, "Error checking keychain item existence: #{message}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @note We ask for an NSData object here in order to get the password.
|
70
|
+
# Returns the password for the first match found, raises an error if
|
71
|
+
# no keychain item is found.
|
72
|
+
#
|
73
|
+
# Blank passwords should come back as an empty string, but that hasn't
|
74
|
+
# been tested.
|
75
|
+
# @raise [KeychainException]
|
76
|
+
# @return [String] UTF8 encoded password string
|
77
|
+
def password
|
78
|
+
result = Pointer.new :id
|
79
|
+
search = @attributes.merge({
|
80
|
+
KSecMatchLimit => KSecMatchLimitOne,
|
81
|
+
KSecReturnData => true
|
82
|
+
})
|
83
|
+
|
84
|
+
case (error_code = SecItemCopyMatching(search, result))
|
85
|
+
when ErrSecSuccess then
|
86
|
+
NSString.alloc.initWithData result[0], encoding:NSUTF8StringEncoding
|
87
|
+
else
|
88
|
+
message = SecCopyErrorMessageString(error_code, nil)
|
89
|
+
raise KeychainException, "Error getting password: #{message}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
# Updates the password associated with the keychain item. If the item does
|
95
|
+
# not exist in the keychain it will be added first.
|
96
|
+
# @raise [KeychainException]
|
97
|
+
# @param [String] new_password a UTF-8 encoded string
|
98
|
+
# @return [String] the saved password
|
99
|
+
def password= new_password
|
100
|
+
password_data = {
|
101
|
+
KSecValueData => new_password.dataUsingEncoding(NSUTF8StringEncoding)
|
102
|
+
}
|
103
|
+
if exists?
|
104
|
+
error_code = SecItemUpdate( @attributes, password_data )
|
105
|
+
else
|
106
|
+
error_code = SecItemAdd( @attributes.merge password_data, nil )
|
107
|
+
end
|
108
|
+
|
109
|
+
case error_code
|
110
|
+
when ErrSecSuccess then
|
111
|
+
password
|
112
|
+
else
|
113
|
+
message = SecCopyErrorMessageString(error_code, nil)
|
114
|
+
raise KeychainException, "Error updating password: #{message}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# @todo This method does not really fit with the rest of the API.
|
119
|
+
# @note This method does not need authorization unless you are
|
120
|
+
# updating the password.
|
121
|
+
# Updates attributes of the item in the keychain. If the item does not
|
122
|
+
# exist yet then this method will raise an exception.
|
123
|
+
#
|
124
|
+
# Use a value of nil to remove an attribute.
|
125
|
+
# @param [Hash] new_attributes the attributes that you want to update
|
126
|
+
# @return [Hash] attributes
|
127
|
+
def update! new_attributes
|
128
|
+
result = Pointer.new :id
|
129
|
+
query = @attributes.merge({ KSecMatchLimit => KSecMatchLimitOne })
|
130
|
+
|
131
|
+
case (error_code = SecItemUpdate(query, new_attributes))
|
132
|
+
when ErrSecSuccess then
|
133
|
+
metadata!
|
134
|
+
else
|
135
|
+
message = SecCopyErrorMessageString(error_code, nil)
|
136
|
+
raise KeychainException, "Error updating keychain item: #{message}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Get all the metadata about a keychain item, they will be keyed
|
141
|
+
# according Apple's documentation.
|
142
|
+
# @raise [KeychainException]
|
143
|
+
# @return [Hash]
|
144
|
+
def metadata
|
145
|
+
result = Pointer.new :id
|
146
|
+
search = @attributes.merge({
|
147
|
+
KSecMatchLimit => KSecMatchLimitOne,
|
148
|
+
KSecReturnAttributes => true
|
149
|
+
})
|
150
|
+
|
151
|
+
case (error_code = SecItemCopyMatching(search, result))
|
152
|
+
when ErrSecSuccess then
|
153
|
+
result[0]
|
154
|
+
else
|
155
|
+
message = SecCopyErrorMessageString(error_code, nil)
|
156
|
+
raise KeychainException, "Error getting metadata: #{message}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Update attributes to include all the metadata from the keychain.
|
161
|
+
# @raise [KeychainException]
|
162
|
+
# @return [Hash]
|
163
|
+
def metadata!
|
164
|
+
@attributes = metadata
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# A trivial exception class that exists to help differentiate where
|
169
|
+
# exceptions are being raised.
|
170
|
+
class KeychainException < Exception
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
# @todo get the spec tests to use mock data
|
4
|
+
describe 'Keychain' do
|
5
|
+
|
6
|
+
describe 'Item' do
|
7
|
+
|
8
|
+
before do
|
9
|
+
@item = Keychain::Item.new
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
describe 'attributes attribute' do
|
14
|
+
it 'is readable' do
|
15
|
+
@item.respond_to?(:attributes).should == true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'is writable' do
|
19
|
+
@item.respond_to?(:attributes=).should == true
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should be initialized with the class being set' do
|
23
|
+
@item.attributes[KSecClass].should_not == nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should be initialized to be of the interenet class' do
|
27
|
+
@item.attributes[KSecClass].should == KSecClassInternetPassword
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should allow the class to be overriden' do
|
31
|
+
@item = Keychain::Item.new({ KSecClass => 'different' })
|
32
|
+
@item.attributes[KSecClass].should == 'different'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
describe '#exists?' do
|
38
|
+
it 'returns false if the item does not exist' do
|
39
|
+
@item.attributes.merge!({
|
40
|
+
KSecAttrProtocol => KSecAttrProtocolIRCS,
|
41
|
+
KSecAttrServer => 'github.com'
|
42
|
+
})
|
43
|
+
@item.exists?.should == false
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns true if the item exists' do
|
47
|
+
@item.attributes.merge!({
|
48
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
49
|
+
KSecAttrServer => 'github.com'
|
50
|
+
})
|
51
|
+
@item.exists?.should == true
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'raises an exception for unexpected error codes' do
|
55
|
+
@item.attributes[KSecClass] = 'made up class'
|
56
|
+
expect { @item.exists? }.to raise_exception(Keychain::KeychainException)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
describe '#password' do
|
62
|
+
it 'should return a string with the password' do
|
63
|
+
@item.attributes.merge!({
|
64
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
65
|
+
KSecAttrServer => 'github.com'
|
66
|
+
})
|
67
|
+
@item.password.class.should == String
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should raise an exception if no password is found' do
|
71
|
+
@item.attributes.merge!({
|
72
|
+
KSecAttrProtocol => KSecAttrProtocolIRCS,
|
73
|
+
KSecAttrServer => 'github.com'
|
74
|
+
})
|
75
|
+
expect { @item.password }.to raise_exception(Keychain::KeychainException)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
describe '#metadata' do
|
81
|
+
it 'should return a hash' do
|
82
|
+
@item.attributes.merge!({
|
83
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
84
|
+
KSecAttrServer => 'github.com'
|
85
|
+
})
|
86
|
+
@item.metadata.class.should == Hash
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should raise an exception if nothing is found' do
|
90
|
+
@item.attributes.merge!({
|
91
|
+
KSecAttrProtocol => KSecAttrProtocolIRCS,
|
92
|
+
KSecAttrServer => 'github.com'
|
93
|
+
})
|
94
|
+
expect { @item.metadata }.to raise_exception(Keychain::KeychainException)
|
95
|
+
end
|
96
|
+
|
97
|
+
# this assumes the keychain item has more metadata
|
98
|
+
it 'should not overwrite @attributes' do
|
99
|
+
@item.attributes.merge!({
|
100
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
101
|
+
KSecAttrServer => 'github.com'
|
102
|
+
})
|
103
|
+
metadata = @item.metadata
|
104
|
+
@item.attributes.should_not == metadata
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
describe '#metadata!' do
|
110
|
+
it 'should return a hash' do
|
111
|
+
@item.attributes.merge!({
|
112
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
113
|
+
KSecAttrServer => 'github.com'
|
114
|
+
})
|
115
|
+
@item.metadata.class.should == Hash
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should raise an exception if nothing is found' do
|
119
|
+
@item.attributes.merge!({
|
120
|
+
KSecAttrProtocol => KSecAttrProtocolIRCS,
|
121
|
+
KSecAttrServer => 'github.com'
|
122
|
+
})
|
123
|
+
expect { @item.metadata }.to raise_exception(Keychain::KeychainException)
|
124
|
+
end
|
125
|
+
|
126
|
+
# this assumes the keychain item has more metadata
|
127
|
+
it 'should overwrite @attributes' do
|
128
|
+
@item.attributes.merge!({
|
129
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
130
|
+
KSecAttrServer => 'github.com'
|
131
|
+
})
|
132
|
+
metadata = @item.metadata!
|
133
|
+
@item.attributes.should == metadata
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
describe '#[]' do
|
139
|
+
it 'should be equivalent to #attributes' do
|
140
|
+
@item.attributes[:test] = 'test'
|
141
|
+
@item[:test].should == 'test'
|
142
|
+
@item[KSecClass].should == @item.attributes[KSecClass]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
describe '#[]=' do
|
148
|
+
it 'should be equivalent to #attributes=' do
|
149
|
+
@item[:test] = 'test'
|
150
|
+
@item.attributes[:test].should == 'test'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
# describe '#password=' do
|
156
|
+
# before do
|
157
|
+
# @item.attributes.merge!({
|
158
|
+
# KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
159
|
+
# KSecAttrServer => 'github.com'
|
160
|
+
# })
|
161
|
+
# end
|
162
|
+
|
163
|
+
# it 'should return the updated password' do
|
164
|
+
# (@item.password = 'new password').should == 'new password'
|
165
|
+
# end
|
166
|
+
|
167
|
+
# it 'should update the password in the keychain' do
|
168
|
+
# (@item.password = 'new password').should == @item.password
|
169
|
+
# end
|
170
|
+
|
171
|
+
# it 'should create entries if they do not exsit' do
|
172
|
+
# @item.attributes.merge!({
|
173
|
+
# KSecAttrAccount => 'test'
|
174
|
+
# })
|
175
|
+
# @item.password = 'another test'
|
176
|
+
# @item.exists?.should == true
|
177
|
+
# end
|
178
|
+
|
179
|
+
# after do
|
180
|
+
# NSLog('I created an entry in your keychain that you should clean up')
|
181
|
+
# end
|
182
|
+
# end
|
183
|
+
|
184
|
+
|
185
|
+
describe '#update!' do
|
186
|
+
it 'should update fields given in the persistent keychain' do
|
187
|
+
@item.attributes.merge!({
|
188
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
189
|
+
KSecAttrServer => 'github.com'
|
190
|
+
})
|
191
|
+
@item.update!({ KSecAttrComment => 'test' })
|
192
|
+
@item.metadata[KSecAttrComment].should == 'test'
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'should raise an exception for non-existant items' do
|
196
|
+
@item.attributes.merge!({
|
197
|
+
KSecAttrProtocol => KSecAttrProtocolIRCS,
|
198
|
+
KSecAttrServer => 'github.com'
|
199
|
+
})
|
200
|
+
expect {
|
201
|
+
@item.update!({ KSecAttrComment => 'different test' })
|
202
|
+
}.to raise_exception(Keychain::KeychainException)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'should update @attributes' do
|
206
|
+
@item.attributes.merge!({
|
207
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
208
|
+
KSecAttrServer => 'github.com'
|
209
|
+
})
|
210
|
+
@item.update!({ KSecAttrComment => 'toast' })
|
211
|
+
@item.attributes[KSecAttrComment].should == 'toast'
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'should return the metadata of the keychain item' do
|
215
|
+
@item.attributes.merge!({
|
216
|
+
KSecAttrProtocol => KSecAttrProtocolHTTPS,
|
217
|
+
KSecAttrServer => 'github.com'
|
218
|
+
})
|
219
|
+
@item.update!({
|
220
|
+
KSecAttrComment => 'bread'
|
221
|
+
})[KSecAttrComment].should == 'bread'
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'mr_keychain'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mr_keychain
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Mark Rada
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-01-05 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 4
|
30
|
+
- 0
|
31
|
+
version: 2.4.0
|
32
|
+
type: :development
|
33
|
+
prerelease: false
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: yard
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
- 6
|
45
|
+
- 0
|
46
|
+
version: 0.6.0
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: bluecloth
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
segments:
|
58
|
+
- 2
|
59
|
+
- 0
|
60
|
+
- 0
|
61
|
+
version: 2.0.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: bundler
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ~>
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 1
|
74
|
+
- 0
|
75
|
+
- 0
|
76
|
+
version: 1.0.0
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *id004
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: jeweler
|
82
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
segments:
|
88
|
+
- 1
|
89
|
+
- 5
|
90
|
+
- 2
|
91
|
+
version: 1.5.2
|
92
|
+
type: :development
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: rcov
|
97
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: *id006
|
108
|
+
description: Uses APIs new in Snow Leopard to create, read, and update keychain entries
|
109
|
+
email: marada@uwaterloo.ca
|
110
|
+
executables: []
|
111
|
+
|
112
|
+
extensions: []
|
113
|
+
|
114
|
+
extra_rdoc_files:
|
115
|
+
- LICENSE.txt
|
116
|
+
- README.markdown
|
117
|
+
files:
|
118
|
+
- .document
|
119
|
+
- .rspec
|
120
|
+
- .rvmrc
|
121
|
+
- .yardopts
|
122
|
+
- Gemfile
|
123
|
+
- Gemfile.lock
|
124
|
+
- LICENSE.txt
|
125
|
+
- README.markdown
|
126
|
+
- Rakefile
|
127
|
+
- VERSION
|
128
|
+
- lib/mr_keychain.rb
|
129
|
+
- spec/keychain_spec.rb
|
130
|
+
- spec/spec_helper.rb
|
131
|
+
has_rdoc: true
|
132
|
+
homepage: http://github.com/ferrous26/keychain
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
hash: -1899776903697949187
|
146
|
+
segments:
|
147
|
+
- 0
|
148
|
+
version: "0"
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
segments:
|
155
|
+
- 0
|
156
|
+
version: "0"
|
157
|
+
requirements: []
|
158
|
+
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 1.3.7
|
161
|
+
signing_key:
|
162
|
+
specification_version: 3
|
163
|
+
summary: Example code of how to use the Mac OS X keychain in MacRuby
|
164
|
+
test_files:
|
165
|
+
- spec/keychain_spec.rb
|
166
|
+
- spec/spec_helper.rb
|