Birst_Command 0.3.0 → 0.4.0

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 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
-