keyrack 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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