ruby-secret_service 0.0.5
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.
- 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: []
|