ruby-secret_service 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +36 -0
- data/README.md +46 -0
- data/Rakefile +7 -0
- data/VERSION +1 -0
- data/bin/secretservice.rb +64 -0
- data/lib/secret_service/collection.rb +89 -0
- data/lib/secret_service/item.rb +87 -0
- data/lib/secret_service/prompt.rb +30 -0
- data/lib/secret_service/secret.rb +86 -0
- data/lib/secret_service.rb +96 -0
- data/ruby-secret_service.gemspec +19 -0
- data/spec/rss_spec.rb +83 -0
- data/spec/spec_helper.rb +40 -0
- metadata +124 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ruby-secret_service (0.0.5)
|
5
|
+
ruby-dbus
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.2.4)
|
11
|
+
excon (0.16.10)
|
12
|
+
highline (1.6.19)
|
13
|
+
rake (10.1.0)
|
14
|
+
rspec (2.14.1)
|
15
|
+
rspec-core (~> 2.14.0)
|
16
|
+
rspec-expectations (~> 2.14.0)
|
17
|
+
rspec-mocks (~> 2.14.0)
|
18
|
+
rspec-core (2.14.5)
|
19
|
+
rspec-expectations (2.14.2)
|
20
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
21
|
+
rspec-mocks (2.14.3)
|
22
|
+
ruby-dbus (0.9.2)
|
23
|
+
woof_util (0.0.16)
|
24
|
+
excon (~> 0.16.10)
|
25
|
+
highline (~> 1.6.15)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
bundler
|
32
|
+
rake
|
33
|
+
rspec
|
34
|
+
ruby-dbus
|
35
|
+
ruby-secret_service!
|
36
|
+
woof_util
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
Ruby Secret Service
|
2
|
+
===================
|
3
|
+
|
4
|
+
Native Ruby bindings for [freedesktop.org's Secret Service](http://standards.freedesktop.org/secret-service/). Uses [ruby-dbus](https://github.com/mvidner/ruby-dbus).
|
5
|
+
|
6
|
+
##Usage
|
7
|
+
```
|
8
|
+
require "secret_service"
|
9
|
+
|
10
|
+
ss = SecretService.new
|
11
|
+
|
12
|
+
# create a secret in the default collection
|
13
|
+
name = "my github password"
|
14
|
+
secret = "s00p3r-s33|<r1+"
|
15
|
+
|
16
|
+
ss.collection.create_item(name, secret)
|
17
|
+
|
18
|
+
# and read it back
|
19
|
+
round_trip_secret = ss.collection.get_secret(name)
|
20
|
+
round_trip_secret == secret || raise "secret reading failed"
|
21
|
+
|
22
|
+
# create new collections!
|
23
|
+
other_coll_name = "other keyring"
|
24
|
+
other_name = "my heroku password"
|
25
|
+
other_secret = "1m s00 l33t"
|
26
|
+
ss.create_collection(other_coll_name)
|
27
|
+
|
28
|
+
ss.collection(other_coll_name).create_item(other_name, other_secret)
|
29
|
+
round_trip_other = ss.collection(other_coll_name).get_secret(other_name)
|
30
|
+
round_trip_other == other_secret || raise "can't read alt collection secret"
|
31
|
+
```
|
32
|
+
|
33
|
+
## Caveats
|
34
|
+
|
35
|
+
* Both KWallet and GNOME Keyring _REQUIRE_ the use of X11 to prompt
|
36
|
+
the user to decrypt ("unlock") the keychain. While you can use
|
37
|
+
this in a terminal-only environment (e.g., ssh'd in from a
|
38
|
+
desktop/laptop without X11), you're going to get errors about
|
39
|
+
having to unlock collections, but there is literally no way in the
|
40
|
+
spec to pass the decryption password over DBus.
|
41
|
+
* This is written chiefly to provide a programmatic access to
|
42
|
+
Secret Service for one specific program - Heroku Toolbelt. While
|
43
|
+
there's some basic support for multiple collections and
|
44
|
+
locking/unlocking, if you're doing anything moderately complex,
|
45
|
+
you're potentially better off [doing this in Python](https://bitbucket.org/kang/python-keyring-lib/overview)
|
46
|
+
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.5
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'secret_service'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
opts = OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: #{$0} -s|-g NAME"
|
9
|
+
opts.on("-g", "--get NAME",
|
10
|
+
"Reads existing secret called NAME, prints secret to stdout") do |key|
|
11
|
+
options[:name] = key
|
12
|
+
options[:mode] = :get
|
13
|
+
end
|
14
|
+
opts.on("-s", "--set NAME",
|
15
|
+
"Creates new secret called NAME, reads secret from stdin") do |key|
|
16
|
+
options[:name] = key
|
17
|
+
options[:mode] = :set
|
18
|
+
end
|
19
|
+
end
|
20
|
+
opts.parse!
|
21
|
+
|
22
|
+
ss = SecretService.new
|
23
|
+
|
24
|
+
case options[:mode]
|
25
|
+
when :set
|
26
|
+
new_secret = STDIN.read
|
27
|
+
typeinfo = nil
|
28
|
+
attrs = ["a{ss}", {"name" => options[:name].to_s }]
|
29
|
+
|
30
|
+
properties =
|
31
|
+
{"org.freedesktop.Secret.Item.Label" => options[:name],
|
32
|
+
"org.freedesktop.Secret.Item.Attributes" => attrs
|
33
|
+
}
|
34
|
+
item = ss.collection.create_item properties, new_secret
|
35
|
+
when :get
|
36
|
+
item = ss.collection.unlocked_items({"name" => options[:name]})[0]
|
37
|
+
puts item.get_secret
|
38
|
+
else
|
39
|
+
puts opts.help
|
40
|
+
exit 1
|
41
|
+
end
|
42
|
+
|
43
|
+
exit 0
|
44
|
+
name = ARGV[0]
|
45
|
+
|
46
|
+
item = ss.collection.unlocked_items({"name" => name})[0]
|
47
|
+
puts "label: #{item.label}"
|
48
|
+
puts "last modified #{item.modified}, locked: #{item.locked?}"
|
49
|
+
puts "attributes: #{item.attributes.to_s}"
|
50
|
+
|
51
|
+
# item.attributes = ["name", "test2"]
|
52
|
+
# {"magic" => "secret_service.rb", "name" => "test"}
|
53
|
+
|
54
|
+
|
55
|
+
#item = SecretService::Item.new(ss.bus, secret_path)
|
56
|
+
|
57
|
+
#puts item.your_mom
|
58
|
+
|
59
|
+
#puts ss.init_session[1]
|
60
|
+
|
61
|
+
#item = SecretService::Item.new(session, secret_path)
|
62
|
+
#
|
63
|
+
#puts item.item["Modified"].class
|
64
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class SecretService
|
2
|
+
class Collection
|
3
|
+
attr_accessor :service, :name, :path
|
4
|
+
|
5
|
+
def initialize(service, name = DEFAULT_COLLECTION, path=nil)
|
6
|
+
@service = service
|
7
|
+
@name = name
|
8
|
+
@path = path || "#{COLLECTION_PREFIX}#{name}"
|
9
|
+
@proxy = @service.get_proxy(@path, IFACE[:collection])
|
10
|
+
end
|
11
|
+
|
12
|
+
def lock!
|
13
|
+
locked_objs, prompt_path = @service.proxy.Lock [@path]
|
14
|
+
return true if prompt_path == "/" and locked_objs.include?(@path)
|
15
|
+
|
16
|
+
# need to do the prompt dance
|
17
|
+
@service.prompt!(prompt_path)
|
18
|
+
locked?
|
19
|
+
end
|
20
|
+
|
21
|
+
def unlock!
|
22
|
+
unlocked_objs, prompt_path = @service.proxy.Unlock [@path]
|
23
|
+
return true if prompt_path == "/" and unlocked_objs.include?(@path)
|
24
|
+
|
25
|
+
#nuts, stupid prompt
|
26
|
+
@service.prompt!(prompt_path)
|
27
|
+
! locked?
|
28
|
+
end
|
29
|
+
|
30
|
+
def locked?
|
31
|
+
get_property(:locked)
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_property name, new_val
|
35
|
+
@proxy.Set(IFACE[:item], name.to_s.downcase.capitalize, new_val)
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_property name
|
39
|
+
@proxy.Get(IFACE[:collection], name.to_s.downcase.capitalize)[0]
|
40
|
+
end
|
41
|
+
|
42
|
+
def session
|
43
|
+
@service.session
|
44
|
+
end
|
45
|
+
|
46
|
+
def unlocked_items(search_pred = {})
|
47
|
+
@proxy.SearchItems(search_pred)[0].map {|path| Item.new self, path }
|
48
|
+
end
|
49
|
+
|
50
|
+
def locked_items(search_pref = {})
|
51
|
+
@proxy.SearchItems(search_pred)[1].map {|path| Item.new self, path }
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_item name, secret, properties=nil, replace=true
|
55
|
+
if properties.nil?
|
56
|
+
# ruby-dbus's type inference system doesn't handle recursion for
|
57
|
+
# vaguely complicated structs, yet the protocol requires
|
58
|
+
# explicit type annotation. Consequently, nontrivial structs
|
59
|
+
# require the user to provide their own annotation
|
60
|
+
attrs = ["a{ss}", {"name" => name.to_s }]
|
61
|
+
|
62
|
+
properties =
|
63
|
+
{"#{SS_PREFIX}Item.Label" => name.to_s,
|
64
|
+
"#{SS_PREFIX}Item.Attributes" => attrs
|
65
|
+
}
|
66
|
+
end
|
67
|
+
result = @proxy.CreateItem(properties, secret_encode(secret), replace)
|
68
|
+
new_item_path = result[0]
|
69
|
+
Item.new(self, new_item_path)
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_secret name
|
73
|
+
(unlocked_items({"name" => name})[0]).get_secret
|
74
|
+
end
|
75
|
+
|
76
|
+
def secret_encode secret_string
|
77
|
+
mime_type = "application/octet-stream"
|
78
|
+
|
79
|
+
if(secret_string.respond_to? "encoding" and
|
80
|
+
secret_string.encoding.to_s != "ASCII-8BIT")
|
81
|
+
secret_string.encode! "UTF-8"
|
82
|
+
#mime_type = "text/plain; charset=utf8"
|
83
|
+
end
|
84
|
+
|
85
|
+
[session[1], [], secret_string.bytes.to_a, mime_type]
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class SecretService
|
2
|
+
class Item
|
3
|
+
|
4
|
+
attr_accessor :path
|
5
|
+
|
6
|
+
def initialize(collection, path)
|
7
|
+
@collection = collection
|
8
|
+
@path = path
|
9
|
+
@proxy = collection.service.get_proxy @path, IFACE[:item]
|
10
|
+
end
|
11
|
+
|
12
|
+
def modified
|
13
|
+
Time.at get_property(:modified)
|
14
|
+
end
|
15
|
+
|
16
|
+
def created
|
17
|
+
Time.at get_property(:created)
|
18
|
+
end
|
19
|
+
|
20
|
+
def locked?
|
21
|
+
get_property(:locked)
|
22
|
+
end
|
23
|
+
|
24
|
+
def lock!
|
25
|
+
locked, prompt_path = @collection.service.proxy.Lock [@path]
|
26
|
+
return true if prompt_path == "/" and locked.include? @path
|
27
|
+
|
28
|
+
# otherwise we have to handle the prompt
|
29
|
+
@collection.service.prompt!(prompt_path)
|
30
|
+
get_property(:locked)
|
31
|
+
end
|
32
|
+
|
33
|
+
def label
|
34
|
+
get_property(:label)
|
35
|
+
end
|
36
|
+
|
37
|
+
def label= new_label
|
38
|
+
set_property(:label, new_label)
|
39
|
+
end
|
40
|
+
|
41
|
+
def attributes
|
42
|
+
get_property(:attributes)
|
43
|
+
end
|
44
|
+
|
45
|
+
# TODO: this keeps throwing type errors
|
46
|
+
def attributes= new_attrs
|
47
|
+
set_property(:attributes, new_attrs)
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_property name, new_val
|
51
|
+
@proxy.Set(IFACE[:item], name.to_s.downcase.capitalize, new_val)
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_property name
|
55
|
+
@proxy.Get(IFACE[:item], name.to_s.downcase.capitalize)[0]
|
56
|
+
end
|
57
|
+
|
58
|
+
def session
|
59
|
+
@collection.session
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_secret
|
63
|
+
secret_decode(@proxy.GetSecret(session[1]))
|
64
|
+
end
|
65
|
+
|
66
|
+
# http://standards.freedesktop.org/secret-service/ch14.html#type-Secret
|
67
|
+
def secret_decode secret_arr
|
68
|
+
secret_struct = secret_arr[0]
|
69
|
+
s = {}
|
70
|
+
[:session, :params, :bytes, :mime].each do |x|
|
71
|
+
s[x] = secret_struct.shift
|
72
|
+
end
|
73
|
+
|
74
|
+
secret_string = (s[:bytes].map {|x| x.chr}).join
|
75
|
+
if(secret_string.respond_to? "encoding" and
|
76
|
+
s[:mime] != "application/octet-stream")
|
77
|
+
charset = s[:mime].match(/charset=(.+)/)[1]
|
78
|
+
unless charset.nil?
|
79
|
+
charset.upcase!.sub! /\AUTF([\w\d])/, "UTF-#{$1}"
|
80
|
+
secret_string.force_encoding charset
|
81
|
+
end
|
82
|
+
end
|
83
|
+
secret_string
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class SecretService
|
2
|
+
class Prompt
|
3
|
+
|
4
|
+
attr_accessor :path
|
5
|
+
|
6
|
+
class NoPromptRequired < Exception ; end
|
7
|
+
|
8
|
+
def initialize service, object_path
|
9
|
+
raise NoPromptRequired if object_path == "/"
|
10
|
+
@service = service
|
11
|
+
@path = object_path
|
12
|
+
@proxy = service.get_proxy object_path, SecretService::IFACE[:prompt]
|
13
|
+
end
|
14
|
+
|
15
|
+
def prompt!
|
16
|
+
loop = DBus::Main.new
|
17
|
+
loop << @service.bus
|
18
|
+
@proxy.on_signal("Completed") do |dismissed, result|
|
19
|
+
loop.quit
|
20
|
+
end
|
21
|
+
@proxy.Prompt ""
|
22
|
+
loop.run
|
23
|
+
end
|
24
|
+
|
25
|
+
def dismiss
|
26
|
+
@proxy.Dismiss
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# http://standards.freedesktop.org/secret-service/ch14.html#id481899
|
2
|
+
# Creating a "secret"
|
3
|
+
|
4
|
+
class SecretService
|
5
|
+
class Secret
|
6
|
+
|
7
|
+
attr_accessor :session_path, :parameters, :content_type
|
8
|
+
|
9
|
+
def initialize attrs
|
10
|
+
@session_path = attrs[:session_path]
|
11
|
+
@parameters = attrs[:parameters] || []
|
12
|
+
@value = attrs[:value]
|
13
|
+
@content_type = attrs[:content_type] || sniff_content_type(@value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def value
|
17
|
+
@value
|
18
|
+
end
|
19
|
+
|
20
|
+
def value= val
|
21
|
+
@value = val
|
22
|
+
@content_type = sniff_content_type attrs[:value]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Create a new SecretService::Secret from the DBus Struct format
|
26
|
+
def self.from_struct s
|
27
|
+
initialize(:session_path => s[0][0],
|
28
|
+
:parameters => s[0][1],
|
29
|
+
:value => (s[0][2].map {|x| x.chr}).join,
|
30
|
+
:content_type => (s[0][3] == "") ? nil : s[0][3]
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convert the human-readable version into the format expected
|
35
|
+
# by DBus.
|
36
|
+
def to_struct
|
37
|
+
[ "(oayays)",
|
38
|
+
[@session_path, @parameters, @value.bytes.to_a, @content_type]
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# Take a string, guess what the Content-type should be.
|
44
|
+
# For Ruby < 1.9, assume binary encoding. You can avoid this
|
45
|
+
# by passing in an explicit content_type to the initializer.
|
46
|
+
#
|
47
|
+
# Or, y'know, using a more recent version of the language.
|
48
|
+
def sniff_content_type str
|
49
|
+
if (str.nil? or
|
50
|
+
(not str.respond_to? :encoding ) or
|
51
|
+
(str.encoding.to_s == "ASCII-8BIT"))
|
52
|
+
"application/octet-stream"
|
53
|
+
else
|
54
|
+
"text/plain; charset=#{str.encoding}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Derive the Ruby String encoding from a given Content-type.
|
59
|
+
# When all else fails, assumes ASCII-8BIT (aka Binary)
|
60
|
+
def self.content_type_to_encoding content_type=nil
|
61
|
+
binary = "ASCII-8BIT"
|
62
|
+
|
63
|
+
begin
|
64
|
+
case charset = content_type.match(/; charset=(.+)\z/)[1].upcase
|
65
|
+
when /\AUTF[^-]/
|
66
|
+
# "UTF8" is a common misspelling of "UTF-8".
|
67
|
+
charset.sub! /\AUTF/, "UTF-"
|
68
|
+
when /./
|
69
|
+
# Accept charset.upcase at face value & hope for the best
|
70
|
+
charset
|
71
|
+
else
|
72
|
+
# This is a technical violation of RFC 2616, sec. 3.7.1, which
|
73
|
+
# states ISO-8859-1 is the default for "text/*" when no
|
74
|
+
# charset is specified. However, I claim UTF-8 is a safer
|
75
|
+
# assumption on the modern Internet.
|
76
|
+
charset = content_type.match(/\Atext\//) ? "UTF-8" : binary
|
77
|
+
end
|
78
|
+
"".encode charset
|
79
|
+
charset
|
80
|
+
rescue
|
81
|
+
binary
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'dbus'
|
2
|
+
|
3
|
+
require 'secret_service/collection'
|
4
|
+
require 'secret_service/item'
|
5
|
+
require 'secret_service/secret'
|
6
|
+
require 'secret_service/prompt'
|
7
|
+
|
8
|
+
class SecretService
|
9
|
+
SECRETS = 'org.freedesktop.secrets'
|
10
|
+
SS_PREFIX = 'org.freedesktop.Secret.'
|
11
|
+
SS_PATH = '/org/freedesktop/secrets'
|
12
|
+
COLLECTION_PREFIX = '/org/freedesktop/secrets/aliases/'
|
13
|
+
DEFAULT_COLLECTION = "default"
|
14
|
+
|
15
|
+
ALGO = "plain"
|
16
|
+
|
17
|
+
DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
|
18
|
+
DBUS_SERVICE_UNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
|
19
|
+
DBUS_EXEC_FAILED = 'org.freedesktop.DBus.Error.Spawn.ExecFailed'
|
20
|
+
DBUS_NO_REPLY = 'org.freedesktop.DBus.Error.NoReply'
|
21
|
+
DBUS_NO_SUCH_OBJECT = 'org.freedesktop.Secret.Error.NoSuchObject'
|
22
|
+
|
23
|
+
IFACE = {}
|
24
|
+
[:service, :item, :collection, :prompt].each do |x|
|
25
|
+
IFACE[x] = "#{SS_PREFIX}#{x.to_s.capitalize}"
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :bus, :proxy
|
29
|
+
|
30
|
+
class NoSessionBus < Exception
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
begin
|
35
|
+
@bus = DBus::SessionBus.instance
|
36
|
+
|
37
|
+
@proxy_maker = @bus.service SECRETS
|
38
|
+
@proxy = get_proxy SS_PATH, IFACE[:service]
|
39
|
+
@collections = {}
|
40
|
+
|
41
|
+
@collection_paths = self.list_collections
|
42
|
+
rescue
|
43
|
+
raise NoSessionBus
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_proxy path, iface=nil
|
48
|
+
obj = @proxy_maker.object path
|
49
|
+
obj.introspect
|
50
|
+
obj.default_iface = iface unless iface.nil?
|
51
|
+
obj
|
52
|
+
end
|
53
|
+
|
54
|
+
def session
|
55
|
+
@session ||= @proxy.OpenSession(ALGO, "")
|
56
|
+
end
|
57
|
+
|
58
|
+
def collection(name=DEFAULT_COLLECTION, path=nil)
|
59
|
+
@collections[name.to_s] ||= Collection.new(self, name, path)
|
60
|
+
end
|
61
|
+
|
62
|
+
def list_collections
|
63
|
+
@proxy.Get(IFACE[:service], 'Collections')
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_collection name, properties=nil
|
67
|
+
properties ||= {"#{SS_PREFIX}Collection.Label" => name}
|
68
|
+
|
69
|
+
coll, prompt_path = @proxy.CreateCollection properties, ""
|
70
|
+
prompt!(prompt_path) if prompt_path != "/"
|
71
|
+
collection(name, coll)
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def set_alias name, collection
|
76
|
+
@proxy.SetAlias name, (collection.class == String ? collection : collection.path)
|
77
|
+
end
|
78
|
+
|
79
|
+
def read_alias name
|
80
|
+
path = @proxy.ReadAlias name
|
81
|
+
collection(name, path)
|
82
|
+
end
|
83
|
+
|
84
|
+
def search_items attrs={}
|
85
|
+
@proxy.SearchItems(attrs)
|
86
|
+
end
|
87
|
+
|
88
|
+
def unlock objects
|
89
|
+
@proxy.Unlock objects
|
90
|
+
end
|
91
|
+
|
92
|
+
def prompt!(prompt_path)
|
93
|
+
SecretService::Prompt.new(self, prompt_path).prompt!
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'ruby-secret_service'
|
5
|
+
s.version = File.read("VERSION").chomp
|
6
|
+
s.summary = 'Ruby bindings for GNOME Keyring & KWallet'
|
7
|
+
s.description = "Native bindings use the D-BUS Secret Service API, docs at http://standards.freedesktop.org/secret-service/"
|
8
|
+
s.authors = ["Tom Maher"]
|
9
|
+
s.email = "tmaher@pw0n.me"
|
10
|
+
s.license = "Apache 2.0"
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.require_path = ["lib"]
|
13
|
+
s.homepage = "https://github.com/tmaher/ruby-secret_service"
|
14
|
+
s.add_development_dependency "woof_util"
|
15
|
+
s.add_development_dependency "rake"
|
16
|
+
s.add_development_dependency "rspec"
|
17
|
+
|
18
|
+
s.add_dependency "ruby-dbus"
|
19
|
+
end
|
data/spec/rss_spec.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SecretService do
|
4
|
+
|
5
|
+
#it "aborts when it can't find dbus" do
|
6
|
+
#state = hide_dbus_configs
|
7
|
+
#SecretService.new
|
8
|
+
#expect {SecretService.new}.to raise_error
|
9
|
+
#restore_dbus_configs state
|
10
|
+
#end
|
11
|
+
|
12
|
+
it "can create a session" do
|
13
|
+
SecretService.new.class.should_not be nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "has a default collection" do
|
17
|
+
SecretService.new.collection.should_not be nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "can lock and unlock collections" do
|
21
|
+
ss = SecretService.new
|
22
|
+
ss.collection.lock!.should eq true
|
23
|
+
ss.collection.locked?.should eq true
|
24
|
+
ss.collection.unlock!.should eq true
|
25
|
+
ss.collection.locked?.should eq false
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can create and read back objects" do
|
29
|
+
name = SecureRandom.hex(8).to_s
|
30
|
+
secret = SecureRandom.hex(8).to_s
|
31
|
+
ss = SecretService.new
|
32
|
+
ss.collection.unlock!
|
33
|
+
ss.collection.create_item("rspec_test_#{name}", secret).should_not be nil
|
34
|
+
ss.collection.get_secret("rspec_test_#{name}").should eq secret
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can create a new collection" do
|
38
|
+
ss = SecretService.new
|
39
|
+
coll_label = "rspec_test_collection_#{SecureRandom.hex(4)}"
|
40
|
+
new_coll = ss.create_collection coll_label
|
41
|
+
new_coll.should_not be nil
|
42
|
+
new_coll.name.should eq coll_label
|
43
|
+
end
|
44
|
+
|
45
|
+
it "can create and lock objects" do
|
46
|
+
name = SecureRandom.hex(8).to_s
|
47
|
+
secret = SecureRandom.hex(8).to_s
|
48
|
+
ss = SecretService.new
|
49
|
+
item = ss.collection.create_item("rspec_test_#{name}", secret)
|
50
|
+
item.lock!
|
51
|
+
item.locked?.should eq true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "can find objects" do
|
55
|
+
ss = SecretService.new
|
56
|
+
unlocked, locked = ss.search_items
|
57
|
+
unlocked.kind_of?(Array).should eq true
|
58
|
+
locked.kind_of?(Array).should eq true
|
59
|
+
(locked + unlocked).each do |item|
|
60
|
+
item.match(/^\/org\/freedesktop\/secrets\/collection/).nil?.should eq false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "can unlock objects" do
|
65
|
+
ss = SecretService.new
|
66
|
+
item_paths, prompt = ss.unlock(ss.search_items[1])
|
67
|
+
end
|
68
|
+
|
69
|
+
it "aborts on invalid prompt creation" do
|
70
|
+
ss = SecretService.new
|
71
|
+
expect {ss.prompt! "/"}.to raise_error SecretService::Prompt::NoPromptRequired
|
72
|
+
end
|
73
|
+
|
74
|
+
#it "can raise a prompt" do
|
75
|
+
#ss = SecretService.new
|
76
|
+
#item_paths, prompt = ss.unlock(ss.search_items[1])
|
77
|
+
#ss.prompt!(prompt)
|
78
|
+
#new_items, new_prompt = ss.unlock(ss.search_items[1])
|
79
|
+
#new_prompt.should be_nil
|
80
|
+
#puts "new_items: #{new_items}"
|
81
|
+
#end
|
82
|
+
|
83
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
6
|
+
|
7
|
+
require 'secret_service'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.color_enabled = true
|
11
|
+
config.order = 'random'
|
12
|
+
config.seed = SecureRandom.random_number(2 ** 24)
|
13
|
+
|
14
|
+
# Use color not only in STDOUT but also in pagers and files
|
15
|
+
config.tty = true
|
16
|
+
|
17
|
+
# Use the specified formatter
|
18
|
+
#config.formatter = :documentation # :progress, :html, :textmate
|
19
|
+
end
|
20
|
+
|
21
|
+
def hide_dbus_configs
|
22
|
+
state = {:addr => ENV["DBUS_SESSION_BUS_ADDRESS"],
|
23
|
+
:display => ENV["DISPLAY"],
|
24
|
+
:dir => File.join(ENV["HOME"], ".dbus-#{SecureRandom.hex(4)}")
|
25
|
+
}
|
26
|
+
ENV.delete "DBUS_SESSION_BUS_ADDRESS"
|
27
|
+
ENV.delete "DISPLAY"
|
28
|
+
begin
|
29
|
+
File.rename File.join(ENV["HOME"], ".dbus"), state[:dir]
|
30
|
+
rescue; end
|
31
|
+
state
|
32
|
+
end
|
33
|
+
|
34
|
+
def restore_dbus_configs state
|
35
|
+
ENV["DBUS_SESSION_BUS_ADDRESS"] = state[:addr]
|
36
|
+
ENV["DISPLAY"] = state[:display]
|
37
|
+
begin
|
38
|
+
File.rename state[:dir], File.join(ENV["HOME"], ".dbus")
|
39
|
+
rescue; end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-secret_service
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tom Maher
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: woof_util
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: ruby-dbus
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Native bindings use the D-BUS Secret Service API, docs at http://standards.freedesktop.org/secret-service/
|
79
|
+
email: tmaher@pw0n.me
|
80
|
+
executables: []
|
81
|
+
extensions: []
|
82
|
+
extra_rdoc_files: []
|
83
|
+
files:
|
84
|
+
- .gitignore
|
85
|
+
- Gemfile
|
86
|
+
- Gemfile.lock
|
87
|
+
- README.md
|
88
|
+
- Rakefile
|
89
|
+
- VERSION
|
90
|
+
- bin/secretservice.rb
|
91
|
+
- lib/secret_service.rb
|
92
|
+
- lib/secret_service/collection.rb
|
93
|
+
- lib/secret_service/item.rb
|
94
|
+
- lib/secret_service/prompt.rb
|
95
|
+
- lib/secret_service/secret.rb
|
96
|
+
- ruby-secret_service.gemspec
|
97
|
+
- spec/rss_spec.rb
|
98
|
+
- spec/spec_helper.rb
|
99
|
+
homepage: https://github.com/tmaher/ruby-secret_service
|
100
|
+
licenses:
|
101
|
+
- Apache 2.0
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- - lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 1.8.23
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: Ruby bindings for GNOME Keyring & KWallet
|
124
|
+
test_files: []
|