leeloo 0.0.16 → 0.4.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,40 +1,207 @@
1
- require 'fileutils'
2
1
  require 'gpgme'
2
+ require 'fileutils'
3
3
  require 'git'
4
+ require 'base64'
4
5
 
5
6
  module Leeloo
7
+
8
+ class KeystoreFactory
9
+ def self.create keystore
10
+ keystore_created = nil
11
+ case keystore["cypher"]
12
+ when "gpg"
13
+ keystore_created = GpgPrivateLocalFileSystemKeystore.new keystore["name"], keystore["path"]
14
+ else
15
+ keystore_created = PrivateLocalFileSystemKeystore.new keystore["name"], keystore["path"]
16
+ end
17
+
18
+ case keystore["vc"]
19
+ when "git"
20
+ GitKeystoreDecorator.new keystore_created
21
+ else
22
+ keystore_created
23
+ end
24
+ end
25
+ end
26
+
6
27
  class Keystore
7
28
 
8
- def self.secret_key_empty?
9
- GPGME::Key.find(:secret, nil, ).empty?
29
+ attr_reader :name
30
+
31
+ def initialize name
32
+ @name = name
33
+ end
34
+
35
+ def secrets
36
+ # returns the secrets list
10
37
  end
11
38
 
12
- def self.add_keystore name, path
13
- FileUtils.mkdir_p path
14
- FileUtils.mkdir_p "#{path}/secrets/"
15
- FileUtils.mkdir_p "#{path}/keys/"
39
+ def secret_of path
40
+ # returns a secret object
41
+ end
42
+
43
+ def secret_from_name name
44
+ # returns a secret object
45
+ end
46
+
47
+ def sync
48
+ # synchronizes the keystore
49
+ end
50
+
51
+ def init
52
+ # initialize the keystore
53
+ end
54
+
55
+ def footprint name
56
+ # footprint a given secret path
57
+ end
16
58
 
17
- GPGME::Key.find(:public, nil, ).each do |key|
18
- key.export(:output => File.open("#{path}/keys/#{key.uids.first.email}", "w+"))
59
+ def secret_from_footprint footprint
60
+ # returns a secret object
61
+ end
62
+
63
+ def == keystore
64
+ self.name == keystore.name
65
+ end
66
+ end
67
+
68
+ class PrivateLocalFileSystemKeystore < Keystore
69
+
70
+ attr_reader :path
71
+
72
+ def initialize name, path
73
+ super name
74
+ @path = path
75
+ FileUtils.mkdir_p "#{@path}/secrets"
76
+ end
77
+
78
+ def secrets
79
+ find_secrets "#{@path}/secrets"
80
+ end
81
+
82
+ def find_secrets path
83
+ elements = []
84
+ Dir.glob("#{path}/**") do |element|
85
+ elements << secret_of(element) unless Dir.exist? element
86
+ elements << find_secrets(element) if Dir.exist? element
19
87
  end
88
+ return elements.flatten
89
+ end
90
+
91
+ def == keystore
92
+ self.name == keystore.name && self.path == keystore.path
93
+ end
20
94
 
21
- g = Git.init path
22
- g.add
23
- g.commit "keystore #{path} added"
95
+ def secret_of path
96
+ name = path.gsub("#{@path}/secrets/", "")
97
+ LocalFileSystemSecret.new path, name
24
98
  end
25
99
 
26
- def self.add_remote path, remote
27
- g = Git.open path
28
- g.add_remote 'origin', remote
100
+ def secret_from_name name
101
+ secret_of "#{path}/secrets/#{name}"
29
102
  end
30
103
 
31
- def self.sync_keystore path
32
- g = Git.open path
33
- unless g.remotes.empty?
34
- g.pull
35
- g.push
104
+ def footprint_of name
105
+ secret = secret_from_name name
106
+ { "footprint" => secret.footprint, "keystore" => self.name, "secret" => secret.name }
107
+ end
108
+
109
+ def secret_from_footprint footprint
110
+ secret = secret_from_name footprint["secret"]
111
+ unless secret.footprint == footprint["footprint"]
112
+ raise "footprint is not valid"
36
113
  end
37
- return !g.remotes.empty?
114
+ secret
115
+ end
116
+
117
+ end
118
+
119
+ class GpgPrivateLocalFileSystemKeystore < PrivateLocalFileSystemKeystore
120
+
121
+ def initialize name, path
122
+ super name, path
123
+ FileUtils.mkdir_p "#{@path}/keys"
124
+
125
+ @recipients = []
126
+ Dir.glob("#{path}/keys/*") { |key| @recipients << File.basename(key) }
127
+ @recipients.each { |key| GPGME::Key.import(File.open("#{path}/keys/#{key}")) }
128
+ end
129
+
130
+ def init
131
+ super
132
+ GPGME::Key.find(:public, nil, ).each { |key| key.export(:output => File.open("#{path}/keys/#{key.uids.first.email}", "w+")) }
133
+ end
134
+
135
+ def secret_of path
136
+ name = path.gsub("#{@path}/secrets/", "").gsub(".gpg", "")
137
+ GpgLocalFileSystemSecret.new path, name, @recipients
138
+ end
139
+
140
+ def secret_from_name name
141
+ secret_of "#{path}/secrets/#{name}.gpg"
142
+ end
143
+
144
+ def footprint_of name
145
+ footprint = super name
146
+ footprint["sign"] = Base64.strict_encode64 GPGME::Crypto.new.sign(footprint["footprint"]).to_s
147
+ footprint
148
+ end
149
+
150
+ def secret_from_footprint footprint
151
+ data = GPGME::Crypto.new.verify(Base64.strict_decode64 footprint["sign"]) { |signature| signature.valid? }
152
+ if data.read == footprint["footprint"]
153
+ super footprint
154
+ else
155
+ raise "signature is not valid"
156
+ end
157
+ end
158
+ end
159
+
160
+ class GitKeystoreDecorator < Keystore
161
+ def initialize keystore
162
+ @keystore = keystore
163
+ Git.init @keystore.path
164
+ @git = Git.open keystore.path
165
+ end
166
+
167
+ def secret_of element
168
+ GitSecretDecorator.new(@git, element)
169
+ end
170
+
171
+ def secret_from_name element
172
+ secret_of @keystore.secret_from_name(element)
173
+ end
174
+
175
+ def secrets
176
+ @keystore.secrets
177
+ end
178
+
179
+ def name
180
+ @keystore.name
181
+ end
182
+
183
+ def path
184
+ @keystore.path
185
+ end
186
+
187
+ def footprint_of element
188
+ @keystore.footprint_of element
189
+ end
190
+
191
+ def secret_from_footprint footprint
192
+ @keystore.secret_from_footprint footprint
193
+ end
194
+
195
+ def sync
196
+ @git.pull
197
+ @keystore.sync
198
+ @git.push
199
+ end
200
+
201
+ def init
202
+ @keystore.init
203
+ @git.add
204
+ @git.commit "keystore #{@keystore.name} added"
38
205
  end
39
206
 
40
207
  end
@@ -0,0 +1,149 @@
1
+ require 'clipboard'
2
+ require 'tty-table'
3
+ require 'tty-tree'
4
+ require 'json'
5
+ require 'base64'
6
+ require 'socket'
7
+
8
+ module Leeloo
9
+
10
+ class Output
11
+
12
+ def render_preferences preferences
13
+ end
14
+
15
+ def render_secrets secrets
16
+ end
17
+
18
+ def render_secret secret
19
+ end
20
+
21
+ def render_text text
22
+ end
23
+
24
+ def render_footprint footprint
25
+ end
26
+
27
+ def render_share footprint
28
+ end
29
+ end
30
+
31
+ class Ascii < Output
32
+
33
+ def render_preferences preferences
34
+ preferences.keystores.each { |keystore| puts keystore.name }
35
+ end
36
+
37
+ def render_secrets secrets
38
+ secrets.sort_by(&:name).each {|secret| puts secret.name}
39
+ end
40
+
41
+ def render_secret secret
42
+ begin
43
+ puts secret.read
44
+ rescue => exception
45
+ puts "#{secret.name} doesn't exist"
46
+ end
47
+ end
48
+
49
+ def render_text text
50
+ puts text
51
+ end
52
+
53
+ def render_footprint footprint
54
+ puts "token:"
55
+ puts Base64.strict_encode64 footprint.to_json
56
+ end
57
+ end
58
+
59
+ class Terminal < Ascii
60
+
61
+ def render_preferences preferences
62
+ rows = []
63
+ default_keystore = preferences.default
64
+ preferences.keystores.each do |keystore|
65
+ is_default = '*' if keystore.name == default_keystore
66
+ rows << [keystore.name, keystore.path, is_default ]
67
+ end
68
+ puts TTY::Table.new(header: ['Name', 'Path', 'Default'], rows: rows).render(:ascii)
69
+ end
70
+
71
+ def render_secrets secrets
72
+ hash = {:secrets => []}
73
+ secrets.sort_by(&:name).each { |secret| sort(hash[:secrets], secret.name) }
74
+ puts TTY::Tree.new(hash).render
75
+ end
76
+
77
+ def sort array, element
78
+ if element
79
+ e = element.split("/", 2)
80
+ if e.length > 1
81
+ found = false
82
+ array.each do |a|
83
+ if a.is_a? Hash
84
+ if a[e.first]
85
+ found = true
86
+ sort(a[e.first], e.last)
87
+ break
88
+ end
89
+ end
90
+ end
91
+
92
+ unless found
93
+ array << { e.first => sort([], e.last) }
94
+ end
95
+ else
96
+ array << e.last
97
+ end
98
+ end
99
+ array
100
+ end
101
+ end
102
+
103
+ class ClipboardOutputDecorator < Output
104
+
105
+ def initialize output
106
+ @output = output
107
+ end
108
+
109
+ def render_preferences preferences
110
+ @output.render_preferences preferences
111
+ end
112
+
113
+ def render_secrets secrets
114
+ @output.render_secrets secrets
115
+ end
116
+
117
+ def render_text text
118
+ @output.render_text text
119
+ end
120
+
121
+ def render_secret secret
122
+
123
+ Signal.trap("INT") do
124
+ Clipboard.clear
125
+ abort "cleared"
126
+ end
127
+
128
+ Clipboard.copy secret.read
129
+ wait = Thread.new do
130
+ puts "cleaning in 30s"
131
+ 30.times {
132
+ print "."
133
+ sleep 1
134
+ }
135
+ end
136
+ wait.join
137
+ Clipboard.clear
138
+ end
139
+
140
+ def render_footprint footprint
141
+ @output.render_footprint footprint
142
+ end
143
+
144
+ def render_share footprint
145
+ @output.render_share footprint
146
+ end
147
+ end
148
+
149
+ end
@@ -0,0 +1,100 @@
1
+ require 'yaml'
2
+
3
+ module Leeloo
4
+ class Preferences
5
+
6
+ attr_reader :default
7
+
8
+ def initialize
9
+ @keystores = []
10
+ @default = nil
11
+ end
12
+
13
+ def load
14
+ # this method loads all preferences
15
+ self
16
+ end
17
+
18
+ def set_default_keystore name
19
+ @default = name
20
+ end
21
+
22
+ def keystore name=nil
23
+ keystores.find { |k| k.name == (name||@default) }
24
+ end
25
+
26
+ def keystores
27
+ @keystores.map { |k| KeystoreFactory::create k }
28
+ end
29
+
30
+ def add_keystore keystore
31
+ unless @keystores.include? keystore
32
+ @keystores << keystore
33
+ end
34
+ end
35
+
36
+ def remove_keystore name
37
+ abort "you can not remove default keystore" if name == @default
38
+ keystore = @keystores.find { |k| k["name"] == name }
39
+ if keystore != nil
40
+ @keystores.delete keystore
41
+ end
42
+ end
43
+ end
44
+
45
+ class PrivateLocalFileSystemPreferences < Preferences
46
+
47
+ DEFAULT_PATH = "#{Dir.home}/.leeloo"
48
+
49
+ def load(path=DEFAULT_PATH)
50
+ @path = path
51
+
52
+ if File.exist? "#{path}/keystores"
53
+ @keystores = YAML.load_file "#{path}/keystores"
54
+ end
55
+
56
+ if File.exist? "#{path}/config"
57
+ config = YAML.load_file "#{path}/config"
58
+ set_default_keystore config["keystore"]
59
+ else
60
+ default_keystore = {
61
+ 'name' => "private",
62
+ 'path' => "#{path}/private",
63
+ 'cypher' => "gpg",
64
+ 'vc' => "git"
65
+ }
66
+ add_keystore default_keystore
67
+ set_default_keystore "private"
68
+ keystore_of("private").init
69
+ end
70
+
71
+ self
72
+ end
73
+
74
+ def keystore_of name
75
+ keystore = @keystores.find { |keystore| keystore["name"] == name }
76
+ KeystoreFactory::create keystore
77
+ end
78
+
79
+ def set_default_keystore name
80
+ super name
81
+ config = {
82
+ "keystore" => name
83
+ }
84
+ File.write("#{@path}/config", config.to_yaml)
85
+ end
86
+
87
+ def add_keystore keystore
88
+ super keystore
89
+ FileUtils.mkdir_p keystore["path"]
90
+ File.write("#{@path}/keystores", @keystores.to_yaml)
91
+ end
92
+
93
+ def remove_keystore name
94
+ super name
95
+ File.write("#{@path}/keystores", @keystores.to_yaml)
96
+ end
97
+
98
+ end
99
+
100
+ end