safe 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -4,6 +4,34 @@ user IDs and passwords using a single password as the master key. It stores the
4
4
  master key as a hash (SHA2), and uses it to encrypt the rest of the data using
5
5
  Blowfish.
6
6
 
7
+ == A Note About Moving from 0.3 to 0.4 ==
8
+ The format of the file changed slightly. Specifically, the salt and hash values
9
+ were changed to be attributes of the root safe element. You must edit your
10
+ .safe.xml file accordingly.
11
+
12
+ Example:
13
+ 0.3 version:
14
+ <safe>
15
+ <salt>
16
+ foo
17
+ </salt>
18
+ <hash>
19
+ bar
20
+ </hash>
21
+ <entries>
22
+ ...
23
+ </entries>
24
+ </salt>
25
+
26
+ 0.4 version:
27
+ <safe salt="foo" hash="bar">
28
+ <entries>
29
+ ...
30
+ </entries>
31
+ </salt>
32
+
33
+ This is a one-time operation.
34
+
7
35
  == License ==
8
36
  Safe is licensed under the BSD License. Copyright (c) 2007, Rob Warner
9
37
 
@@ -37,7 +65,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37
65
  To install safe, you must have Ruby and Ruby Gems installed. From the command
38
66
  line, type:
39
67
 
40
- gem install safe --include-dependencies
68
+ gem install safe
41
69
 
42
70
  safe depends on two gems:
43
71
 
@@ -59,6 +87,7 @@ Windows:
59
87
  0.2 -- August 19, 2007
60
88
  0.3 -- September 7, 2007
61
89
  0.4 -- April 4, 2008
90
+ 0.5 -- April 6, 2008
62
91
 
63
92
  See Release-Notes.txt for more information
64
93
 
@@ -107,7 +136,9 @@ Options:
107
136
  -l, --list NAME List an entry
108
137
  -v, --version Print version
109
138
  -p, --password Change password
110
-
139
+ -f, --diff DIR Diff against password file in directory DIR
140
+ -m, --merge DIR Merge with password file in directory DIR
141
+
111
142
  For example, to add an entry to your password file for Rubyforge.org, you type:
112
143
 
113
144
  safe -a Rubyforge.org
@@ -147,6 +178,21 @@ You will be prompted for your master password, and then your Rubyforge.org
147
178
  password is irretrievably deleted. You will not be asked to confirm the
148
179
  deletion.
149
180
 
181
+ == Diff and Merge ==
182
+ Diffing and merging deviates slightly from diffing and merging other types of
183
+ files, since typically users want a union of the files without deleting any
184
+ entries. Consequently, instead of Add/Delete/Change, the output of diff will
185
+ indicate Master/Other/Change, meaning the entry is only in the Master copy
186
+ (the one in the directory pointed to by SAFE_DIR), the Other copy (the one in
187
+ the directory passed on the command line), or is in both but has been Changed,
188
+ respectively.
189
+
190
+ Merge does two things:
191
+ 1) Adds all entries from the Other copy that don't exist in the Master copy
192
+ to the Master copy
193
+ 2) Interactively performs requested changes to changed entries on the Master
194
+ copy
195
+
150
196
  == Backups ==
151
197
  Please back up your .safe.xml file! See License section for disclaimer
152
198
  information.
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ gem_spec = Gem::Specification.new do |spec|
7
7
  spec.summary = 'A command-line password storage program'
8
8
  spec.description = %{safe safely stores all your user IDs and passwords,
9
9
  encrypted by a password of your choosing.}
10
- spec.version = '0.4'
10
+ spec.version = '0.5'
11
11
 
12
12
  # Author information
13
13
  spec.author = 'Rob Warner'
data/Release-Notes.txt CHANGED
@@ -1,5 +1,11 @@
1
1
  Safe Release Notes
2
2
  ------------------
3
+ 0.5 (April 6, 2008)
4
+ -------------------
5
+ [FIXED] Version number in help output corrected
6
+ [NEW] Added diff and merge capabilities
7
+ [CHANGE] Incorrect password no longer displays stack trace
8
+
3
9
  0.4 (April 4, 2008)
4
10
  -------------------
5
11
  [FIXED] Passwords stopped authenticating (change in REXML perhaps?--whitespace returned in salt and hash), so moved salt
data/bin/safe CHANGED
@@ -41,7 +41,7 @@ require File.join(File.dirname(__FILE__), '..', 'lib', 'safeutils')
41
41
  # TODO support list <NAME> without -l flag
42
42
 
43
43
  class Safe
44
- SAFE_VERSION = '0.3'
44
+ SAFE_VERSION = '0.5'
45
45
 
46
46
  # Gets the command line
47
47
  def self.get_command_line
@@ -76,6 +76,14 @@ class Safe
76
76
  opts.on('-p', '--password', 'Change password') do
77
77
  options[:action] = 'password'
78
78
  end
79
+ opts.on('-f', '--diff DIR', 'Diff against password file in directory DIR') do |val|
80
+ options[:name] = val
81
+ options[:action] = 'diff'
82
+ end
83
+ opts.on('-m', '--merge DIR', 'Merge with password file in directory DIR') do |val|
84
+ options[:name] = val
85
+ options[:action] = 'merge'
86
+ end
79
87
  opts.parse! rescue abort [$!.message, opts].join("\n")
80
88
  options
81
89
  end
@@ -88,21 +96,22 @@ class Safe
88
96
  else
89
97
  begin
90
98
  dir = SafeUtils::get_safe_dir
99
+ file = SafeUtils::get_safe_file(dir)
100
+
101
+ # Perform the requested action
102
+ case options[:action]
103
+ when 'list': file.list(options[:name])
104
+ when 'add': file.add(options[:name])
105
+ when 'delete': file.delete(options[:name])
106
+ when 'count': puts file.count
107
+ when 'password': file.change_password
108
+ when 'import': SafeUtils::import(file, options[:name])
109
+ when 'diff': SafeUtils::diff(file, options[:name])
110
+ when 'merge': SafeUtils::merge(file, options[:name])
111
+ end
91
112
  rescue
92
113
  abort $!.message
93
114
  end
94
- password = SafeUtils::get_password
95
- file = SafeFile.new(password, dir)
96
-
97
- # Perform the requested action
98
- case options[:action]
99
- when 'list': file.list(options[:name])
100
- when 'add': file.add(options[:name])
101
- when 'delete': file.delete(options[:name])
102
- when 'count': puts file.count
103
- when 'password': file.change_password
104
- when 'import': SafeUtils::import(file, options[:name])
105
- end
106
115
  end
107
116
  end
108
117
  end
data/lib/safediff.rb ADDED
@@ -0,0 +1,80 @@
1
+ ########################################################################
2
+ # safediff.rb
3
+ # Copyright (c) 2007, Rob Warner
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ # * Neither the name of Rob Warner nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ ########################################################################
31
+
32
+ ##
33
+ # SafeDiff
34
+ # Contains a difference between two safe entries
35
+ ##
36
+ class SafeDiff
37
+ attr_accessor :operation, :master, :other
38
+
39
+ MASTER = "m"
40
+ OTHER = "o"
41
+ CHANGE = "c"
42
+
43
+ def initialize(master, other)
44
+ self.master = master
45
+ self.other = other
46
+ compare
47
+ end
48
+
49
+ def compare
50
+ if self.master != nil && self.master.equals?(self.other)
51
+ raise "Entries are not different:\n#{self.master.to_s}\n#{self.other.to_s}"
52
+ elsif self.master != nil && self.other == nil
53
+ self.operation = MASTER
54
+ elsif self.master == nil && self.other != nil
55
+ self.operation = OTHER
56
+ else
57
+ self.operation = CHANGE
58
+ end
59
+ end
60
+
61
+ def to_s
62
+ s = ""
63
+ s << self.operation
64
+ s << "\n"
65
+ if self.operation == MASTER || self.operation == CHANGE
66
+ s << "< "
67
+ s << self.master.to_s
68
+ s << "\n"
69
+ end
70
+ if self.operation == CHANGE
71
+ s << "---\n"
72
+ end
73
+ if self.operation == OTHER || self.operation == CHANGE
74
+ s << "> "
75
+ s << self.other.to_s
76
+ s << "\n"
77
+ end
78
+ s
79
+ end
80
+ end
data/lib/safeentry.rb CHANGED
@@ -37,9 +37,9 @@ class SafeEntry
37
37
  attr_accessor :name, :id, :password
38
38
 
39
39
  def initialize(name, id, password)
40
- @name = name
41
- @id = id
42
- @password = password
40
+ self.name = name
41
+ self.id = id
42
+ self.password = password
43
43
  end
44
44
 
45
45
  def to_s
@@ -49,5 +49,13 @@ class SafeEntry
49
49
  def <=>(other)
50
50
  self.name.upcase <=> other.name.upcase
51
51
  end
52
+
53
+ def equals? other
54
+ if other == nil
55
+ false
56
+ else
57
+ self.name == other.name && self.id == other.id && self.password == other.password
58
+ end
59
+ end
52
60
  end
53
61
 
data/lib/safeutils.rb CHANGED
@@ -28,6 +28,7 @@
28
28
  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
29
  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
30
  ########################################################################
31
+ require File.join(File.dirname(__FILE__), 'safediff')
31
32
  require 'highline/import'
32
33
 
33
34
  ##
@@ -73,4 +74,67 @@ class SafeUtils
73
74
  raise "Problem importing file #{import_file}"
74
75
  end
75
76
  end
77
+
78
+ def SafeUtils.get_safe_file(dir, prompt = 'Password: ')
79
+ password = SafeUtils::get_password(prompt)
80
+ SafeFile.new(password, dir)
81
+ end
82
+
83
+ # Diffs two .safe.xml files
84
+ def SafeUtils.diff(safe_file, dir)
85
+ other_file = get_safe_file(dir, "#{dir} Password: ")
86
+ diffs = get_diffs(safe_file, other_file)
87
+ puts diffs
88
+ end
89
+
90
+ # Merges two .safe.xml files
91
+ def SafeUtils.merge(safe_file, dir)
92
+ other_file = get_safe_file(dir, "#{dir} Password: ")
93
+ diffs = get_diffs(safe_file, other_file)
94
+ do_merge safe_file, diffs
95
+ safe_file.save
96
+ end
97
+
98
+ # Returns the SafeDiff objects in an Array
99
+ def SafeUtils.get_diffs(safe_file, other_file)
100
+ diffs = Array.new
101
+
102
+ # Find master-only and changes
103
+ safe_file.entries.each_value do |entry|
104
+ if other_file.entries.has_key? entry.name
105
+ other_entry = other_file.entries[entry.name]
106
+ diffs << SafeDiff.new(entry, other_entry) unless entry.equals? other_entry
107
+ else
108
+ diffs << SafeDiff.new(entry, nil)
109
+ end
110
+ end
111
+
112
+ # Find other-only
113
+ other_file.entries.each_value do |entry|
114
+ if !safe_file.entries.has_key? entry.name
115
+ diffs << SafeDiff.new(nil, entry)
116
+ end
117
+ end
118
+
119
+ diffs
120
+ end
121
+
122
+ def SafeUtils.do_merge(safe_file, diffs, override_master = false)
123
+ diffs.each do |diff|
124
+ case diff.operation
125
+ when SafeDiff::MASTER: # Do nothing
126
+ when SafeDiff::OTHER: # Add to safe file
127
+ safe_file.insert(diff.other.name, diff.other.id, diff.other.password)
128
+ when SafeDiff::CHANGE: # Update safe file
129
+ if override_master || replace_master?(diff)
130
+ safe_file.insert(diff.other.name, diff.other.id, diff.other.password)
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ def SafeUtils.replace_master?(diff)
137
+ print "Replace:\n\t#{diff.master.to_s}\nWith:\n\t#{diff.other.to_s}\n?"
138
+ gets.chomp == 'y'
139
+ end
76
140
  end
@@ -0,0 +1,52 @@
1
+ ########################################################################
2
+ # tc_safediff.rb
3
+ # Copyright (c) 2007 Rob Warner.
4
+ # All rights reserved. This program and the accompanying materials
5
+ # are made available under the terms of the Eclipse Public License v1.0
6
+ # which accompanies this distribution, and is available at
7
+ # http://www.eclipse.org/legal/epl-v10.html
8
+ ########################################################################
9
+
10
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'safediff')
11
+ require 'test/unit'
12
+
13
+ class SafeDiffTest < Test::Unit::TestCase
14
+ def test_diff
15
+ e1 = SafeEntry.new('apple', 'baker', 'charlie')
16
+ e2 = SafeEntry.new('apple', 'baker', 'charlie')
17
+ e3 = SafeEntry.new('apple1', 'baker', 'charlie')
18
+ e4 = SafeEntry.new('apple', 'baker1', 'charlie')
19
+ e5 = SafeEntry.new('apple', 'baker', 'charlie1')
20
+
21
+ # Test two equal entries
22
+ begin
23
+ diff = SafeDiff.new e1, e2
24
+ rescue RuntimeError
25
+ assert_equal "Entries are not different:\n#{e1.to_s}\n#{e2.to_s}", $!.message
26
+ end
27
+
28
+ # Test an OTHER
29
+ diff = SafeDiff.new nil, e1
30
+ assert_equal diff.operation, SafeDiff::OTHER
31
+ assert_equal diff.to_s, "#{SafeDiff::OTHER}\n> #{e1.to_s}\n"
32
+
33
+ # Test a MASTER
34
+ diff = SafeDiff.new e1, nil
35
+ assert_equal diff.operation, SafeDiff::MASTER
36
+ assert_equal diff.to_s, "#{SafeDiff::MASTER}\n< #{e1.to_s}\n"
37
+
38
+ # Test a change
39
+ diff = SafeDiff.new e1, e3
40
+ assert_equal diff.operation, SafeDiff::CHANGE
41
+ assert_equal diff.to_s, "#{SafeDiff::CHANGE}\n< #{e1.to_s}\n---\n> #{e3.to_s}\n"
42
+
43
+ # Test various changes
44
+ diff = SafeDiff.new e1, e4
45
+ assert_equal diff.operation, SafeDiff::CHANGE
46
+ assert_equal diff.to_s, "#{SafeDiff::CHANGE}\n< #{e1.to_s}\n---\n> #{e4.to_s}\n"
47
+
48
+ diff = SafeDiff.new e1, e5
49
+ assert_equal diff.operation, SafeDiff::CHANGE
50
+ assert_equal diff.to_s, "#{SafeDiff::CHANGE}\n< #{e1.to_s}\n---\n> #{e5.to_s}\n"
51
+ end
52
+ end
data/test/tc_safeutils.rb CHANGED
@@ -42,4 +42,60 @@ class SafeUtilsTest < Test::Unit::TestCase
42
42
  # Make sure the directory exists
43
43
  FileUtils::cd dir
44
44
  end
45
+
46
+ def test_diff
47
+ f1 = SafeFile.new("password", ".")
48
+ f2 = SafeFile.new("password", ".", ".safe2.xml")
49
+
50
+ f1.insert('apple', 'baker', 'charlie')
51
+ diffs = SafeUtils.get_diffs(f1, f2)
52
+ assert_equal 1, diffs.length
53
+ assert_equal "m\n< apple\tbaker\tcharlie\n", diffs[0].to_s
54
+
55
+ f2.insert('apple', 'baker', 'charlie')
56
+ diffs = SafeUtils.get_diffs(f1, f2)
57
+ assert_equal 0, diffs.length
58
+
59
+ f2.insert('foo', 'bar', 'baz')
60
+ diffs = SafeUtils.get_diffs(f1, f2)
61
+ assert_equal 1, diffs.length
62
+ assert_equal "o\n> foo\tbar\tbaz\n", diffs[0].to_s
63
+
64
+ f2.insert('apple', 'ibm', 'compaq')
65
+ f2.delete('foo')
66
+ diffs = SafeUtils.get_diffs(f1, f2)
67
+ assert_equal 1, diffs.length
68
+ assert_equal "c\n< apple\tbaker\tcharlie\n---\n> apple\tibm\tcompaq\n", diffs[0].to_s
69
+
70
+ FileUtils::rm("./.safe2.xml")
71
+ FileUtils::rm("./.safe.xml")
72
+ end
73
+
74
+ def test_merge
75
+ f1 = SafeFile.new("password", ".")
76
+ f2 = SafeFile.new("password", ".", ".safe2.xml")
77
+
78
+ f1.insert('apple', 'baker', 'charlie')
79
+ f1.insert('one', 'baker', 'charlie')
80
+ f1.insert('two', 'baker', 'charlie')
81
+ f1.insert('three', 'baker', 'charlie')
82
+ f1.insert('four', 'baker', 'charlie')
83
+ f1.insert('five', 'baker', 'charlie')
84
+
85
+ f2.insert('three', 'baker', 'charlie')
86
+ f2.insert('four', 'goober', 'raisinet')
87
+ f2.insert('five', 'coke', 'pepsi')
88
+ f2.insert('six', 'baker', 'charlie')
89
+ f2.insert('seven', 'baker', 'charlie')
90
+ f2.insert('eight', 'baker', 'charlie')
91
+
92
+ diffs = SafeUtils.get_diffs(f1, f2)
93
+ SafeUtils.do_merge(f1, diffs, true)
94
+ assert_equal 9, f1.count
95
+ assert_equal "four\tgoober\traisinet", f1.entries['four'].to_s
96
+ assert_equal "five\tcoke\tpepsi", f1.entries['five'].to_s
97
+
98
+ FileUtils::rm("./.safe2.xml")
99
+ FileUtils::rm("./.safe.xml")
100
+ end
45
101
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safe
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.4"
4
+ version: "0.5"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Warner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-04-04 00:00:00 -04:00
12
+ date: 2008-04-06 00:00:00 -04:00
13
13
  default_executable: safe
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -40,6 +40,7 @@ extra_rdoc_files: []
40
40
 
41
41
  files:
42
42
  - README
43
+ - lib/safediff.rb
43
44
  - lib/safeentry.rb
44
45
  - lib/safefile.rb
45
46
  - lib/safeutils.rb
@@ -73,6 +74,7 @@ signing_key:
73
74
  specification_version: 2
74
75
  summary: A command-line password storage program
75
76
  test_files:
77
+ - test/tc_safediff.rb
76
78
  - test/tc_safeentry.rb
77
79
  - test/tc_safefile.rb
78
80
  - test/tc_safeutils.rb