pwss 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,156 @@
1
+ require 'slop'
2
+ require 'pwss/password'
3
+
4
+ module Pwss
5
+ module CommandSyntax
6
+ # return a hash with all the commands and their options
7
+ def self.commands
8
+ h = Hash.new
9
+ self.methods.each do |method|
10
+ if method.to_s.include?("_opts") then
11
+ h = h.merge(eval(method.to_s))
12
+ end
13
+ end
14
+ return h
15
+ end
16
+
17
+ # the default number of seconds password is available in the clipboard
18
+ DEFAULT_WAIT = 45
19
+ # the default password length
20
+ DEFAULT_LENGTH = Pwss::Password::DEFAULT_PASSWORD_LENGTH
21
+
22
+ private
23
+
24
+ def self.version_opts
25
+ opts = Slop::Options.new
26
+ opts.banner = "version -- print version information"
27
+ return { :version => [opts, :version] }
28
+ end
29
+
30
+ def self.init_opts
31
+ opts = Slop::Options.new
32
+ opts.banner = "init [options] -- init a new password file"
33
+ opts.string "-f", "--filename", "Password file to create. Use '.enc' or '.gpg' for encryption"
34
+ return { :init => [opts, :init] }
35
+ end
36
+
37
+ def self.list_opts
38
+ opts = Slop::Options.new
39
+ opts.banner = "list [options] -- list all entries in a file"
40
+ opts.string "-f", "--filename", "Password file to use"
41
+ opts.bool "-c", "--clean", "Clean timestamps from entries"
42
+ return { :list => [opts, :list] }
43
+ end
44
+
45
+ def self.get_opts
46
+ opts = Slop::Options.new
47
+ opts.banner = "get [options] -- get a stored field of a record (it defaults to password)"
48
+
49
+ opts.string "-f", "--filename", "Password file to use"
50
+ opts.bool "--stdout", "Output the password to standard output"
51
+ opts.bool "-h", "--hide", "Hide sensitive fields"
52
+ opts.integer "-w", "--wait", "Number of seconds the field is available in the clipboard (0 = wait for user input)", default: DEFAULT_WAIT
53
+ opts.string "--field", "Field to make available on stdout or clipboard (password by default)"
54
+ return { :get => [opts, :get] }
55
+ end
56
+
57
+ def self.add_and_new_opts
58
+ opts = Slop::Options.new
59
+ opts.banner = "add|new [options] [entry title] -- add an entry and copy its password in the clipboard"
60
+ opts.string "-f", "--filename", "Password file to use"
61
+ opts.integer "-w", "--wait", "Seconds password is available in the clipboard (0 = interactive)", default: DEFAULT_WAIT
62
+ opts.string "-t", "--type", "Create an entry of type TYPE (Entry, CreditCard, BankAccount, SoftwareLicense, Sim).\n Default to 'Entry', which is good enough for websites credentials"
63
+ opts.string "-m", "--method", "Method to generate the password (one of: random, alpha, ask; default to random)"
64
+ opts.bool "--ask", "A shortcut for --method ask"
65
+ opts.integer "-l", "--length", "Password length (when random or alpha; default #{DEFAULT_LENGTH})", default: DEFAULT_LENGTH
66
+ return { :add => [opts, :add_entry],
67
+ :new => [opts, :add_entry] }
68
+ end
69
+
70
+ def self.update_opts
71
+ opts = Slop::Options.new
72
+ opts.banner = "update [options] string -- Update specified field of (user selected) entry matching <string>"
73
+ opts.string "-f", "--filename", "Password file to use"
74
+ opts.string "--field", "Field to update"
75
+ opts.bool "-p", "--password", "an alias for --field password"
76
+ opts.string "-m", "--method", "Method to generate the password (one of: random, alpha, ask; default to random)"
77
+ opts.bool "--ask", "A shortcut for [--field password] --method ask"
78
+ opts.integer "-l", "--length", "Password length (when random or alpha; default #{DEFAULT_LENGTH})", default: DEFAULT_LENGTH
79
+ opts.integer "-w", "--wait", "Seconds new field is available in the clipboard for (0 = interactive)", default: DEFAULT_WAIT
80
+ return { :update => [opts, :update] }
81
+ end
82
+
83
+ def self.destroy_opts
84
+ opts = Slop::Options.new
85
+ opts.banner = "destroy|rm [options] string -- Destroy a user-selected entry matching <string>, after user confirmation"
86
+ opts.string "-f", "--filename", "Password file to create. Use extension '.enc' to encrypt it"
87
+ return { :destroy => [opts, :destroy],
88
+ :rm => [opts, :destroy] }
89
+ end
90
+
91
+ def self.encrypt_opts
92
+ opts = Slop::Options.new
93
+ opts.banner = "encrypt [options] -- Encrypt a password file"
94
+ opts.bool "--symmetric", "Use symmetric encryption"
95
+ opts.bool "--gpg", "Use gpg (default: no need to specify it)"
96
+ opts.string "-f", "--filename", "Password file to encrypt. Write to <file>.[enc,gpg]"
97
+ return { :encrypt => [opts, :encrypt] }
98
+ end
99
+
100
+ def self.decrypt_opts
101
+ opts = Slop::Options.new
102
+ opts.banner = "decrypt [options] -- Decrypt a password file"
103
+ opts.string "-f", "--filename", "Password file to encrypt. Write to <file>.enc"
104
+ return { :decrypt => [opts, :decrypt] }
105
+ end
106
+
107
+ def self.console_opts
108
+ opts = Slop::Options.new
109
+ opts.banner = "console [options] -- Enter the console"
110
+ opts.string "-f", "--filename", "Password file to encrypt. Write to <file>.enc"
111
+ return { :console => [opts, :console] }
112
+ end
113
+
114
+ def self.man_opts
115
+ opts = Slop::Options.new
116
+ opts.banner = "man -- print a manual page"
117
+ return { :man => [opts, :man] }
118
+ end
119
+
120
+ def self.help_opts
121
+ opts = Slop::Options.new
122
+ opts.banner = "help [command] -- print usage string"
123
+ return { :help => [opts, :help] }
124
+ end
125
+
126
+ def self.describe_opts
127
+ opts = Slop::Options.new
128
+ opts.banner = "describe [options] -- describe fields of an entry type or all types"
129
+ opts.string "-t", "--type", "Type to describe"
130
+ return { :describe => [opts, :describe] }
131
+ end
132
+
133
+ #
134
+ # COMMANDS WHICH MAKE SENSE ONLY WITH THE CONSOLE
135
+ #
136
+
137
+ # change the default file
138
+ def self.open_opts
139
+ opts = Slop::Options.new
140
+ opts.banner = "open [options] -- change the default file used in the console"
141
+ opts.banner = " (makes sense only when launched from the console) "
142
+ opts.string "-f", "--filename", "Password file to use"
143
+ return { :open => [opts, :open] }
144
+ end
145
+
146
+ # which is the default file?
147
+ def self.default_opts
148
+ opts = Slop::Options.new
149
+ opts.banner = "default -- which file is the console currently operating on?"
150
+ opts.banner = " (makes sense only when launched from the console) "
151
+ return { :default => [opts, :default] }
152
+ end
153
+
154
+
155
+ end
156
+ end
@@ -1,33 +1,42 @@
1
1
  require 'fileutils'
2
+ require 'gpgme'
2
3
 
3
4
  #
4
5
  # From file to string and back
5
6
  # There is no lower level than this
6
7
  #
7
- module FileOps
8
- # load a file into a string
9
- def self.load filename
10
- file = File.open(filename, "rb")
11
- file.read
12
- end
8
+ module Pwss
9
+ module FileOps
10
+ # load a file into a string
11
+ def self.load filename
12
+ file = File.open(filename, "rb")
13
+ file.read
14
+ end
13
15
 
14
- # save a string to a file
15
- def self.save filename, data
16
- file = File.open(filename, "wb")
17
- file.write data
18
- file.close
19
- # puts "Password safe #{filename} updated."
20
- end
16
+ # save a string to a file
17
+ def self.save filename, data
18
+ file = File.open(filename, "wb")
19
+ file.write data
20
+ file.close
21
+ end
21
22
 
22
- # check if the extension is ".enc"
23
- def self.encrypted? filename
24
- File.extname(filename) == ".enc"
25
- end
23
+ # check if the extension is ".enc"
24
+ def self.encrypted? filename
25
+ gpg? filename or symmetric? filename
26
+ end
26
27
 
27
- def self.backup filename
28
- FileUtils::cp filename, filename + "~"
29
- puts "Backup copy of password safe created in #{filename}~."
28
+ def self.symmetric? filename
29
+ File.extname(filename) == ".enc"
30
+ end
31
+
32
+ def self.gpg? filename
33
+ File.extname(filename) == ".gpg"
34
+ end
35
+
36
+ def self.backup filename
37
+ FileUtils::cp filename, filename + "~"
38
+ puts "Backup copy of password safe created in #{filename}~."
39
+ end
40
+
30
41
  end
31
-
32
42
  end
33
-
@@ -0,0 +1,18 @@
1
+ require 'pwss/generators/entry'
2
+
3
+ module Pwss
4
+ class BankAccount < Entry
5
+ def initialize
6
+ super
7
+ @fields = [
8
+ "title",
9
+ "name",
10
+ "iban",
11
+ "url",
12
+ "username",
13
+ "password",
14
+ "description"
15
+ ]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require 'pwss/generators/entry'
2
+
3
+ module Pwss
4
+ class Code < Entry
5
+ def initialize
6
+ super
7
+ @fields = [
8
+ "title",
9
+ "code",
10
+ ]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ require 'pwss/generators/entry'
2
+
3
+ module Pwss
4
+ class CreditCard < Entry
5
+ def initialize
6
+ super
7
+ @fields = [
8
+ "title",
9
+ "issuer",
10
+ "name_on_card",
11
+ "card_number",
12
+ "valid_from",
13
+ "valid_till",
14
+ "verification_number",
15
+ "pin",
16
+ "url",
17
+ "username",
18
+ "password",
19
+ "description"
20
+ ]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ module Pwss
2
+ #
3
+ # Entry generates an entry for the password safe
4
+ # It is a wrapper to a Hash
5
+ #
6
+ class Entry
7
+ attr_reader :entry
8
+ attr_reader :fields
9
+
10
+ def initialize
11
+ @entry = Hash.new
12
+ @fields = [
13
+ "title",
14
+ "url",
15
+ "username",
16
+ "password",
17
+ "recovery_email",
18
+ "description"
19
+ ]
20
+ end
21
+
22
+ # interactively ask the fields specified in +@fields+
23
+ #
24
+ # optional hash +arguments+ allows to pass arguments to the
25
+ # input-asking functions (including the default value for a key)
26
+ # See the documentation of Pwss::Fields::ask for more details.
27
+ #
28
+ def ask arguments = {}
29
+ @entry = Hash.new
30
+ @fields.each do |key|
31
+ @entry[key] = Fields.ask key, arguments
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,119 @@
1
+ require 'readline'
2
+ require 'date'
3
+
4
+ require 'pwss/password'
5
+
6
+ module Pwss
7
+ module Fields
8
+ INPUT_F = 0
9
+ DEFAULT = 1
10
+ HIDDEN = 2
11
+
12
+ # this is a set of fields useful for different types of entries
13
+ # each entry will reference the symbols it needs to make sense
14
+ FIELDS = {
15
+ # everyone has...
16
+ "title" => ["Readline.readline('title: ')", "''", false],
17
+ "url" => ["Readline.readline('url: ')", "''", false],
18
+ "username" => ["Readline.readline('username: ')", "''", false],
19
+ "recovery_email" => ["Readline.readline('email: ')", "''", false],
20
+ "password" => ["Pwss::Password.password(arguments)", "Pwss::Password.password", true],
21
+ "description" => ["get_lines", "''", false],
22
+
23
+ # banks also have ...
24
+ "name" => ["Readline.readline('name: ')", "''", false],
25
+ "iban" => ["Readline.readline('iban: ')", "ITkk xaaa aabb bbbc cccc cccc ccc", false],
26
+
27
+ # cards also have
28
+ "issuer" => ["Readline.readline('issuer: ')", "''", false],
29
+ "name_on_card" => ["Readline.readline('name on card: ')", "''", false],
30
+ "card_number" => ["Readline.readline('number: ')", "''", true],
31
+ "valid_from" => ["Readline.readline('valid from: ')", "''", false],
32
+ "valid_till" => ["Readline.readline('valid till: ')", "''", false],
33
+ "verification_number" => ["Readline.readline('verification number: ')", "''", true],
34
+ "pin" => ["Readline.readline('pin: ')", "''", true],
35
+
36
+ # SIMs also have
37
+ "puk" => ["Readline.readline('puk: ')", "XXXX", true],
38
+ "phone" => ["Readline.readline('phone: ')", "NNN NNN NNNN", false],
39
+
40
+ # Code has only title and code
41
+ "code" => ["Readline.readline('code: ')", "XXXX", true],
42
+
43
+ # useful for software licenses
44
+ "version" => ["Readline.readline('version: ')", "''", false],
45
+ "licensed_to" => ["Readline.readline('licensed to: ')", "''", false],
46
+ "license_number" => ["Readline.readline('license number: ')", "''", true],
47
+ "purchased_on" => ["Readline.readline('purchased on: ')", "Date.today", false],
48
+ }
49
+
50
+ # ask the value for +key+
51
+ #
52
+ # This is performed by invoking the function defined for +key+ in the
53
+ # +FIELDS+, which typically asks for user input. If the user enters a
54
+ # value this is the one the function returns, otherwise we return the
55
+ # default value defined for +key+ in +FIELDS+.
56
+ #
57
+ # Optional hash +arguments+ contains a list of arguments to be passed to
58
+ # the function in +FIELDS+. This allows to customize the behaviour of the
59
+ # user-input function.
60
+ #
61
+ # **As a special case, if +arguments+ contains +key+, this is returned as
62
+ # the value. This allows to set the default for a +key+ outside this
63
+ # module.**
64
+ #
65
+ # Thus, for instance: ask 'username', {'username' => 'a'} will return 'a'.
66
+ #
67
+ def self.ask key, arguments
68
+ # if the default is specified outside this class, return it!
69
+ return arguments[key] if arguments[key]
70
+
71
+ # ... otherwise, do some work and ask for the value!
72
+ input_f = FIELDS[key] ? FIELDS[key][INPUT_F] : "Readline.readline('#{key}: ')"
73
+ default = FIELDS[key] ? FIELDS[key][DEFAULT] : nil
74
+ value = eval input_f
75
+ if value != nil and value != "" then
76
+ value
77
+ else
78
+ default
79
+ end
80
+ end
81
+
82
+ # read n-lines (terminated by a ".")
83
+ def self.get_lines
84
+ puts "description (terminate with '.'):"
85
+ lines = []
86
+ line = ""
87
+ until line == "."
88
+ line = Readline.readline
89
+ lines << line if line != "."
90
+ end
91
+ lines.join("\n")
92
+ end
93
+
94
+ # take a hash as input and reorder the fields according to the order in
95
+ # which the fields are defined in the +FIELDS+ variable
96
+ #
97
+ # this function is used to present records in the YAML file always in the
98
+ # same order.
99
+ def self.to_clean_hash hash
100
+ output = Hash.new
101
+ FIELDS.keys.each do |field|
102
+ output[field] = hash[field] if hash[field]
103
+ end
104
+ # all the remaining fields (i.e., user-defined fields in records)
105
+ (hash.keys - FIELDS.keys).each do |field|
106
+ output[field] = hash[field]
107
+ end
108
+ output
109
+ end
110
+
111
+ def self.sensitive? field
112
+ FIELDS[field][HIDDEN]
113
+ end
114
+
115
+ def self.sensitive
116
+ FIELDS.select { |x| FIELDS[x][HIDDEN] }.keys
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,15 @@
1
+ require 'pwss/generators/entry'
2
+
3
+ module Pwss
4
+ class Sim < Entry
5
+ def initialize
6
+ super
7
+ @fields = [
8
+ "title",
9
+ "phone",
10
+ "pin",
11
+ "puk"
12
+ ]
13
+ end
14
+ end
15
+ end