pwss 0.5.1 → 0.6.0

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.
@@ -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