duse 0.0.2
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.
- checksums.yaml +15 -0
- data/.gitignore +35 -0
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +13 -0
- data/LICENSE +22 -0
- data/README.md +89 -0
- data/Rakefile +11 -0
- data/bin/duse +8 -0
- data/duse.gemspec +22 -0
- data/lib/duse.rb +10 -0
- data/lib/duse/cli.rb +104 -0
- data/lib/duse/cli/account.rb +20 -0
- data/lib/duse/cli/account_confirm.rb +22 -0
- data/lib/duse/cli/account_info.rb +18 -0
- data/lib/duse/cli/account_password.rb +14 -0
- data/lib/duse/cli/account_password_change.rb +30 -0
- data/lib/duse/cli/account_password_reset.rb +20 -0
- data/lib/duse/cli/account_resend_confirmation.rb +20 -0
- data/lib/duse/cli/account_update.rb +25 -0
- data/lib/duse/cli/api_command.rb +33 -0
- data/lib/duse/cli/cli_config.rb +54 -0
- data/lib/duse/cli/command.rb +202 -0
- data/lib/duse/cli/config.rb +16 -0
- data/lib/duse/cli/help.rb +23 -0
- data/lib/duse/cli/key_helper.rb +84 -0
- data/lib/duse/cli/login.rb +27 -0
- data/lib/duse/cli/meta_command.rb +12 -0
- data/lib/duse/cli/parser.rb +43 -0
- data/lib/duse/cli/password_helper.rb +17 -0
- data/lib/duse/cli/register.rb +45 -0
- data/lib/duse/cli/secret.rb +19 -0
- data/lib/duse/cli/secret_add.rb +38 -0
- data/lib/duse/cli/secret_generator.rb +10 -0
- data/lib/duse/cli/secret_get.rb +40 -0
- data/lib/duse/cli/secret_list.rb +20 -0
- data/lib/duse/cli/secret_remove.rb +19 -0
- data/lib/duse/cli/secret_update.rb +44 -0
- data/lib/duse/cli/share_with_user.rb +53 -0
- data/lib/duse/cli/version.rb +12 -0
- data/lib/duse/client/config.rb +36 -0
- data/lib/duse/client/entity.rb +87 -0
- data/lib/duse/client/namespace.rb +112 -0
- data/lib/duse/client/secret.rb +69 -0
- data/lib/duse/client/session.rb +128 -0
- data/lib/duse/client/user.rb +23 -0
- data/lib/duse/encryption.rb +39 -0
- data/lib/duse/version.rb +3 -0
- data/spec/cli/cli_config_spec.rb +49 -0
- data/spec/cli/commands/account_spec.rb +45 -0
- data/spec/cli/commands/config_spec.rb +17 -0
- data/spec/cli/commands/login_spec.rb +51 -0
- data/spec/cli/commands/register_spec.rb +38 -0
- data/spec/cli/commands/secret_spec.rb +142 -0
- data/spec/client/secret_marshaller_spec.rb +32 -0
- data/spec/client/secret_spec.rb +96 -0
- data/spec/client/user_spec.rb +105 -0
- data/spec/spec_helper.rb +70 -0
- data/spec/support/helpers.rb +43 -0
- data/spec/support/mock_api.rb +142 -0
- metadata +159 -0
data/lib/duse/version.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'duse/cli/cli_config'
|
2
|
+
|
3
|
+
RSpec.describe Duse::CLIConfig do
|
4
|
+
describe '.load' do
|
5
|
+
context 'config file does not exist' do
|
6
|
+
it 'returns an empty hash' do
|
7
|
+
config = Duse::CLIConfig.load
|
8
|
+
expect(config).to eq Hash.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'valid config file' do
|
13
|
+
it 'correctly loads the config file' do
|
14
|
+
config = { 'uri' => 'https://duse.io/' }
|
15
|
+
FileUtils.mkdir_p Duse::CLIConfig.config_dir
|
16
|
+
File.open(Duse::CLIConfig.config_file, 'w') do |f|
|
17
|
+
f.write config.to_yaml
|
18
|
+
f.chmod 0600
|
19
|
+
end
|
20
|
+
|
21
|
+
expect(Duse::CLIConfig.load).to eq config
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'malformed config file' do
|
26
|
+
it 'uses an empty config when config is malformatted' do
|
27
|
+
FileUtils.mkdir_p Duse::CLIConfig.config_dir
|
28
|
+
File.open(Duse::CLIConfig.config_file, 'w') do |f|
|
29
|
+
f.write 'not yaml content'
|
30
|
+
f.chmod 0600
|
31
|
+
end
|
32
|
+
|
33
|
+
expect(Duse::CLIConfig.load).to eq Hash.new
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.save' do
|
39
|
+
it 'builds the config file correctly' do
|
40
|
+
config = Duse::CLIConfig.new
|
41
|
+
config.uri = 'https://duse.io/'
|
42
|
+
Duse::CLIConfig.save(config)
|
43
|
+
|
44
|
+
expect(File.exist? Duse::CLIConfig.config_file).to be true
|
45
|
+
expected_file_content = "---\nuri: https://duse.io/\n"
|
46
|
+
expect(File.read Duse::CLIConfig.config_file).to eq expected_file_content
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
RSpec.describe 'duse account' do
|
2
|
+
before :each do
|
3
|
+
FileUtils.mkdir_p Duse::CLIConfig.config_dir
|
4
|
+
open(Duse::CLIConfig.config_file, 'w') do |f|
|
5
|
+
f.puts '---'
|
6
|
+
f.puts 'uri: https://example.com/'
|
7
|
+
f.puts 'token: token'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'prints info about its subcommands' do
|
12
|
+
expect(run_cli('account').success?).to be true
|
13
|
+
expect(last_run.out).to include('duse account COMMAND')
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'confirm' do
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'resend-confirmation' do
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'info' do
|
23
|
+
context 'if logged in' do
|
24
|
+
it 'prints the current users account information' do
|
25
|
+
stub_user_me_get
|
26
|
+
|
27
|
+
expect(run_cli('account', 'info').success?).to be true
|
28
|
+
expect(last_run.out).to eq [
|
29
|
+
"\nUsername: flower-pot\n",
|
30
|
+
"Email: flower-pot@example.org\n"
|
31
|
+
].join
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'update' do
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'password' do
|
40
|
+
it 'prints info about its subcommands' do
|
41
|
+
expect(run_cli('account', 'password').success?).to be true
|
42
|
+
expect(last_run.out).to include('duse account password COMMAND')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
describe 'duse config' do
|
2
|
+
context 'false uri provided' do
|
3
|
+
it 'errors with a message' do
|
4
|
+
expect(run_cli('config') { |i| i.puts('test') }.err).to eq(
|
5
|
+
"Not an uri\n"
|
6
|
+
)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'correct uri provided' do
|
11
|
+
it 'accepts it' do
|
12
|
+
run = run_cli('config') { |i| i.puts('https://duse.io/') }
|
13
|
+
expect(run.err).to eq('')
|
14
|
+
expect(run.success?).to be true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
describe 'duse login' do
|
2
|
+
before :each do
|
3
|
+
FileUtils.mkdir_p Duse::CLIConfig.config_dir
|
4
|
+
open(Duse::CLIConfig.config_file, 'w') do |f|
|
5
|
+
f.puts '---'
|
6
|
+
f.puts 'uri: https://example.com/'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'correct credentials' do
|
11
|
+
it 'writes the auth token in the config file' do
|
12
|
+
open(File.join(Duse::CLIConfig.config_dir, 'flower-pot'), 'w') do |f|
|
13
|
+
f.puts "-----BEGIN RSA PRIVATE KEY-----\nMIICWgIBAAKBgQCftZvHkB6uKWVDvrIzmy2p496Hv9PD/hhRk+DSXcE/CPtRmvYZ\nzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTeNdlaH9cRFV2wc2A/hbg2kaIS\nxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET2XM6tZcuwFULX6bl8QIDAQAB\nAoGAEJwyt26lwjdL8N/EaNmaxjCM1FF/FMM4hEN8/mQB1Sx59uLG9agPWzrDJcoS\nlH7ZalKLwpORTuCYvCtKH7Qm+fgnjKl/qyn6/cDmtk6VtJvPjQGi3oh2eRIMcwZv\nva+NQLF11bm0kVtZG5viIKlb1snHzkpPjFAOPBqQj2FNdgECQQDQdHWC5XYww2RQ\n/FpRBacJPIxb8PAwb7+EjqJSaruGO9CtLiDiCzlmidGP0Q++zwjAxksSqP4qkr6k\npKvDqkydAkEAxCLuq0c+6gnE9X1PUy4Bl/hUOxrk3ZQRCMUCE4XB8mNmJTLNY43O\ncY7Z1sdaCu7pAiGxQqojUYgwFACGmbOcZQJAZAvg8mfq59B/bxcOyeAqoRY8T0w+\nGyEnDBng8iljwzMmHlgLVDIK5Jm0yI+QPQXkr5D8KwKMqiYv9ZlLDufHSQJAJs9i\nurGWWWkleA4brDHmTtPsluVzdATgegPBrWtCPVw90g6DZbehqgbCRCWeQ5uSr8FK\n+g4AfxmbqdmQyMkpoQI/HvHjjPB9a/2qkpyjeiJIx2gmCmhBke9V/b3XFGBy3ci7\nLZRZUZLlAdJORX177Ief6MWqgXldlcP1N7mzWskE\n-----END RSA PRIVATE KEY-----\n"
|
14
|
+
end
|
15
|
+
stub_user_me_get
|
16
|
+
stub_request(:post, "https://example.com/users/token").
|
17
|
+
with(body: {username: "flower-pot", password: "Passw0rd!"}.to_json,
|
18
|
+
headers: {'Accept'=>'application/vnd.duse.1+json', 'Content-Type'=>'application/json'}).
|
19
|
+
to_return(status: 201, body: { api_token: 'token' }.to_json, headers: {})
|
20
|
+
|
21
|
+
expect(run_cli('login') do |i|
|
22
|
+
i.puts 'flower-pot'
|
23
|
+
i.puts 'Passw0rd!'
|
24
|
+
end.success?).to be true
|
25
|
+
|
26
|
+
expect(File.read Duse::CLIConfig.config_file).to eq(
|
27
|
+
"---\nuri: https://example.com/\ntoken: token\n"
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'incorrect credentials' do
|
33
|
+
it 'errors with a message' do
|
34
|
+
stub_request(:post, "https://example.com/users/token").
|
35
|
+
with(body: {username: "flower-pot", password: "wrong password"}.to_json,
|
36
|
+
headers: {'Accept'=>'application/vnd.duse.1+json', 'Content-Type'=>'application/json'}).
|
37
|
+
to_return(status: 401, body: "", headers: {})
|
38
|
+
|
39
|
+
expect(run_cli('login') do |i|
|
40
|
+
i.puts 'flower-pot'
|
41
|
+
i.puts 'wrong password'
|
42
|
+
end.err).to eq(
|
43
|
+
"Wrong username or password!\n"
|
44
|
+
)
|
45
|
+
|
46
|
+
expect(File.read Duse::CLIConfig.config_file).to eq(
|
47
|
+
"---\nuri: https://example.com/\n"
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
describe 'duse register' do
|
2
|
+
before :each do
|
3
|
+
FileUtils.mkdir_p Duse::CLIConfig.config_dir
|
4
|
+
|
5
|
+
open(Duse::CLIConfig.config_file, 'w') do |f|
|
6
|
+
f.puts '---'
|
7
|
+
f.puts 'uri: https://example.com/'
|
8
|
+
end
|
9
|
+
public_key_file = File.expand_path('~/.ssh/id_rsa.pub')
|
10
|
+
FileUtils.rm_rf File.dirname(public_key_file)
|
11
|
+
FileUtils.mkdir_p File.dirname(public_key_file)
|
12
|
+
open(public_key_file, 'w') do |f|
|
13
|
+
f.puts "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCtaAORZpFJ037AN1Drm88TLYyZy+vLyVZr9XKPfMUF/KCHEsT1gJfQYFRI7t/gHjL3VouKM10671f/g8s5t1hWHF6YOvaFTd3yDXAkf86x5jrPBrIH6M3M5WOwwqwW9aRF22CFzlBoCoV4GQt4KhRzqrG2kRJULsBuT9TiHCKEPw== RSA-1024"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'if all user inputs are valid' do
|
18
|
+
it 'registers successfully' do
|
19
|
+
stub_request(:post, "https://example.com/users").
|
20
|
+
with(headers: {'Accept'=>'application/vnd.duse.1+json', 'Content-Type'=>'application/json'}).
|
21
|
+
to_return(status: 201, body: {
|
22
|
+
'id' => 2,
|
23
|
+
'username' => 'flower-pot',
|
24
|
+
'email' => 'flower-pot@example.org',
|
25
|
+
'public_key' => "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCftZvHkB6uKWVDvrIzmy2p496H\nv9PD/hhRk+DSXcE/CPtRmvYZzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTe\nNdlaH9cRFV2wc2A/hbg2kaISxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET\n2XM6tZcuwFULX6bl8QIDAQAB\n-----END PUBLIC KEY-----\n",
|
26
|
+
'url' => 'https://example.com/users/2'
|
27
|
+
}.to_json , headers: {})
|
28
|
+
|
29
|
+
expect(run_cli('register') do |i|
|
30
|
+
i.puts 'flower-pot'
|
31
|
+
i.puts 'fbranczyk@gmail.com'
|
32
|
+
i.puts 'Passw0rd!'
|
33
|
+
i.puts 'Passw0rd!'
|
34
|
+
i.puts '1'
|
35
|
+
end.success?).to be true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
RSpec.describe 'duse secret' do
|
2
|
+
before :each do
|
3
|
+
FileUtils.mkdir_p Duse::CLIConfig.config_dir
|
4
|
+
open(Duse::CLIConfig.config_file, 'w') do |f|
|
5
|
+
f.puts '---'
|
6
|
+
f.puts 'uri: https://example.com/'
|
7
|
+
f.puts 'token: token'
|
8
|
+
end
|
9
|
+
open(Duse::CLIConfig.new.private_key_file_for(OpenStruct.new(username: 'flower-pot')), 'w') do |f|
|
10
|
+
f.puts "-----BEGIN RSA PRIVATE KEY-----"
|
11
|
+
f.puts "MIICWgIBAAKBgQCftZvHkB6uKWVDvrIzmy2p496Hv9PD/hhRk+DSXcE/CPtRmvYZ"
|
12
|
+
f.puts "zbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTeNdlaH9cRFV2wc2A/hbg2kaIS"
|
13
|
+
f.puts "xrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET2XM6tZcuwFULX6bl8QIDAQAB"
|
14
|
+
f.puts "AoGAEJwyt26lwjdL8N/EaNmaxjCM1FF/FMM4hEN8/mQB1Sx59uLG9agPWzrDJcoS"
|
15
|
+
f.puts "lH7ZalKLwpORTuCYvCtKH7Qm+fgnjKl/qyn6/cDmtk6VtJvPjQGi3oh2eRIMcwZv"
|
16
|
+
f.puts "va+NQLF11bm0kVtZG5viIKlb1snHzkpPjFAOPBqQj2FNdgECQQDQdHWC5XYww2RQ"
|
17
|
+
f.puts "/FpRBacJPIxb8PAwb7+EjqJSaruGO9CtLiDiCzlmidGP0Q++zwjAxksSqP4qkr6k"
|
18
|
+
f.puts "pKvDqkydAkEAxCLuq0c+6gnE9X1PUy4Bl/hUOxrk3ZQRCMUCE4XB8mNmJTLNY43O"
|
19
|
+
f.puts "cY7Z1sdaCu7pAiGxQqojUYgwFACGmbOcZQJAZAvg8mfq59B/bxcOyeAqoRY8T0w+"
|
20
|
+
f.puts "GyEnDBng8iljwzMmHlgLVDIK5Jm0yI+QPQXkr5D8KwKMqiYv9ZlLDufHSQJAJs9i"
|
21
|
+
f.puts "urGWWWkleA4brDHmTtPsluVzdATgegPBrWtCPVw90g6DZbehqgbCRCWeQ5uSr8FK"
|
22
|
+
f.puts "+g4AfxmbqdmQyMkpoQI/HvHjjPB9a/2qkpyjeiJIx2gmCmhBke9V/b3XFGBy3ci7"
|
23
|
+
f.puts "LZRZUZLlAdJORX177Ief6MWqgXldlcP1N7mzWskE"
|
24
|
+
f.puts "-----END RSA PRIVATE KEY-----"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'get' do
|
29
|
+
context 'provide secret id from the call' do
|
30
|
+
it 'takes the secrets id from the cli call and does not ask for it' do
|
31
|
+
stub_secret_get
|
32
|
+
stub_user_me_get
|
33
|
+
stub_server_user_get
|
34
|
+
expect(run_cli('secret', 'get', '1').out).to eq(
|
35
|
+
"\nName: test\nSecret: test\nAccess: server, flower-pot\n"
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'outputs the secret content plaintext when using the plain flag' do
|
40
|
+
stub_secret_get
|
41
|
+
stub_user_me_get
|
42
|
+
stub_server_user_get
|
43
|
+
expect(run_cli('secret', 'get', '1', '--plain').out).to eq("test")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'secret does not exist' do
|
48
|
+
it 'shows an error message when getting not existant secrets' do
|
49
|
+
stub_request(:get, "https://example.com/secrets/2").
|
50
|
+
with(headers: {'Accept'=>'application/vnd.duse.1+json', 'Authorization'=>'token'}).
|
51
|
+
to_return(status: 404, body: { message: 'Not found' }.to_json)
|
52
|
+
|
53
|
+
expect(run_cli('secret', 'get', '2').err).to eq(
|
54
|
+
"Not found\n"
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'secret exists' do
|
60
|
+
it 'asks for the secret id' do
|
61
|
+
stub_secret_get
|
62
|
+
stub_user_me_get
|
63
|
+
stub_server_user_get
|
64
|
+
expect(run_cli('secret', 'get') { |i| i.puts('1') }.out).to eq(
|
65
|
+
"Secret to retrieve: \nName: test\nSecret: test\nAccess: server, flower-pot\n"
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'list' do
|
72
|
+
it 'lists secrets' do
|
73
|
+
stub_get_secrets
|
74
|
+
|
75
|
+
expect(run_cli('secret', 'list').out).to eq(
|
76
|
+
"1: test\n"
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'delete' do
|
82
|
+
it 'deletes the provided secret id' do
|
83
|
+
stub_request(:delete, "https://example.com/secrets/1").
|
84
|
+
with(headers: {'Accept'=>'application/vnd.duse.1+json', 'Authorization'=>'token'}).
|
85
|
+
to_return(status: 204, body: "", headers: {})
|
86
|
+
|
87
|
+
expect(run_cli('secret', 'remove', '1').success?).to be true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'add' do
|
92
|
+
context 'minimal users' do
|
93
|
+
it 'can create a secret' do
|
94
|
+
stub_get_users
|
95
|
+
stub_user_me_get
|
96
|
+
stub_server_user_get
|
97
|
+
stub_create_secret
|
98
|
+
|
99
|
+
expect(run_cli('secret', 'add') do |i|
|
100
|
+
i.puts 'test'
|
101
|
+
i.puts 'test'
|
102
|
+
i.puts 'n'
|
103
|
+
end.success?).to be true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'multiple users' do
|
108
|
+
it 'can create a secret with multiple users' do
|
109
|
+
stub_get_users
|
110
|
+
stub_user_me_get
|
111
|
+
stub_server_user_get
|
112
|
+
stub_get_other_user
|
113
|
+
stub_create_secret
|
114
|
+
|
115
|
+
expect(run_cli('secret', 'add') do |i|
|
116
|
+
i.puts 'test'
|
117
|
+
i.puts 'test'
|
118
|
+
i.puts 'Y'
|
119
|
+
i.puts '1'
|
120
|
+
end.success?).to be true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'invalid user input' do
|
125
|
+
it 'repeats asking for a users selection if the previous selection was invalid' do
|
126
|
+
stub_get_users
|
127
|
+
stub_user_me_get
|
128
|
+
stub_server_user_get
|
129
|
+
stub_get_other_user
|
130
|
+
stub_create_secret
|
131
|
+
|
132
|
+
expect(run_cli('secret', 'add') do |i|
|
133
|
+
i.puts 'test'
|
134
|
+
i.puts 'test'
|
135
|
+
i.puts 'Y'
|
136
|
+
i.puts '3'
|
137
|
+
i.puts '1'
|
138
|
+
end.success?).to be true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
RSpec.describe Duse::Client::SecretMarshaller do
|
2
|
+
before :each do
|
3
|
+
Duse.config = Duse::CLIConfig.new({ 'uri' => 'https://example.com/' })
|
4
|
+
end
|
5
|
+
|
6
|
+
describe '#to_h' do
|
7
|
+
context 'own and server user' do
|
8
|
+
it 'marshalls a secret without errors' do
|
9
|
+
private_key = OpenSSL::PKey::RSA.new "-----BEGIN RSA PRIVATE KEY-----\nMIICWgIBAAKBgQCftZvHkB6uKWVDvrIzmy2p496Hv9PD/hhRk+DSXcE/CPtRmvYZ\nzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTeNdlaH9cRFV2wc2A/hbg2kaIS\nxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET2XM6tZcuwFULX6bl8QIDAQAB\nAoGAEJwyt26lwjdL8N/EaNmaxjCM1FF/FMM4hEN8/mQB1Sx59uLG9agPWzrDJcoS\nlH7ZalKLwpORTuCYvCtKH7Qm+fgnjKl/qyn6/cDmtk6VtJvPjQGi3oh2eRIMcwZv\nva+NQLF11bm0kVtZG5viIKlb1snHzkpPjFAOPBqQj2FNdgECQQDQdHWC5XYww2RQ\n/FpRBacJPIxb8PAwb7+EjqJSaruGO9CtLiDiCzlmidGP0Q++zwjAxksSqP4qkr6k\npKvDqkydAkEAxCLuq0c+6gnE9X1PUy4Bl/hUOxrk3ZQRCMUCE4XB8mNmJTLNY43O\ncY7Z1sdaCu7pAiGxQqojUYgwFACGmbOcZQJAZAvg8mfq59B/bxcOyeAqoRY8T0w+\nGyEnDBng8iljwzMmHlgLVDIK5Jm0yI+QPQXkr5D8KwKMqiYv9ZlLDufHSQJAJs9i\nurGWWWkleA4brDHmTtPsluVzdATgegPBrWtCPVw90g6DZbehqgbCRCWeQ5uSr8FK\n+g4AfxmbqdmQyMkpoQI/HvHjjPB9a/2qkpyjeiJIx2gmCmhBke9V/b3XFGBy3ci7\nLZRZUZLlAdJORX177Ief6MWqgXldlcP1N7mzWskE\n-----END RSA PRIVATE KEY-----\n"
|
10
|
+
current_user = OpenStruct.new public_key: OpenSSL::PKey::RSA.new("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCftZvHkB6uKWVDvrIzmy2p496H\nv9PD/hhRk+DSXcE/CPtRmvYZzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTe\nNdlaH9cRFV2wc2A/hbg2kaISxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET\n2XM6tZcuwFULX6bl8QIDAQAB\n-----END PUBLIC KEY-----\n")
|
11
|
+
server_user = OpenStruct.new public_key: OpenSSL::PKey::RSA.new("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC8Z1K4aCksOb6rsbKNcF4fNcN\n1Tbyv+ids751YvmfU2WHDXB3wIVoN1YRdb8Dk8608YlGAAqVaGVwfgYdyLMppIGs\nglZIMjwZFM2F84T4swKOEJJx6o3ZCRnP9ZQcceqzcIuTjiIqC7xu+QOvtADAMW68\nzZIpFOHjjiuxkA7PQQIDAQAB\n-----END PUBLIC KEY-----\n")
|
12
|
+
secret_text = "-----BEGIN RSA PRIVATE KEY-----\nMIICWgIBAAKBgQCftZvHkB6uKWVDvrIzmy2p496Hv9PD/hhRk+DSXcE/CPtRmvYZ\nzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTeNdlaH9cRFV2wc2A/hbg2kaIS\nxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET2XM6tZcuwFULX6bl8QIDAQAB\nAoGAEJwyt26lwjdL8N/EaNmaxjCM1FF/FMM4hEN8/mQB1Sx59uLG9agPWzrDJcoS\nlH7ZalKLwpORTuCYvCtKH7Qm+fgnjKl/qyn6/cDmtk6VtJvPjQGi3oh2eRIMcwZv\nva+NQLF11bm0kVtZG5viIKlb1snHzkpPjFAOPBqQj2FNdgECQQDQdHWC5XYww2RQ\n/FpRBacJPIxb8PAwb7+EjqJSaruGO9CtLiDiCzlmidGP0Q++zwjAxksSqP4qkr6k\npKvDqkydAkEAxCLuq0c+6gnE9X1PUy4Bl/hUOxrk3ZQRCMUCE4XB8mNmJTLNY43O\ncY7Z1sdaCu7pAiGxQqojUYgwFACGmbOcZQJAZAvg8mfq59B/bxcOyeAqoRY8T0w+\nGyEnDBng8iljwzMmHlgLVDIK5Jm0yI+QPQXkr5D8KwKMqiYv9ZlLDufHSQJAJs9i\nurGWWWkleA4brDHmTtPsluVzdATgegPBrWtCPVw90g6DZbehqgbCRCWeQ5uSr8FK\n+g4AfxmbqdmQyMkpoQI/HvHjjPB9a/2qkpyjeiJIx2gmCmhBke9V/b3XFGBy3ci7\nLZRZUZLlAdJORX177Ief6MWqgXldlcP1N7mzWskE\n-----END RSA PRIVATE KEY-----\n"
|
13
|
+
secret = Duse::Client::Secret.new title: 'test', secret_text: secret_text, users: [current_user, server_user]
|
14
|
+
Duse::Client::SecretMarshaller.new(secret, private_key).to_h
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'own, server user and another user' do
|
19
|
+
it 'marshalls a secret with more users than self and server without errors' do
|
20
|
+
private_key = OpenSSL::PKey::RSA.new "-----BEGIN RSA PRIVATE KEY-----\nMIICWgIBAAKBgQCftZvHkB6uKWVDvrIzmy2p496Hv9PD/hhRk+DSXcE/CPtRmvYZ\nzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTeNdlaH9cRFV2wc2A/hbg2kaIS\nxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET2XM6tZcuwFULX6bl8QIDAQAB\nAoGAEJwyt26lwjdL8N/EaNmaxjCM1FF/FMM4hEN8/mQB1Sx59uLG9agPWzrDJcoS\nlH7ZalKLwpORTuCYvCtKH7Qm+fgnjKl/qyn6/cDmtk6VtJvPjQGi3oh2eRIMcwZv\nva+NQLF11bm0kVtZG5viIKlb1snHzkpPjFAOPBqQj2FNdgECQQDQdHWC5XYww2RQ\n/FpRBacJPIxb8PAwb7+EjqJSaruGO9CtLiDiCzlmidGP0Q++zwjAxksSqP4qkr6k\npKvDqkydAkEAxCLuq0c+6gnE9X1PUy4Bl/hUOxrk3ZQRCMUCE4XB8mNmJTLNY43O\ncY7Z1sdaCu7pAiGxQqojUYgwFACGmbOcZQJAZAvg8mfq59B/bxcOyeAqoRY8T0w+\nGyEnDBng8iljwzMmHlgLVDIK5Jm0yI+QPQXkr5D8KwKMqiYv9ZlLDufHSQJAJs9i\nurGWWWkleA4brDHmTtPsluVzdATgegPBrWtCPVw90g6DZbehqgbCRCWeQ5uSr8FK\n+g4AfxmbqdmQyMkpoQI/HvHjjPB9a/2qkpyjeiJIx2gmCmhBke9V/b3XFGBy3ci7\nLZRZUZLlAdJORX177Ief6MWqgXldlcP1N7mzWskE\n-----END RSA PRIVATE KEY-----\n"
|
21
|
+
current_user = OpenStruct.new public_key: OpenSSL::PKey::RSA.new("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCftZvHkB6uKWVDvrIzmy2p496H\nv9PD/hhRk+DSXcE/CPtRmvYZzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTe\nNdlaH9cRFV2wc2A/hbg2kaISxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET\n2XM6tZcuwFULX6bl8QIDAQAB\n-----END PUBLIC KEY-----\n")
|
22
|
+
server_user = OpenStruct.new public_key: OpenSSL::PKey::RSA.new("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC8Z1K4aCksOb6rsbKNcF4fNcN\n1Tbyv+ids751YvmfU2WHDXB3wIVoN1YRdb8Dk8608YlGAAqVaGVwfgYdyLMppIGs\nglZIMjwZFM2F84T4swKOEJJx6o3ZCRnP9ZQcceqzcIuTjiIqC7xu+QOvtADAMW68\nzZIpFOHjjiuxkA7PQQIDAQAB\n-----END PUBLIC KEY-----\n")
|
23
|
+
other_user = OpenStruct.new public_key: OpenSSL::PKey::RSA.new("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDTF2gEqXRy2hJ6+xjj6IbzAgHG\nHvnLNnZlwkYm0ZV89uiPxL9mKYNiW4KA1azZlvJZviTF4218WAwO1IGIH+PppdXF\nIK8vmB6IIaQcO4UTjSA6ZTn8Uwf1fwS4EAuL3Zr3IVdjVYQ4+/ZNtmSyVMmo+7zP\nyOa31hUhDNYrJO1iEQIDAQAB\n-----END PUBLIC KEY-----\n")
|
24
|
+
secret_text = "-----BEGIN RSA PRIVATE KEY-----\nMIICWgIBAAKBgQCftZvHkB6uKWVDvrIzmy2p496Hv9PD/hhRk+DSXcE/CPtRmvYZ\nzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTeNdlaH9cRFV2wc2A/hbg2kaIS\nxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET2XM6tZcuwFULX6bl8QIDAQAB\nAoGAEJwyt26lwjdL8N/EaNmaxjCM1FF/FMM4hEN8/mQB1Sx59uLG9agPWzrDJcoS\nlH7ZalKLwpORTuCYvCtKH7Qm+fgnjKl/qyn6/cDmtk6VtJvPjQGi3oh2eRIMcwZv\nva+NQLF11bm0kVtZG5viIKlb1snHzkpPjFAOPBqQj2FNdgECQQDQdHWC5XYww2RQ\n/FpRBacJPIxb8PAwb7+EjqJSaruGO9CtLiDiCzlmidGP0Q++zwjAxksSqP4qkr6k\npKvDqkydAkEAxCLuq0c+6gnE9X1PUy4Bl/hUOxrk3ZQRCMUCE4XB8mNmJTLNY43O\ncY7Z1sdaCu7pAiGxQqojUYgwFACGmbOcZQJAZAvg8mfq59B/bxcOyeAqoRY8T0w+\nGyEnDBng8iljwzMmHlgLVDIK5Jm0yI+QPQXkr5D8KwKMqiYv9ZlLDufHSQJAJs9i\nurGWWWkleA4brDHmTtPsluVzdATgegPBrWtCPVw90g6DZbehqgbCRCWeQ5uSr8FK\n+g4AfxmbqdmQyMkpoQI/HvHjjPB9a/2qkpyjeiJIx2gmCmhBke9V/b3XFGBy3ci7\nLZRZUZLlAdJORX177Ief6MWqgXldlcP1N7mzWskE\n-----END RSA PRIVATE KEY-----\n"
|
25
|
+
secret = Duse::Client::Secret.new title: 'test', secret_text: secret_text, users: [other_user, current_user, server_user]
|
26
|
+
|
27
|
+
Duse::Client::SecretMarshaller.new(secret, private_key).to_h
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,96 @@
|
|
1
|
+
RSpec.describe Duse::Client::Secret do
|
2
|
+
before :each do
|
3
|
+
Duse.config = Duse::CLIConfig.new({ 'uri' => 'https://example.com/'})
|
4
|
+
end
|
5
|
+
|
6
|
+
describe '.all' do
|
7
|
+
it 'loads a users secrets without shares' do
|
8
|
+
stub_get_secrets
|
9
|
+
secrets = Duse::Secret.all
|
10
|
+
|
11
|
+
expect(secrets.length).to eq 1
|
12
|
+
expect(secrets.class).to be Array
|
13
|
+
secrets.each do |secret|
|
14
|
+
expect(secret.class).to be Duse::Client::Secret
|
15
|
+
end
|
16
|
+
secret = secrets.first
|
17
|
+
expect(secret.title).to eq 'test'
|
18
|
+
expect(secret.attributes['shares']).to eq nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.find' do
|
23
|
+
context 'secret exists' do
|
24
|
+
it 'loads a single secret with shares' do
|
25
|
+
stub_secret_get
|
26
|
+
private_key = OpenSSL::PKey::RSA.new "-----BEGIN RSA PRIVATE KEY-----\nMIICWgIBAAKBgQCftZvHkB6uKWVDvrIzmy2p496Hv9PD/hhRk+DSXcE/CPtRmvYZ\nzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTeNdlaH9cRFV2wc2A/hbg2kaIS\nxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET2XM6tZcuwFULX6bl8QIDAQAB\nAoGAEJwyt26lwjdL8N/EaNmaxjCM1FF/FMM4hEN8/mQB1Sx59uLG9agPWzrDJcoS\nlH7ZalKLwpORTuCYvCtKH7Qm+fgnjKl/qyn6/cDmtk6VtJvPjQGi3oh2eRIMcwZv\nva+NQLF11bm0kVtZG5viIKlb1snHzkpPjFAOPBqQj2FNdgECQQDQdHWC5XYww2RQ\n/FpRBacJPIxb8PAwb7+EjqJSaruGO9CtLiDiCzlmidGP0Q++zwjAxksSqP4qkr6k\npKvDqkydAkEAxCLuq0c+6gnE9X1PUy4Bl/hUOxrk3ZQRCMUCE4XB8mNmJTLNY43O\ncY7Z1sdaCu7pAiGxQqojUYgwFACGmbOcZQJAZAvg8mfq59B/bxcOyeAqoRY8T0w+\nGyEnDBng8iljwzMmHlgLVDIK5Jm0yI+QPQXkr5D8KwKMqiYv9ZlLDufHSQJAJs9i\nurGWWWkleA4brDHmTtPsluVzdATgegPBrWtCPVw90g6DZbehqgbCRCWeQ5uSr8FK\n+g4AfxmbqdmQyMkpoQI/HvHjjPB9a/2qkpyjeiJIx2gmCmhBke9V/b3XFGBy3ci7\nLZRZUZLlAdJORX177Ief6MWqgXldlcP1N7mzWskE\n-----END RSA PRIVATE KEY-----\n"
|
27
|
+
|
28
|
+
secret = Duse::Secret.find 1
|
29
|
+
|
30
|
+
expect(secret.title).to eq 'test'
|
31
|
+
expect(secret.decrypt(private_key)).to eq 'test'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'secret does not exist' do
|
36
|
+
it 'raises an exception when requesting a non existant secret' do
|
37
|
+
stub_request(:get, "https://example.com/secrets/2").
|
38
|
+
with(headers: {'Accept'=>'application/vnd.duse.1+json'}).
|
39
|
+
to_return(status: 404, body: "", headers: {})
|
40
|
+
expect { Duse::Secret.find 2 }.to raise_error Duse::Client::NotFound
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#delete' do
|
46
|
+
it 'deletes an existing secret by id' do
|
47
|
+
stub_secret_delete
|
48
|
+
Duse::Secret.delete 1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '.delete' do
|
53
|
+
it 'can delete a previously retrieved secret' do
|
54
|
+
stub_secret_get
|
55
|
+
stub_secret_delete
|
56
|
+
secret = Duse::Secret.find 1
|
57
|
+
secret.delete
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '.create' do
|
62
|
+
it 'builds a secret' do
|
63
|
+
stub_create_secret
|
64
|
+
private_key = OpenSSL::PKey::RSA.new "-----BEGIN RSA PRIVATE KEY-----\nMIICWgIBAAKBgQCftZvHkB6uKWVDvrIzmy2p496Hv9PD/hhRk+DSXcE/CPtRmvYZ\nzbWbbBup9hkvhyH/P1O5EF8KSZm4Cdnz6p37idTeNdlaH9cRFV2wc2A/hbg2kaIS\nxrDxUqRbywBE9NOBSjXu2wRpy0TMo85eM2A0E2ET2XM6tZcuwFULX6bl8QIDAQAB\nAoGAEJwyt26lwjdL8N/EaNmaxjCM1FF/FMM4hEN8/mQB1Sx59uLG9agPWzrDJcoS\nlH7ZalKLwpORTuCYvCtKH7Qm+fgnjKl/qyn6/cDmtk6VtJvPjQGi3oh2eRIMcwZv\nva+NQLF11bm0kVtZG5viIKlb1snHzkpPjFAOPBqQj2FNdgECQQDQdHWC5XYww2RQ\n/FpRBacJPIxb8PAwb7+EjqJSaruGO9CtLiDiCzlmidGP0Q++zwjAxksSqP4qkr6k\npKvDqkydAkEAxCLuq0c+6gnE9X1PUy4Bl/hUOxrk3ZQRCMUCE4XB8mNmJTLNY43O\ncY7Z1sdaCu7pAiGxQqojUYgwFACGmbOcZQJAZAvg8mfq59B/bxcOyeAqoRY8T0w+\nGyEnDBng8iljwzMmHlgLVDIK5Jm0yI+QPQXkr5D8KwKMqiYv9ZlLDufHSQJAJs9i\nurGWWWkleA4brDHmTtPsluVzdATgegPBrWtCPVw90g6DZbehqgbCRCWeQ5uSr8FK\n+g4AfxmbqdmQyMkpoQI/HvHjjPB9a/2qkpyjeiJIx2gmCmhBke9V/b3XFGBy3ci7\nLZRZUZLlAdJORX177Ief6MWqgXldlcP1N7mzWskE\n-----END RSA PRIVATE KEY-----\n"
|
65
|
+
|
66
|
+
secret = Duse::Secret.create({
|
67
|
+
title: 'test',
|
68
|
+
parts: [[
|
69
|
+
{
|
70
|
+
user_id: 'server',
|
71
|
+
content: "T6BKRp0U41zRRZ2NSRPwpJW7NjsSZaJ1eSSdtYNjchscXb5UIEqzSr/+B6Zy\nJ8JUA9CPrad+Z6s1CNNPGN6sJKtdAtzk+zJr5vTeg/4Aw42799A8cFPw/fE9\nd5K+IIYjn4Yxtypcv0I2j+dYsgDvN+mhosZ21cdibfX5PyyibuA=\n",
|
72
|
+
signature: "XBMWwpKyO5K+S1dimX/7aZ4oX7dW5SDlf4KaagYUBoVm7ii7jX9jfLKWqrRL\nj2f85JYMSUQ3UoXVWT1LDWXXZIs3KO02xlvA+oflmx5ZSGx57TDvuYpusEBu\n/LNSpNj6ooROXTm+Xq+AvQfmt0bjQZCg/PSOt8Qx11q5JLmhL38=\n"
|
73
|
+
},
|
74
|
+
{
|
75
|
+
user_id: 'me',
|
76
|
+
content: "XMcasmkkD0eOB52ilT3sGUOy9ehHpsuIFnbmErKLsTq0PExcvSuGT6RwMKjE\nM3rS7Lu2nHgWm0IPSzi5Vd8ieJTgyayYgT9VCOOnKGqfAMmCpV0WrHpfNwLu\nUgH7VC3Wfk1F+6yzWAFOoYXBDUuIRRau4uswCpedp1pe3csmO+I=\n",
|
77
|
+
signature: "dAhhcWeebiE5E9jfhexu+/zem1emWG2vBQed06f4CJJ24xrWDwUgX9kggHka\nsOWgu/RnCN/qzIuN8XCPgh2I1zQegd9d5FdAHUqXJRgpT2bvmAYPZ6DM6UYs\nB8OJ7+QgQy9EahTfhfbrSKuqtnX9103ftTZtiGagzFdYi5ylMNM=\n"
|
78
|
+
}
|
79
|
+
]]
|
80
|
+
})
|
81
|
+
|
82
|
+
expect(secret.title).to eq 'test'
|
83
|
+
expect(secret.decrypt(private_key)).to eq 'test'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'can handle any utf-8 character' do
|
88
|
+
encoded_secret = Duse::Encryption.encode('äõüß')
|
89
|
+
shares = SecretSharing.split_secret(encoded_secret, 2, 2)
|
90
|
+
private_key = OpenSSL::PKey::RSA.new(1024)
|
91
|
+
shares = shares.map { |p| Duse::Encryption.encrypt(private_key, private_key.public_key, p)[0] }
|
92
|
+
secret = Duse::Client::Secret.new(parts: [shares])
|
93
|
+
secret_text = secret.decrypt(private_key)
|
94
|
+
expect(secret_text).to eq 'äõüß'
|
95
|
+
end
|
96
|
+
end
|