keyrack 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  require 'openssl'
2
2
  require 'yaml'
3
+ require 'json'
3
4
  require 'optparse'
4
5
  require 'securerandom'
5
6
  require 'net/scp'
@@ -1,7 +1,7 @@
1
1
  module Keyrack
2
2
  class Database
3
3
  DEFAULT_ENCRYPT_OPTIONS = { :maxmem => 0, :maxmemfrac => 0.125, :maxtime => 5.0 }
4
- DEFAULT_DECRYPT_OPTIONS = { :maxmem => 0, :maxmemfrac => 0.250, :maxtime => 10.0 }
4
+ DEFAULT_DECRYPT_OPTIONS = { :maxmem => 0, :maxmemfrac => 0.500, :maxtime => 20.0 }
5
5
  VERSION = 4
6
6
 
7
7
  def initialize(password, store, encrypt_options = {}, decrypt_options = {})
@@ -10,16 +10,16 @@ module Keyrack
10
10
  @decrypt_options = DEFAULT_DECRYPT_OPTIONS.merge(decrypt_options)
11
11
  @store = store
12
12
  @password = password_hash(password)
13
- @database = decrypt(password)
13
+ @attributes = decrypt(password)
14
14
  setup_hooks
15
15
  end
16
16
 
17
17
  def version
18
- @database['version']
18
+ @attributes['version']
19
19
  end
20
20
 
21
21
  def top_group
22
- @database['groups']['top']
22
+ @attributes['groups']['top']
23
23
  end
24
24
 
25
25
  def dirty?
@@ -28,7 +28,7 @@ module Keyrack
28
28
 
29
29
  def save(password)
30
30
  if password_hash(password) == @password
31
- @store.write(Scrypty.encrypt(@database.to_yaml, password,
31
+ @store.write(Scrypty.encrypt(JSON.generate(self.to_h), password,
32
32
  *@encrypt_options.values_at(:maxmem, :maxmemfrac, :maxtime)))
33
33
  @dirty = false
34
34
  true
@@ -46,6 +46,15 @@ module Keyrack
46
46
  end
47
47
  end
48
48
 
49
+ def to_h
50
+ hash = @attributes.dup
51
+ hash['groups'] = hash['groups'].inject({}) do |hash2, (key, value)|
52
+ hash2[key] = value.to_h
53
+ hash2
54
+ end
55
+ hash
56
+ end
57
+
49
58
  private
50
59
 
51
60
  def password_hash(password)
@@ -60,7 +69,15 @@ module Keyrack
60
69
  if data
61
70
  str = Scrypty.decrypt(data, password,
62
71
  *@decrypt_options.values_at(:maxmem, :maxmemfrac, :maxtime))
63
- hash = YAML.load(str)
72
+
73
+ hash =
74
+ if str =~ /^---\s*\n/
75
+ @dirty = true
76
+ YAML.load(str.gsub(/!map:Keyrack::\w+/, "!map"))
77
+ else
78
+ JSON.parse(str)
79
+ end
80
+
64
81
  migrated_hash = Migrator.run(hash)
65
82
  if !migrated_hash.equal?(hash)
66
83
  hash = migrated_hash
@@ -78,7 +95,7 @@ module Keyrack
78
95
  end
79
96
 
80
97
  def setup_hooks
81
- @database['groups'].each_pair do |group_name, group|
98
+ @attributes['groups'].each_pair do |group_name, group|
82
99
  add_group_hooks_for(group)
83
100
  end
84
101
  end
@@ -1,11 +1,13 @@
1
1
  module Keyrack
2
- class Group < Hash
2
+ class Group
3
3
  def initialize(arg = nil)
4
+ @attributes = {}
5
+
4
6
  case arg
5
7
  when String
6
- self['name'] = arg
7
- self['sites'] = []
8
- self['groups'] = {}
8
+ @attributes['name'] = arg
9
+ @attributes['sites'] = []
10
+ @attributes['groups'] = {}
9
11
  when Hash
10
12
  load(arg)
11
13
  when nil
@@ -24,7 +26,7 @@ module Keyrack
24
26
  if !hash['name'].is_a?(String)
25
27
  raise ArgumentError, "name is not a String"
26
28
  end
27
- self['name'] = hash['name']
29
+ @attributes['name'] = hash['name']
28
30
 
29
31
  if !hash.has_key?('sites')
30
32
  raise ArgumentError, "hash is missing the 'sites' key"
@@ -40,7 +42,7 @@ module Keyrack
40
42
  raise ArgumentError, "groups is not a Hash"
41
43
  end
42
44
 
43
- self['sites'] = []
45
+ @attributes['sites'] = []
44
46
  hash['sites'].each_with_index do |site_hash, site_index|
45
47
  if !site_hash.is_a?(Hash)
46
48
  raise ArgumentError, "site #{site_index} is not a Hash"
@@ -54,7 +56,7 @@ module Keyrack
54
56
  end
55
57
  end
56
58
 
57
- self['groups'] = {}
59
+ @attributes['groups'] = {}
58
60
  hash['groups'].each_pair do |group_name, group_hash|
59
61
  if !group_name.is_a?(String)
60
62
  raise ArgumentError, "group key is not a String"
@@ -82,15 +84,15 @@ module Keyrack
82
84
  def change_attribute(name, value)
83
85
  event = Event.new(self, 'change')
84
86
  event.attribute_name = name
85
- event.previous_value = self[name]
87
+ event.previous_value = @attributes[name]
86
88
  event.new_value = value
87
89
 
88
- self[name] = value
90
+ @attributes[name] = value
89
91
  trigger(event)
90
92
  end
91
93
 
92
94
  def name
93
- self['name']
95
+ @attributes['name']
94
96
  end
95
97
 
96
98
  def name=(name)
@@ -98,11 +100,11 @@ module Keyrack
98
100
  end
99
101
 
100
102
  def sites
101
- self['sites']
103
+ @attributes['sites']
102
104
  end
103
105
 
104
106
  def groups
105
- self['groups']
107
+ @attributes['groups']
106
108
  end
107
109
 
108
110
  def add_site(site)
@@ -168,8 +170,14 @@ module Keyrack
168
170
  @after_event << block
169
171
  end
170
172
 
171
- def encode_with(coder)
172
- coder.represent_map(nil, self)
173
+ def to_h
174
+ hash = @attributes.dup
175
+ hash['sites'] = hash['sites'].collect(&:to_h)
176
+ hash['groups'] = hash['groups'].inject({}) do |hash2, (key, value)|
177
+ hash2[key] = value.to_h
178
+ hash2
179
+ end
180
+ hash
173
181
  end
174
182
 
175
183
  private
@@ -212,9 +220,9 @@ module Keyrack
212
220
  def add_group_hooks_for(group)
213
221
  group.after_event do |group_event|
214
222
  if group_event.name == 'change' && group_event.attribute_name == 'name'
215
- key, value = self.groups.find { |(k, v)| v.equal?(group) }
223
+ key, value = groups.find { |(k, v)| v.equal?(group) }
216
224
  if key
217
- self['groups'][group.name] = self['groups'].delete(key)
225
+ groups[group.name] = groups.delete(key)
218
226
  end
219
227
  end
220
228
 
@@ -1,5 +1,5 @@
1
1
  module Keyrack
2
- class Site < Hash
2
+ class Site
3
3
  def initialize(*args)
4
4
  if args[0].is_a?(Hash)
5
5
  hash = args[0]
@@ -21,12 +21,14 @@ module Keyrack
21
21
  if !hash['password'].is_a?(String)
22
22
  raise ArgumentError, "name is not a String"
23
23
  end
24
- self.update(hash)
25
24
  else
26
- self['name'] = args[0]
27
- self['username'] = args[1]
28
- self['password'] = args[2]
25
+ hash = {
26
+ 'name' => args[0],
27
+ 'username' => args[1],
28
+ 'password' => args[2]
29
+ }
29
30
  end
31
+ @attributes = hash
30
32
 
31
33
  @event_hooks = []
32
34
  end
@@ -34,15 +36,15 @@ module Keyrack
34
36
  def change_attribute(name, value)
35
37
  event = Event.new(self, 'change')
36
38
  event.attribute_name = name
37
- event.previous_value = self[name]
39
+ event.previous_value = @attributes[name]
38
40
  event.new_value = value
39
41
 
40
- self[name] = value
42
+ @attributes[name] = value
41
43
  trigger(event)
42
44
  end
43
45
 
44
46
  def name
45
- self['name']
47
+ @attributes['name']
46
48
  end
47
49
 
48
50
  def name=(name)
@@ -50,7 +52,7 @@ module Keyrack
50
52
  end
51
53
 
52
54
  def username
53
- self['username']
55
+ @attributes['username']
54
56
  end
55
57
 
56
58
  def username=(username)
@@ -58,7 +60,7 @@ module Keyrack
58
60
  end
59
61
 
60
62
  def password
61
- self['password']
63
+ @attributes['password']
62
64
  end
63
65
 
64
66
  def password=(password)
@@ -69,10 +71,6 @@ module Keyrack
69
71
  @event_hooks << block
70
72
  end
71
73
 
72
- def encode_with(coder)
73
- coder.represent_map(nil, self)
74
- end
75
-
76
74
  def ==(other)
77
75
  if other.instance_of?(Site)
78
76
  other.name == name && other.username == username
@@ -81,6 +79,10 @@ module Keyrack
81
79
  end
82
80
  end
83
81
 
82
+ def to_h
83
+ @attributes.clone
84
+ end
85
+
84
86
  private
85
87
 
86
88
  def trigger(event)
@@ -1,3 +1,3 @@
1
1
  module Keyrack
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -37,8 +37,8 @@ class TestDatabase < Test::Unit::TestCase
37
37
 
38
38
  test "encrypting database" do
39
39
  encrypted_data = File.read(@path)
40
- yaml = decrypt(encrypted_data, @key)
41
- data = YAML.load(yaml)
40
+ json = decrypt(encrypted_data, @key)
41
+ data = JSON.parse(json)
42
42
  expected = {
43
43
  'groups' => {
44
44
  'top' => {
@@ -64,6 +64,18 @@ class TestDatabase < Test::Unit::TestCase
64
64
  assert database.dirty?
65
65
  end
66
66
 
67
+ test "auto-converting from YAML to JSON" do
68
+ store = Keyrack::Store['filesystem'].new('path' => fixture_path('database-yaml.dat'))
69
+ database = Keyrack::Database.new('foobar', store)
70
+ assert database.dirty?
71
+ end
72
+
73
+ test "auto-converting from 1.9.2 YAML to JSON" do
74
+ store = Keyrack::Store['filesystem'].new('path' => fixture_path('database-yaml-192.dat'))
75
+ database = Keyrack::Database.new('foobar', store)
76
+ assert database.dirty?
77
+ end
78
+
67
79
  [true, false].each do |reload|
68
80
  database_test "database is dirty after adding site to top group", reload do
69
81
  assert !@database.dirty?
@@ -84,8 +84,11 @@ class TestGroup < Test::Unit::TestCase
84
84
  subgroup = new_group("Klingon")
85
85
  group.add_group(subgroup)
86
86
 
87
- expected = {"Klingon" => {'name' => "Klingon", 'sites' => [], 'groups' => {}}}
88
- assert_equal(expected, group.groups)
87
+ assert_equal 1, group.groups.length
88
+ group = group.group('Klingon')
89
+ assert_equal 'Klingon', group.name
90
+ assert_equal [], group.sites
91
+ assert_equal({}, group.groups)
89
92
  end
90
93
 
91
94
  test "adding already existing group raises error" do
@@ -143,16 +146,15 @@ class TestGroup < Test::Unit::TestCase
143
146
  }
144
147
  }
145
148
  }
146
- group = new_group(hash)
147
- assert_equal "Starships", group.name
148
- assert_equal hash['sites'], group.sites
149
- assert_equal hash['groups'], group.groups
150
-
151
- group = Keyrack::Group.new
152
- group.load(hash)
153
- assert_equal "Starships", group.name
154
- assert_equal hash['sites'], group.sites
155
- assert_equal hash['groups'], group.groups
149
+ values = [new_group(hash), Keyrack::Group.new]
150
+ values[1].load(hash)
151
+ values.each do |group|
152
+ assert_equal "Starships", group.name
153
+ assert_equal 1, group.sites.length
154
+ assert_kind_of Keyrack::Site, group.site(0)
155
+ assert_equal 1, group.groups.length
156
+ assert_kind_of Keyrack::Group, group.group('Klingon')
157
+ end
156
158
  end
157
159
 
158
160
  test "loading group from hash with missing name" do
@@ -666,7 +668,7 @@ class TestGroup < Test::Unit::TestCase
666
668
  assert called
667
669
  end
668
670
 
669
- test "to_yaml" do
671
+ test "to_h" do
670
672
  group = new_group("Starships")
671
673
  site = new_site("Enterprise", "picard", "livingston")
672
674
  group.add_site(site)
@@ -697,7 +699,7 @@ class TestGroup < Test::Unit::TestCase
697
699
  'groups' => {}
698
700
  }
699
701
  }
700
- }.to_yaml
701
- assert_equal expected, group.to_yaml
702
+ }
703
+ assert_equal expected, group.to_h
702
704
  end
703
705
  end
@@ -161,14 +161,14 @@ class TestSite < Test::Unit::TestCase
161
161
  assert called
162
162
  end
163
163
 
164
- test "serializing to yaml" do
164
+ test "to_h" do
165
165
  site = new_site("Enterprise", "picard", "livingston")
166
166
  expected = {
167
167
  'name' => 'Enterprise',
168
168
  'username' => 'picard',
169
169
  'password' => 'livingston'
170
- }.to_yaml
171
- assert_equal expected, site.to_yaml
170
+ }
171
+ assert_equal expected, site.to_h
172
172
  end
173
173
 
174
174
  test "sites with same name and username are equal" do
metadata CHANGED
@@ -1,111 +1,126 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keyrack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Jeremy Stephens
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-06-11 00:00:00.000000000 Z
12
+ date: 2013-11-02 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: net-scp
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
- - - '>='
19
+ - - ! '>='
18
20
  - !ruby/object:Gem::Version
19
21
  version: '0'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
- - - '>='
27
+ - - ! '>='
25
28
  - !ruby/object:Gem::Version
26
29
  version: '0'
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: highline
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
- - - '>='
35
+ - - ! '>='
32
36
  - !ruby/object:Gem::Version
33
37
  version: '0'
34
38
  type: :runtime
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
- - - '>='
43
+ - - ! '>='
39
44
  - !ruby/object:Gem::Version
40
45
  version: '0'
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: clipboard
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
- - - '>='
51
+ - - ! '>='
46
52
  - !ruby/object:Gem::Version
47
53
  version: '0'
48
54
  type: :runtime
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
- - - '>='
59
+ - - ! '>='
53
60
  - !ruby/object:Gem::Version
54
61
  version: '0'
55
62
  - !ruby/object:Gem::Dependency
56
63
  name: scrypty
57
64
  requirement: !ruby/object:Gem::Requirement
65
+ none: false
58
66
  requirements:
59
- - - '>='
67
+ - - ! '>='
60
68
  - !ruby/object:Gem::Version
61
69
  version: '0'
62
70
  type: :runtime
63
71
  prerelease: false
64
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
65
74
  requirements:
66
- - - '>='
75
+ - - ! '>='
67
76
  - !ruby/object:Gem::Version
68
77
  version: '0'
69
78
  - !ruby/object:Gem::Dependency
70
79
  name: bundler
71
80
  requirement: !ruby/object:Gem::Requirement
81
+ none: false
72
82
  requirements:
73
- - - '>='
83
+ - - ! '>='
74
84
  - !ruby/object:Gem::Version
75
85
  version: '0'
76
86
  type: :development
77
87
  prerelease: false
78
88
  version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
79
90
  requirements:
80
- - - '>='
91
+ - - ! '>='
81
92
  - !ruby/object:Gem::Version
82
93
  version: '0'
83
94
  - !ruby/object:Gem::Dependency
84
95
  name: mocha
85
96
  requirement: !ruby/object:Gem::Requirement
97
+ none: false
86
98
  requirements:
87
- - - '>='
99
+ - - ! '>='
88
100
  - !ruby/object:Gem::Version
89
101
  version: '0'
90
102
  type: :development
91
103
  prerelease: false
92
104
  version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
93
106
  requirements:
94
- - - '>='
107
+ - - ! '>='
95
108
  - !ruby/object:Gem::Version
96
109
  version: '0'
97
110
  - !ruby/object:Gem::Dependency
98
111
  name: test-unit
99
112
  requirement: !ruby/object:Gem::Requirement
113
+ none: false
100
114
  requirements:
101
- - - '>='
115
+ - - ! '>='
102
116
  - !ruby/object:Gem::Version
103
117
  version: '0'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
107
122
  requirements:
108
- - - '>='
123
+ - - ! '>='
109
124
  - !ruby/object:Gem::Version
110
125
  version: '0'
111
126
  description: Simple password manager with local/remote database storage and scrypt
@@ -143,6 +158,8 @@ files:
143
158
  - lib/keyrack/utils.rb
144
159
  - lib/keyrack/version.rb
145
160
  - test/fixtures/database-3.dat
161
+ - test/fixtures/database-yaml-192.dat
162
+ - test/fixtures/database-yaml.dat
146
163
  - test/fixtures/foo.txt
147
164
  - test/helper.rb
148
165
  - test/integration/test_interactive_console.rb
@@ -159,29 +176,32 @@ files:
159
176
  - test/unit/ui/test_console.rb
160
177
  homepage: http://github.com/viking/keyrack
161
178
  licenses: []
162
- metadata: {}
163
179
  post_install_message:
164
180
  rdoc_options: []
165
181
  require_paths:
166
182
  - lib
167
183
  required_ruby_version: !ruby/object:Gem::Requirement
184
+ none: false
168
185
  requirements:
169
- - - '>='
186
+ - - ! '>='
170
187
  - !ruby/object:Gem::Version
171
188
  version: '0'
172
189
  required_rubygems_version: !ruby/object:Gem::Requirement
190
+ none: false
173
191
  requirements:
174
- - - '>='
192
+ - - ! '>='
175
193
  - !ruby/object:Gem::Version
176
194
  version: '0'
177
195
  requirements: []
178
196
  rubyforge_project:
179
- rubygems_version: 2.0.2
197
+ rubygems_version: 1.8.23
180
198
  signing_key:
181
- specification_version: 4
199
+ specification_version: 3
182
200
  summary: Simple password manager
183
201
  test_files:
184
202
  - test/fixtures/database-3.dat
203
+ - test/fixtures/database-yaml-192.dat
204
+ - test/fixtures/database-yaml.dat
185
205
  - test/fixtures/foo.txt
186
206
  - test/helper.rb
187
207
  - test/integration/test_interactive_console.rb
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: efd84a41e075f8edc77908a7bb71ea21a3590af2
4
- data.tar.gz: 3714b2f53884956c10b480ba128efcb58e463727
5
- SHA512:
6
- metadata.gz: d53d8a1ca8ce99b1147907d48d87a6e20a95efe4fcbe13af11d777e179ec294994b6aa903d4f189f27cda35bea060c7cb181e70d1f72f3ff9332b7fe799b9d85
7
- data.tar.gz: e0d31fee61321d665a632ed2e59259778a37824ba19136834c3ece001ca3ef39798fcbe3605862e33a80bac26a1f6872cbbb2db1eb746cd9374c283f0cb9b56d