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.
Files changed (61) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +35 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +13 -0
  6. data/LICENSE +22 -0
  7. data/README.md +89 -0
  8. data/Rakefile +11 -0
  9. data/bin/duse +8 -0
  10. data/duse.gemspec +22 -0
  11. data/lib/duse.rb +10 -0
  12. data/lib/duse/cli.rb +104 -0
  13. data/lib/duse/cli/account.rb +20 -0
  14. data/lib/duse/cli/account_confirm.rb +22 -0
  15. data/lib/duse/cli/account_info.rb +18 -0
  16. data/lib/duse/cli/account_password.rb +14 -0
  17. data/lib/duse/cli/account_password_change.rb +30 -0
  18. data/lib/duse/cli/account_password_reset.rb +20 -0
  19. data/lib/duse/cli/account_resend_confirmation.rb +20 -0
  20. data/lib/duse/cli/account_update.rb +25 -0
  21. data/lib/duse/cli/api_command.rb +33 -0
  22. data/lib/duse/cli/cli_config.rb +54 -0
  23. data/lib/duse/cli/command.rb +202 -0
  24. data/lib/duse/cli/config.rb +16 -0
  25. data/lib/duse/cli/help.rb +23 -0
  26. data/lib/duse/cli/key_helper.rb +84 -0
  27. data/lib/duse/cli/login.rb +27 -0
  28. data/lib/duse/cli/meta_command.rb +12 -0
  29. data/lib/duse/cli/parser.rb +43 -0
  30. data/lib/duse/cli/password_helper.rb +17 -0
  31. data/lib/duse/cli/register.rb +45 -0
  32. data/lib/duse/cli/secret.rb +19 -0
  33. data/lib/duse/cli/secret_add.rb +38 -0
  34. data/lib/duse/cli/secret_generator.rb +10 -0
  35. data/lib/duse/cli/secret_get.rb +40 -0
  36. data/lib/duse/cli/secret_list.rb +20 -0
  37. data/lib/duse/cli/secret_remove.rb +19 -0
  38. data/lib/duse/cli/secret_update.rb +44 -0
  39. data/lib/duse/cli/share_with_user.rb +53 -0
  40. data/lib/duse/cli/version.rb +12 -0
  41. data/lib/duse/client/config.rb +36 -0
  42. data/lib/duse/client/entity.rb +87 -0
  43. data/lib/duse/client/namespace.rb +112 -0
  44. data/lib/duse/client/secret.rb +69 -0
  45. data/lib/duse/client/session.rb +128 -0
  46. data/lib/duse/client/user.rb +23 -0
  47. data/lib/duse/encryption.rb +39 -0
  48. data/lib/duse/version.rb +3 -0
  49. data/spec/cli/cli_config_spec.rb +49 -0
  50. data/spec/cli/commands/account_spec.rb +45 -0
  51. data/spec/cli/commands/config_spec.rb +17 -0
  52. data/spec/cli/commands/login_spec.rb +51 -0
  53. data/spec/cli/commands/register_spec.rb +38 -0
  54. data/spec/cli/commands/secret_spec.rb +142 -0
  55. data/spec/client/secret_marshaller_spec.rb +32 -0
  56. data/spec/client/secret_spec.rb +96 -0
  57. data/spec/client/user_spec.rb +105 -0
  58. data/spec/spec_helper.rb +70 -0
  59. data/spec/support/helpers.rb +43 -0
  60. data/spec/support/mock_api.rb +142 -0
  61. metadata +159 -0
@@ -0,0 +1,3 @@
1
+ module Duse
2
+ VERSION = '0.0.2'
3
+ end
@@ -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