noms-command 0.5.0 → 2.1.1
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 +4 -4
- data/README.rst +104 -37
- data/TODO.rst +3 -3
- data/fixture/dnc.rb +110 -1
- data/fixture/public/dnc.json +6 -5
- data/fixture/public/lib/dnc.js +171 -24
- data/fixture/public/lib/nomsargs.js +72 -0
- data/lib/noms/command.rb +37 -8
- data/lib/noms/command/application.rb +32 -26
- data/lib/noms/command/auth.rb +44 -62
- data/lib/noms/command/auth/identity.rb +205 -5
- data/lib/noms/command/base.rb +11 -1
- data/lib/noms/command/formatter.rb +5 -4
- data/lib/noms/command/home.rb +21 -0
- data/lib/noms/command/useragent.rb +117 -40
- data/lib/noms/command/useragent/cache.rb +124 -0
- data/lib/noms/command/useragent/requester.rb +48 -0
- data/lib/noms/command/useragent/requester/httpclient.rb +61 -0
- data/lib/noms/command/useragent/requester/typhoeus.rb +73 -0
- data/lib/noms/command/useragent/response.rb +202 -0
- data/lib/noms/command/useragent/response/httpclient.rb +59 -0
- data/lib/noms/command/useragent/response/typhoeus.rb +74 -0
- data/lib/noms/command/version.rb +1 -1
- data/lib/noms/command/window.rb +21 -3
- data/lib/noms/command/xmlhttprequest.rb +8 -8
- data/noms-command.gemspec +3 -1
- data/spec/07js_spec.rb +1 -1
- data/spec/10auth_spec.rb +132 -0
- data/spec/11useragent_cache_spec.rb +160 -0
- data/spec/12useragent_auth_cookie_spec.rb +53 -0
- data/spec/13useragent_auth_spec.rb +90 -0
- data/spec/spec_helper.rb +5 -0
- metadata +46 -4
- data/fixture/public/lib/noms-args.js +0 -13
data/lib/noms/command/version.rb
CHANGED
data/lib/noms/command/window.rb
CHANGED
@@ -54,11 +54,29 @@ class NOMS::Command::Window::Console < NOMS::Command::Base
|
|
54
54
|
|
55
55
|
# Some implementations have a kind of format string. I don't
|
56
56
|
def log(*items)
|
57
|
-
@log.debug(items.map { |s|
|
57
|
+
@log.debug(items.map { |s| _string(s) }.join(', '))
|
58
58
|
end
|
59
59
|
|
60
|
-
def
|
61
|
-
s
|
60
|
+
def _string(s)
|
61
|
+
s = _sanitize(s)
|
62
|
+
s.kind_of?(Enumerable) ? s.to_json : s.inspect
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get rid of V8 stuff
|
66
|
+
def _sanitize(thing)
|
67
|
+
# This really needs to go into a class
|
68
|
+
if thing.kind_of? V8::Array or thing.respond_to? :to_ary
|
69
|
+
thing.map do |item|
|
70
|
+
_sanitize item
|
71
|
+
end
|
72
|
+
elsif thing.respond_to? :keys
|
73
|
+
Hash[
|
74
|
+
thing.keys.map do |key|
|
75
|
+
[key, _sanitize(thing[key])]
|
76
|
+
end]
|
77
|
+
else
|
78
|
+
thing
|
79
|
+
end
|
62
80
|
end
|
63
81
|
|
64
82
|
end
|
@@ -4,8 +4,6 @@ require 'noms/command/urinion'
|
|
4
4
|
require 'noms/command/useragent'
|
5
5
|
require 'noms/command/error'
|
6
6
|
|
7
|
-
require 'httpclient'
|
8
|
-
|
9
7
|
class NOMS
|
10
8
|
|
11
9
|
end
|
@@ -113,7 +111,7 @@ class NOMS::Command::XMLHttpRequest
|
|
113
111
|
end
|
114
112
|
|
115
113
|
def HEADERS_RECEIVED
|
116
|
-
|
114
|
+
HEADERS_RECEIVED
|
117
115
|
end
|
118
116
|
|
119
117
|
def LOADING
|
@@ -154,24 +152,26 @@ class NOMS::Command::XMLHttpRequest
|
|
154
152
|
self.readyState = OPENED
|
155
153
|
self.readyState = HEADERS_RECEIVED
|
156
154
|
self.readyState = LOADING
|
157
|
-
@responseText = @response.
|
155
|
+
@responseText = @response.body
|
158
156
|
self.readyState = DONE
|
159
157
|
end
|
160
158
|
|
161
159
|
def status
|
162
|
-
@response.status
|
160
|
+
@response.status unless @response.nil?
|
163
161
|
end
|
164
162
|
|
165
163
|
def statusText
|
166
|
-
@response.
|
164
|
+
@response.statusText unless @response.nil?
|
167
165
|
end
|
168
166
|
|
169
167
|
def getResponseHeader(header)
|
170
|
-
@response.header
|
168
|
+
@response.header header unless @response.nil?
|
171
169
|
end
|
172
170
|
|
173
171
|
def getAllResponseHeaders
|
174
|
-
|
172
|
+
unless @response.nil?
|
173
|
+
lambda { || @response.header.map { |h, v| "#{h}: #{v}" }.join("\n") + "\n" }
|
174
|
+
end
|
175
175
|
end
|
176
176
|
|
177
177
|
def abort()
|
data/noms-command.gemspec
CHANGED
@@ -19,12 +19,14 @@ Gem::Specification.new do |spec|
|
|
19
19
|
|
20
20
|
spec.add_runtime_dependency "therubyracer"
|
21
21
|
spec.add_runtime_dependency "mime-types"
|
22
|
-
spec.add_runtime_dependency "
|
22
|
+
spec.add_runtime_dependency "typhoeus"
|
23
23
|
spec.add_runtime_dependency "json"
|
24
24
|
spec.add_runtime_dependency "trollop"
|
25
25
|
spec.add_runtime_dependency "highline"
|
26
|
+
spec.add_runtime_dependency "bcrypt"
|
26
27
|
|
27
28
|
spec.add_development_dependency "bundler", "~> 1.7"
|
28
29
|
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "rspec"
|
29
31
|
spec.add_development_dependency "sinatra"
|
30
32
|
end
|
data/spec/07js_spec.rb
CHANGED
@@ -67,7 +67,7 @@ describe NOMS::Command::Application do
|
|
67
67
|
app = make_script_app("console.log('test debug output')", :logger => log)
|
68
68
|
app.fetch!
|
69
69
|
app.render!
|
70
|
-
expect(logcatcher.string).to match(/^D,.*test debug output
|
70
|
+
expect(logcatcher.string).to match(/^D,.*test debug output/)
|
71
71
|
end
|
72
72
|
|
73
73
|
end
|
data/spec/10auth_spec.rb
CHANGED
@@ -2,6 +2,138 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
+
require 'digest/sha1'
|
6
|
+
|
7
|
+
require 'noms/command/application'
|
8
|
+
require 'noms/command/auth'
|
9
|
+
|
10
|
+
describe NOMS::Command::Auth::Identity do
|
11
|
+
|
12
|
+
before(:all) do
|
13
|
+
setup_fixture
|
14
|
+
NOMS::Command::Auth::Identity.identity_dir = 'test/identities'
|
15
|
+
File.chmod 0600, 'test/identity'
|
16
|
+
FileUtils.rm_r 'test/identities' if File.directory? 'test/identities'
|
17
|
+
File.unlink 'test/identities/.noms-vault-key' if File.exist? 'test/identities/.noms-vault-key'
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:all) do
|
21
|
+
teardown_fixture
|
22
|
+
end
|
23
|
+
|
24
|
+
before(:each) do
|
25
|
+
@identity = NOMS::Command::Auth::Identity.new({ 'id' => 'Authorization+Required=http://localhost:8787/',
|
26
|
+
'data' => 'testdata' })
|
27
|
+
identity_file = File.join(NOMS::Command::Auth::Identity.identity_dir, @identity.id_number)
|
28
|
+
@vault_keyfile = File.join(NOMS::Command::Auth::Identity.identity_dir, '.noms-vault-key')
|
29
|
+
File.unlink "#{identity_file}.json" if File.exist? "#{identity_file}.json"
|
30
|
+
File.unlink "#{identity_file}.enc" if File.exist? "#{identity_file}.json"
|
31
|
+
File.unlink @vault_keyfile if File.exist? @vault_keyfile
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '.new' do
|
35
|
+
|
36
|
+
it "should create an identity" do
|
37
|
+
identity = NOMS::Command::Auth::Identity.new({'id' => 'Authorization+Required=http://localhost:8787/',
|
38
|
+
'data' => 'testdata' })
|
39
|
+
expect(identity).to be_a NOMS::Command::Auth::Identity
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#id_number" do
|
45
|
+
|
46
|
+
it "should return the hash of the identity id" do
|
47
|
+
comp_id = Digest::SHA1.new.update(@identity['id']).hexdigest
|
48
|
+
expect(@identity.id_number).to eq comp_id
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#save" do
|
54
|
+
|
55
|
+
it "should save the identity in an encrypted file" do
|
56
|
+
file = @identity.save
|
57
|
+
expect(File.basename(file)).to eq @identity.id_number + '.enc'
|
58
|
+
expect(File.exist? file).to be_truthy
|
59
|
+
expect(File.read file).to_not match(/Authorization/)
|
60
|
+
expect(File.exist? File.join(NOMS::Command::Auth::Identity.identity_dir, '.noms-vault-key')).to be_truthy
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should save unencrypted when directed" do
|
64
|
+
file = @identity.save :encrypt => false
|
65
|
+
expect(File.basename(file)).to eq @identity.id_number + '.json'
|
66
|
+
expect(File.read file).to match(/Authorization/)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should save in specific file when asked" do
|
70
|
+
file = @identity.save :file => 'test/identities/test-identity.json', :encrypt => false
|
71
|
+
expect(File.basename(file)).to eq 'test-identity.json'
|
72
|
+
expect(File.read file).to match(/Authorization/)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should not save when from specified file" do
|
76
|
+
this_identity = NOMS::Command::Auth::Identity.from 'test/identity'
|
77
|
+
file = this_identity.save :file => 'test/identities/test-identity.enc'
|
78
|
+
expect(file).to eq 'test/identity'
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '.saved' do
|
84
|
+
|
85
|
+
it 'should be false when not saved' do
|
86
|
+
expect(NOMS::Command::Auth::Identity.saved @identity['id']).to be_falsey
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should be true when saved' do
|
90
|
+
file = @identity.save
|
91
|
+
expect(NOMS::Command::Auth::Identity.saved @identity['id']).to be_truthy
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should be false when not decryptable' do
|
95
|
+
file = @identity.save
|
96
|
+
expect(NOMS::Command::Auth::Identity.saved @identity['id']).to be_truthy
|
97
|
+
|
98
|
+
File.unlink NOMS::Command::Auth::Identity.vault_keyfile
|
99
|
+
expect(NOMS::Command::Auth::Identity.saved @identity['id']).to be_falsey
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should be false when too old' do
|
103
|
+
file = @identity.save
|
104
|
+
expect(NOMS::Command::Auth::Identity.saved @identity['id']).to be_truthy
|
105
|
+
old_hash = Digest::SHA1.new.update(File.read @vault_keyfile).hexdigest
|
106
|
+
|
107
|
+
old_ts = Time.now - 24 * 3600
|
108
|
+
File.utime(old_ts, old_ts, @vault_keyfile)
|
109
|
+
expect(NOMS::Command::Auth::Identity.saved @identity['id']).to be_falsey
|
110
|
+
new_hash = Digest::SHA1.new.update(File.read @vault_keyfile).hexdigest
|
111
|
+
expect(new_hash).to_not eq old_hash
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should load an identity' do
|
115
|
+
file = @identity.save
|
116
|
+
|
117
|
+
id_data = NOMS::Command::Auth::Identity.saved @identity['id']
|
118
|
+
expect(id_data['id']).to eq @identity['id']
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should update vault key mtime' do
|
122
|
+
file = @identity.save
|
123
|
+
|
124
|
+
old_ts = Time.now - (5 * 60)
|
125
|
+
File.utime(old_ts, old_ts, @vault_keyfile)
|
126
|
+
|
127
|
+
id_data = NOMS::Command::Auth::Identity.saved @identity['id']
|
128
|
+
new_identity = NOMS::Command::Auth::Identity.new(id_data)
|
129
|
+
new_ts = File.stat(@vault_keyfile).mtime
|
130
|
+
expect(Time.now - new_ts).to be < 5
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
5
137
|
describe "NOMS::Command::Application" do
|
6
138
|
|
7
139
|
before(:all) do
|
@@ -0,0 +1,160 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'noms/command/useragent'
|
6
|
+
|
7
|
+
describe 'NOMS::Command::UserAgent' do
|
8
|
+
before(:all) do
|
9
|
+
setup_fixture
|
10
|
+
start_server
|
11
|
+
end
|
12
|
+
|
13
|
+
after(:all) do
|
14
|
+
stop_server
|
15
|
+
teardown_fixture
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when caching' do
|
19
|
+
|
20
|
+
describe '.request' do
|
21
|
+
|
22
|
+
before(:each) do
|
23
|
+
@ua = NOMS::Command::UserAgent.new 'http://localhost:8787/', :max_age => 10, :cache => true
|
24
|
+
@ua.clear_cache!
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'loads fresh content' do
|
28
|
+
response, = @ua.request('GET', 'http://localhost:8787/static/expires-4')
|
29
|
+
expect(response.from_cache?).to be_falsey
|
30
|
+
generated = get_generated(response)
|
31
|
+
expect(Time.now - generated).to be <= 1
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'loads cached content' do
|
35
|
+
response0, = @ua.request('GET', 'http://localhost:8787/static/expires-4')
|
36
|
+
expect(response0.from_cache?).to be_falsey
|
37
|
+
generated0 = get_generated response0
|
38
|
+
|
39
|
+
sleep 2
|
40
|
+
response1, = @ua.request('GET', 'http://localhost:8787/static/expires-4')
|
41
|
+
expect(response1.from_cache?).to be_truthy
|
42
|
+
generated1 = get_generated response1
|
43
|
+
|
44
|
+
expect(generated1).to eq generated0
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'refetches cached content' do
|
48
|
+
response0, = @ua.request('GET', 'http://localhost:8787/static/expires-4')
|
49
|
+
expect(response0.from_cache?).to be_falsey
|
50
|
+
generated0 = get_generated response0
|
51
|
+
|
52
|
+
sleep 5
|
53
|
+
response1, = @ua.request('GET', 'http://localhost:8787/static/expires-4')
|
54
|
+
expect(response1.from_cache?).to be_falsey
|
55
|
+
generated1 = get_generated response1
|
56
|
+
|
57
|
+
expect(Time.now - generated1).to be <= 1
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'revalidates cached content with last-modified' do
|
61
|
+
response0, = @ua.request('GET', 'http://localhost:8787/static/last-modified')
|
62
|
+
expect(response0.from_cache?).to be_falsey
|
63
|
+
generated0 = get_generated response0
|
64
|
+
|
65
|
+
sleep 5
|
66
|
+
response1, = @ua.request('GET', 'http://localhost:8787/static/last-modified')
|
67
|
+
expect(response1.from_cache?).to be_truthy
|
68
|
+
generated1 = get_generated response1
|
69
|
+
|
70
|
+
expect(generated1).to eq generated0
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'revalidates cached content' do
|
74
|
+
response0, = @ua.request('GET', 'http://localhost:8787/static/expires-2-constant')
|
75
|
+
expect(response0.from_cache?).to be_falsey
|
76
|
+
generated0 = get_generated response0
|
77
|
+
|
78
|
+
sleep 3
|
79
|
+
response1, = @ua.request('GET', 'http://localhost:8787/static/expires-2-constant')
|
80
|
+
expect(response1.from_cache?).to be_truthy
|
81
|
+
generated1 = get_generated response1
|
82
|
+
|
83
|
+
expect(generated1).to eq generated0
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'refuses to cache longer than :max_age' do
|
87
|
+
response0, = @ua.request('GET', 'http://localhost:8787/static/long-cache')
|
88
|
+
expect(response0.from_cache?).to be_falsey
|
89
|
+
generated0 = get_generated response0
|
90
|
+
|
91
|
+
sleep 5
|
92
|
+
response1, = @ua.request('GET', 'http://localhost:8787/static/long-cache')
|
93
|
+
expect(response1.from_cache?).to be_truthy
|
94
|
+
generated1 = get_generated response1
|
95
|
+
|
96
|
+
expect(generated1).to eq generated0
|
97
|
+
|
98
|
+
sleep 6
|
99
|
+
response2, = @ua.request('GET', 'http://localhost:8787/static/long-cache')
|
100
|
+
expect(response2.from_cache?).to be_falsey
|
101
|
+
generated2 = get_generated response2
|
102
|
+
|
103
|
+
expect(generated2 - Time.now).to be <= 1
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'refuses to cache when directed' do
|
107
|
+
ua = NOMS::Command::UserAgent.new 'http://localhost:8787/', :cache => false
|
108
|
+
response0, = ua.request('GET', 'http://localhost:8787/static/expires-4')
|
109
|
+
expect(response0.from_cache?).to be_falsey
|
110
|
+
generated0 = get_generated response0
|
111
|
+
|
112
|
+
sleep 2
|
113
|
+
response1, = ua.request('GET', 'http://localhost:8787/static/expires-4')
|
114
|
+
expect(response1.from_cache?).to be_falsey
|
115
|
+
generated1 = get_generated response1
|
116
|
+
|
117
|
+
expect(generated1).to_not eq generated0
|
118
|
+
expect(generated0 - Time.now).to be <= 1
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'does not serve cached reply for authenticated pages, when unauthenticated' do
|
122
|
+
File.chmod 0600, 'test/identity'
|
123
|
+
File.open('test/fail-identity', 'w') do |outfh|
|
124
|
+
outfh.write JSON.pretty_generate(File.open('test/identity') { |fh| JSON.load(fh) }.merge({ 'password' => 'FAIL' }))
|
125
|
+
end
|
126
|
+
File.chmod 0600, 'test/fail-identity'
|
127
|
+
|
128
|
+
ua = NOMS::Command::UserAgent.new 'http://localhost:8787/', :cache => true,
|
129
|
+
:specified_identities => ['test/identity']
|
130
|
+
|
131
|
+
response0, = ua.request('GET', 'http://localhost:8787/auth/cacheable')
|
132
|
+
expect(response0.from_cache?).to be_falsey
|
133
|
+
generated0 = get_generated response0
|
134
|
+
|
135
|
+
ua = NOMS::Command::UserAgent.new 'http://localhost:8787/', :cache => true,
|
136
|
+
:specified_identities => ['test/fail-identity']
|
137
|
+
response1, = ua.request('GET', 'http://localhost:8787/auth/cacheable')
|
138
|
+
expect(response1.from_cache?).to be_falsey
|
139
|
+
expect(response1.status).to eq 401
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'does serve cached reply for authenticated pages, when authenticated' do
|
143
|
+
File.chmod 0600, 'test/identity'
|
144
|
+
ua = NOMS::Command::UserAgent.new 'http://localhost:8787/', :cache => true,
|
145
|
+
:specified_identities => ['test/identity']
|
146
|
+
response0, = ua.request('GET', 'http://localhost:8787/auth/cacheable')
|
147
|
+
expect(response0.from_cache?).to be_falsey
|
148
|
+
generated0 = get_generated response0
|
149
|
+
|
150
|
+
sleep 3
|
151
|
+
response1, = ua.request('GET', 'http://localhost:8787/auth/cacheable')
|
152
|
+
expect(response1.from_cache?).to be_falsey
|
153
|
+
generated1 = get_generated response1
|
154
|
+
expect(Time.now - generated1).to be < 1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'noms/command/useragent'
|
6
|
+
|
7
|
+
describe NOMS::Command::UserAgent do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
setup_fixture
|
11
|
+
start_server
|
12
|
+
end
|
13
|
+
|
14
|
+
after(:all) do
|
15
|
+
teardown_fixture
|
16
|
+
stop_server
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when using cookies' do
|
20
|
+
|
21
|
+
describe '#request' do
|
22
|
+
|
23
|
+
before(:each) do
|
24
|
+
@cookie_jar = File.join(NOMS::Command.home, 'cookies.txt')
|
25
|
+
File.unlink @cookie_jar if File.exist? @cookie_jar
|
26
|
+
@ua = NOMS::Command::UserAgent.new 'http://localhost:8787/', :specified_identities => ['test/identity'],
|
27
|
+
:cache => false
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'gets cookie-authorized document' do
|
31
|
+
response, = @ua.get 'http://localhost:8787/cookie/home'
|
32
|
+
result = JSON.parse(response.body)
|
33
|
+
expect(result['cookie_user']).to eq 'testuser'
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'persists cookies between invocations' do
|
37
|
+
response0, = @ua.get 'http://localhost:8787/cookie/home'
|
38
|
+
result0 = JSON.parse(response0.body)
|
39
|
+
|
40
|
+
expect(File.exist? @cookie_jar).to be_truthy
|
41
|
+
|
42
|
+
ua = NOMS::Command::UserAgent.new 'http://localhost:8787/', :cache => false
|
43
|
+
response1, = ua.get 'http://localhost:8787/cookie/home'
|
44
|
+
result1 = JSON.parse(response1.body)
|
45
|
+
|
46
|
+
expect(result1).to have_key 'cookie_user'
|
47
|
+
expect(result1['cookie_user']).to eq 'testuser'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|