Birst_Command 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bcdd92e145719837d3ed3e8852f5988dd67ce45f
4
- data.tar.gz: d2da607de6a3b7b6464f2d75a3b184233f3bc06b
3
+ metadata.gz: 2fcfeb6fbba597581204ab786c08a523038a34dd
4
+ data.tar.gz: 3aa45b83a061a5375c33d1a25caa0f7f9a242d2a
5
5
  SHA512:
6
- metadata.gz: a4b62531154a7185851a7063ca352c4f02b5699b7c971c776d66923564dfcf1df4cd207b1e057c499cf6623519cbf7a214d6f5a7134841d198acf084a3b7c330
7
- data.tar.gz: 4450044f4856ed4ecfe05b59ba189926f8edf93cad684a3d38f9b5ab0ebbfb024579448a39bede084cb75843e98c3811163c2d1f676155ade18c5861253e8043
6
+ metadata.gz: 06dc2ba21870c94529450d603469d216b77cbfd307d2b8ec2784404320273d367e40715dcd22d9a1a0597b9e6dae8ec8aab05c6e875745f84dfad97161350d38
7
+ data.tar.gz: 58252f327c73d797487fe37cb8e9f901400454c46495df153751ee12efbfb82f9a65c6c6baecc918ff2cda63d51b5ab9ad13d4efc63cb131111fb096c733d7e4
data/README.md CHANGED
@@ -2,13 +2,17 @@ Birst_Command
2
2
  ====================
3
3
 
4
4
  Birst Command is a Ruby gem that allows you to build Ruby scripts that
5
- interface with the Birst Web API.
5
+ interface with the Birst Web API. It also comes with a simple command line
6
+ tool that can be used to execute simple API requests from the command line.
6
7
 
7
8
  Note: this is not an officially-sanctioned Birst project. I'm just a
8
9
  Birst user that needed to set up a very basic Ruby interface.
9
10
 
10
11
  # Installation & Setup
11
12
 
13
+ **SPECIAL NOTE:** Password management has changed since version 0.3.0.
14
+ It is now more secure but requires some new configuration.
15
+
12
16
  Prerequisites: Ruby > 2.0 and rubygems.
13
17
 
14
18
  Install the gem using `gem install Birst_Command` or using rvm or
@@ -22,23 +26,38 @@ config file should look like,
22
26
  "wsdl": "https://app2102.bws.birst.com/CommandWebService.asmx?WSDL",
23
27
  "endpoint": "https://app2102.bws.birst.com/CommandWebService.asmx",
24
28
  "username": "name@myplace.com",
25
- "password": "obfuscated pwd"
29
+ "password": "encrypted pwd"
26
30
  }
27
31
 
28
- Most users should only need to modify the username and
29
- password. (**Note**: do not use `login.bws.birst.com` since it does
30
- not use an updated WSDL; a specific app server must be specified).
31
- Since I have a strong aversion to storing passwords in plaintext, the
32
- password in the config file needs to be an obfuscated password. Birst
33
- Command comes with a password obfuscator that can be executed via
32
+ Save it to `$HOME/.birstcl`. Most users should only need to modify
33
+ the username and password. (**Note**: do not use `login.bws.birst.com`
34
+ since it does not use an updated WSDL; a specific app server must be
35
+ specified). Since I have a strong aversion to storing passwords in
36
+ plaintext, the password in the config file needs to use and encrypted
37
+ password. Birst Command comes with a password encryptor that can be
38
+ executed via
34
39
 
35
- $ obfuscate_pwd.rb mypassword
36
- 0x8GOZ5nA3oRSSS8ao1l6Q==
40
+ ````bash
41
+ $ birstcl -e mypassword
42
+ ````
37
43
 
38
- Copy and paste the obfuscated password into the config file and **save
39
- to a secure location**. If any attacker is able to get your
40
- obfuscated password and knows it was created using this program, it
41
- would be trivial to get your Birst login credentials.
44
+ which should give an output similar to
45
+ ````
46
+ Set these keys as environment variables
47
+ - Do not lose them or you will have to regenerate your password.
48
+ - Keep them secure, your password is compromised if these keys are compromised.
49
+ - Remove them as environment variables to generate new ones
50
+ BIRST_COMMAND_IV="3Nn26chRPkclusqTHePpig=="
51
+ BIRST_COMMAND_KEY="MWHa7gksYQaZTTq4snjyOnBDWUnKaVJq1VF4cv82MgA="
52
+ BIRST_COMMAND_SALT="KI//0xfSrX4mdSpiSp69BQ=="
53
+ ...
54
+ Use this encrypted password in your .birstcl file: "JlCX9/RvHnGuWZWUcjTelg=="
55
+ ````
56
+
57
+ Copy and paste the encrypted password into the config file. You will
58
+ also need to ensure that the three environment variables are set as
59
+ indicated above. If you're running in a development environment, you
60
+ can include these in your bash `~/.profile` file.
42
61
 
43
62
  # Usage - Birst command line tool
44
63
 
@@ -130,6 +149,18 @@ The `spaces` variable is a Ruby hash parsed from the SOAP response.
130
149
  The structure of the returned hash follows the structure that Birst
131
150
  returns.
132
151
 
152
+ ## Command arguments
153
+
154
+ Some Birst API commands require arguments. All arguments are supplied
155
+ as an argument hash. All arguments are mandatory even if they're blank/null
156
+ (Birst web API requirement). For example, to create a new space,
157
+
158
+ ````ruby
159
+ Birst_Command::Session.start do |bc|
160
+ new_space_id = bc.create_new_space :spaceName => "myNewSpace", :comments => "Just testing",:automatic => "false"
161
+ end
162
+ ````
163
+
133
164
  ## Cookies
134
165
 
135
166
  The start session block can also accept an argument named `use_cookie` to
@@ -157,7 +188,8 @@ puts "COMPLETE? #{is_job_complete}"
157
188
  ## Helper methods
158
189
 
159
190
  I find some of the Birst API responses to be rather cumbersome. For
160
- example, why do I need hash with a single `user_space` key? I'd
191
+ example, why do I need hash with a single `user_space` key when I
192
+ run the `list_spaces` command? I'd
161
193
  rather have an array of hashes here. To that end I find it convenient
162
194
  to define helper methods that extend the Session class to simplify
163
195
  some of this. To override the return value of the native
@@ -168,7 +200,7 @@ class Birst_Command::Session
168
200
  def list_spaces(*args)
169
201
  result = command __method__, *args
170
202
  [result[:user_space]].flatten
171
- end
203
+ end
172
204
  end
173
205
  ````
174
206
 
@@ -181,17 +213,6 @@ gem because what I find helpful, you may not. Birst Command just
181
213
  provides the basic interface.
182
214
 
183
215
 
184
- ## Command arguments
185
-
186
- Some Birst API commands require arguments. All arguments are supplied
187
- as an argument hash. For example, to create a new space,
188
-
189
- ````ruby
190
- Birst_Command::Session.start do |bc|
191
- new_space_id = bc.create_new_space :spaceName => "myNewSpace", :comments => "Just testing",:automatic => "false"
192
- end
193
- ````
194
-
195
216
  ## camelCase/snake_case issues
196
217
 
197
218
  Birst Command uses [Savon](http://savonrb.com/version2/client.html) as
@@ -206,4 +227,3 @@ entirely consistent in its use of camelCase for arguments (e.g.,
206
227
  `listUsersInSpace`). This inconsistency requires us to **submit
207
228
  commands as snake_case and arguments as the camelCase that Birst
208
229
  uses.**
209
-
@@ -6,6 +6,24 @@ require "optparse"
6
6
  module BirstCL
7
7
  extend self
8
8
 
9
+ def run
10
+ set_options
11
+
12
+ if @options[:version]
13
+ puts "Birst_Command Version: #{Birst_Command::VERSION}"
14
+ exit
15
+ end
16
+
17
+ if @options[:encrypt_password]
18
+ encrypt_password(@options[:encrypt_password])
19
+ end
20
+
21
+ if @options[:command]
22
+ read_config_file
23
+ execute_command
24
+ end
25
+ end
26
+
9
27
  def set_options
10
28
  @options = {}
11
29
 
@@ -26,6 +44,11 @@ module BirstCL
26
44
  exit
27
45
  end
28
46
 
47
+ @options[:encrypt_password] = nil
48
+ opts.on("-e","--encrypt_password <PASSWORD>","Generates an encrypted version of PASSWORD that needs to be placed in ~/.birstcl") do |opt|
49
+ @options[:encrypt_password] = opt
50
+ end
51
+
29
52
  @options[:command] = nil
30
53
  opts.on("-c","--command <COMMAND>","COMMAND is the snake_case Birst web API command") do |opt|
31
54
  @options[:command] = opt
@@ -57,13 +80,7 @@ module BirstCL
57
80
  opts.on("-r","--read_cookie_file <COOKIE FILE>", "Path to cookie file to read") do |opt|
58
81
  @options[:read_cookie_full_path] = opt
59
82
  end
60
-
61
83
  end.parse!
62
-
63
- if @options[:version]
64
- puts "Birst_Command Version: #{Birst_Command::VERSION}"
65
- exit
66
- end
67
84
  end
68
85
 
69
86
 
@@ -71,6 +88,14 @@ module BirstCL
71
88
  Birst_Command::Config.read_config(@options[:config_full_path])
72
89
  end
73
90
 
91
+ def encrypt_password(password)
92
+ Birst_Command::Password.generate_keys(verbose: true)
93
+ puts <<-EOF.unindent
94
+ ...
95
+ Use this encrypted password in your .birstcl file: "#{Birst_Command::Password.encrypt(password)}"
96
+ EOF
97
+ end
98
+
74
99
 
75
100
  def write_cookie_file(file_full_path)
76
101
  return nil if file_full_path.nil?
@@ -100,6 +125,4 @@ module BirstCL
100
125
  end
101
126
  end
102
127
 
103
- BirstCL.set_options
104
- BirstCL.read_config_file
105
- BirstCL.execute_command
128
+ BirstCL.run
@@ -2,10 +2,12 @@ require 'savon'
2
2
  require 'httpclient'
3
3
  require 'openssl'
4
4
  require 'base64'
5
+ require 'securerandom'
5
6
  require 'json'
6
7
 
7
8
  require 'birst_command/config'
9
+ require 'birst_command/core_additions'
8
10
  require 'birst_command/version'
9
- require 'birst_command/obfuscate'
11
+ require 'birst_command/password'
10
12
  require 'birst_command/session'
11
13
 
@@ -1,4 +1,5 @@
1
1
  module Birst_Command
2
+ require 'erb'
2
3
  module Config
3
4
  extend self
4
5
 
@@ -12,7 +13,9 @@ module Birst_Command
12
13
  }
13
14
 
14
15
  def read_config(config_full_path = @config_full_path)
15
- @options = @options.merge!(JSON.parse(IO.read(config_full_path), :symbolize_names => true))
16
+ parse_erb = ERB.new(IO.read(config_full_path)).result(binding)
17
+ parse_json = JSON.parse(parse_erb, :symbolize_names => true)
18
+ @options = @options.merge!(parse_json)
16
19
  end
17
20
 
18
21
  def set_debug
@@ -0,0 +1,8 @@
1
+ class String
2
+ # Strip leading whitespace from each line that is the same as the
3
+ # amount of whitespace on the first line of the string.
4
+ # Leaves _additional_ indentation on later lines intact.
5
+ def unindent
6
+ gsub /^#{self[/\A\s*/]}/, ''
7
+ end
8
+ end
@@ -0,0 +1,66 @@
1
+ module Birst_Command
2
+ module Password
3
+ extend self
4
+
5
+ def generate_keys(verbose: false)
6
+ iv = ENV['BIRST_COMMAND_IV'] || SecureRandom.base64
7
+ key = ENV['BIRST_COMMAND_KEY'] || SecureRandom.base64(32)
8
+ salt = ENV['BIRST_COMMAND_SALT'] || SecureRandom.base64
9
+
10
+ if verbose
11
+ puts <<-EOF.unindent
12
+ Set these keys as environment variables
13
+ - Do not lose them or you will have to regenerate your password.
14
+ - Keep them secure, your password is compromised if these keys are compromised.
15
+ - Remove them as environment variables to generate new ones
16
+ BIRST_COMMAND_IV="#{iv}"
17
+ BIRST_COMMAND_KEY="#{key}"
18
+ BIRST_COMMAND_SALT="#{salt}"
19
+ EOF
20
+ end
21
+
22
+ ENV['BIRST_COMMAND_IV'] = iv
23
+ ENV['BIRST_COMMAND_KEY'] = key
24
+ ENV['BIRST_COMMAND_SALT'] = salt
25
+ end
26
+
27
+ # Generate a new cipher for encryption or decryption
28
+ def create_cipher(mode)
29
+ iv = ENV['BIRST_COMMAND_IV'] || SecureRandom.base64
30
+ key = ENV['BIRST_COMMAND_KEY'] || SecureRandom.base64(32)
31
+ salt = ENV['BIRST_COMMAND_SALT'] || SecureRandom.base64
32
+
33
+ cipher = OpenSSL::Cipher.new 'AES-128-CBC'
34
+ cipher.send(mode)
35
+ cipher.iv = iv
36
+
37
+ digest = OpenSSL::Digest::SHA256.new
38
+ key_len = cipher.key_len
39
+ iter = 20000
40
+ cipher.key = OpenSSL::PKCS5.pbkdf2_hmac(key, salt, iter, key_len, digest)
41
+ cipher
42
+ end
43
+
44
+
45
+ # Returns a base64-obfuscated password to be stored in the config.json file
46
+ def encrypt(pwd)
47
+ cipher = create_cipher(:encrypt)
48
+
49
+ encrypted = cipher.update pwd
50
+ encrypted << cipher.final
51
+
52
+ Base64.encode64(encrypted).chomp
53
+ end
54
+
55
+
56
+ # Returns a plaintext password
57
+ def decrypt(encrypted_pwd)
58
+ cipher = create_cipher(:decrypt)
59
+
60
+ decrypted = cipher.update Base64.decode64(encrypted_pwd)
61
+ decrypted << cipher.final
62
+
63
+ decrypted
64
+ end
65
+ end
66
+ end
@@ -39,7 +39,7 @@ module Birst_Command
39
39
  cookies: @auth_cookies,
40
40
  message: {
41
41
  username: @options[:username],
42
- password: Obfuscate.deobfuscate(@options[:password])
42
+ password: Password.decrypt(@options[:password])
43
43
  })
44
44
 
45
45
  @auth_cookies = @response.http.cookies if @auth_cookies.nil?
@@ -1,3 +1,3 @@
1
1
  module Birst_Command
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -0,0 +1,54 @@
1
+ require "test_birst_command"
2
+
3
+ class Test_password < Test::Unit::TestCase
4
+
5
+ def setup
6
+ ENV['BIRST_COMMAND_IV'] = "3Ez9fL0Jlt/E1d7QlVtKdw=="
7
+ ENV['BIRST_COMMAND_KEY'] = "N589Xi0YzzkE+bRGwp3yaoVk/lneYsLHdFP+366hwcY="
8
+ ENV['BIRST_COMMAND_SALT'] = "AUkJj8QSmNW3QazpyNl7og=="
9
+
10
+ @password = "mysecretpass"
11
+ @encrypted = "dP5+BfQyTAvKOM6s1ik4zg=="
12
+ end
13
+
14
+ def teardown
15
+ end
16
+
17
+ def test_key_generation
18
+ ENV['BIRST_COMMAND_IV'] = nil
19
+ ENV['BIRST_COMMAND_KEY'] = nil
20
+ ENV['BIRST_COMMAND_SALT'] = nil
21
+
22
+ Password.generate_keys
23
+
24
+ encrypted = Password.encrypt(@password)
25
+ decrypted = Password.decrypt(encrypted)
26
+
27
+ assert_equal @password, decrypted, "Wrong decrypted password"
28
+ end
29
+
30
+
31
+ def test_decryption_failure
32
+ Password.generate_keys
33
+
34
+ encrypted = Password.encrypt(@password)
35
+ ENV['BIRST_COMMAND_SALT'] = SecureRandom.base64
36
+
37
+ assert_raise OpenSSL::Cipher::CipherError do
38
+ decrypted = Password.decrypt(encrypted)
39
+ end
40
+ end
41
+
42
+
43
+ def test_encrypt
44
+ assert_equal @encrypted, Password.encrypt(@password), "Expecting encrypted password #{@encrypted}"
45
+ end
46
+
47
+ def test_decrypt
48
+ assert_equal @password, Password.decrypt(@encrypted), "Expecting decrypted password #{@password}"
49
+ end
50
+
51
+
52
+ end
53
+
54
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: Birst_Command
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sterling Paramore
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-14 00:00:00.000000000 Z
11
+ date: 2014-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: savon
@@ -43,7 +43,6 @@ email:
43
43
  - gnilrets@gmail.com
44
44
  executables:
45
45
  - birstcl
46
- - obfuscate_pwd.rb
47
46
  extensions: []
48
47
  extra_rdoc_files: []
49
48
  files:
@@ -56,12 +55,12 @@ files:
56
55
  - README.md
57
56
  - Rakefile
58
57
  - bin/birstcl
59
- - bin/obfuscate_pwd.rb
60
58
  - config.json_template
61
59
  - doc/roadmap.md
62
60
  - lib/birst_command.rb
63
61
  - lib/birst_command/config.rb
64
- - lib/birst_command/obfuscate.rb
62
+ - lib/birst_command/core_additions.rb
63
+ - lib/birst_command/password.rb
65
64
  - lib/birst_command/session.rb
66
65
  - lib/birst_command/version.rb
67
66
  - test/.gitignore
@@ -71,9 +70,9 @@ files:
71
70
  - test/standard/test_command_helper.rb
72
71
  - test/standard/test_cookie.rb
73
72
  - test/standard/test_copy_space.rb
74
- - test/standard/test_deobfuscate.rb
75
73
  - test/standard/test_list_spaces.rb
76
74
  - test/standard/test_login.rb
75
+ - test/standard/test_password.rb
77
76
  - test/standard/test_read_config.rb
78
77
  - test/test_birst_command.rb
79
78
  homepage: https://github.com/gnilrets
@@ -107,8 +106,8 @@ test_files:
107
106
  - test/standard/test_command_helper.rb
108
107
  - test/standard/test_cookie.rb
109
108
  - test/standard/test_copy_space.rb
110
- - test/standard/test_deobfuscate.rb
111
109
  - test/standard/test_list_spaces.rb
112
110
  - test/standard/test_login.rb
111
+ - test/standard/test_password.rb
113
112
  - test/standard/test_read_config.rb
114
113
  - test/test_birst_command.rb
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env ruby
2
- require "birst_command"
3
-
4
- if ARGV[0]
5
- puts Birst_Command::Obfuscate.obfuscate(ARGV[0])
6
- else
7
- puts "USAGE: ./obfuscate_pwd <plaintxt password>"
8
- end
9
-
@@ -1,35 +0,0 @@
1
- module Birst_Command
2
- module Obfuscate
3
- extend self
4
-
5
- @crypt_pwd = "BirstIsAwesome"
6
- @salt = "31415927"
7
- @cypher = "AES-128-CBC"
8
-
9
- def obfuscate(pwd)
10
- # Returns a base64-obfuscated password to be stored in the config.json file
11
- encrypter = OpenSSL::Cipher::Cipher.new @cypher
12
- encrypter.encrypt
13
- encrypter.pkcs5_keyivgen @crypt_pwd, @salt
14
-
15
- encrypted = encrypter.update pwd
16
- encrypted << encrypter.final
17
-
18
- Base64.encode64(encrypted).chomp
19
- end
20
-
21
-
22
- def deobfuscate(obfuscated_pwd)
23
- # Returns a plaintext password
24
- decrypter = OpenSSL::Cipher::Cipher.new @cypher
25
- decrypter.decrypt
26
- decrypter.pkcs5_keyivgen @crypt_pwd, @salt
27
-
28
- plain = decrypter.update Base64.decode64(obfuscated_pwd)
29
- plain << decrypter.final
30
-
31
- plain
32
- end
33
-
34
- end
35
- end
@@ -1,23 +0,0 @@
1
- require "test_birst_command"
2
-
3
- class Test_deobfuscate < Test::Unit::TestCase
4
-
5
- def setup
6
- @pwd = "mysecretpass"
7
- @obs_pwd = "IQX4os6wCE7rl+JuSYL2Iw=="
8
- end
9
-
10
- def teardown
11
- end
12
-
13
- def test_obfuscate
14
- assert_equal @obs_pwd, Obfuscate.obfuscate(@pwd), "Expecting password #{@obs_pwd}"
15
- end
16
-
17
- def test_deobfuscate
18
- assert_equal @pwd, Obfuscate.deobfuscate(@obs_pwd), "Expecting password #{@pwd}"
19
- end
20
-
21
- end
22
-
23
-