This repository has been archived by the owner on Aug 15, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
hooks.js
226 lines (195 loc) · 8.65 KB
/
hooks.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
var hooks = require('hooks');
var https = require('https');
var stash = {};
hooks.beforeAll(function (transactions, cb) {
if ( stash['apikey'] )
return cb(null);
function respondWith(message) {
console.log(message);
cb(message);
}
https.get({
hostname: require('url').parse(hooks.configuration.server).hostname,
port: 443,
path: '/api/authn/users/login',
auth: 'alice:secret'
}, function (res) {
if ( res.statusCode === 200 ) {
var apikey = '';
res.setEncoding('ascii');
res.on('data', function (chunk) {
apikey += chunk;
});
res.on('end', function (chunk) {
stash['apikey'] = apikey;
console.log("Stashed API key " + apikey);
cb(null);
});
}
else {
respondWith("HTTP error in /api/authn/users/login hook : " + res.statusCode);
}
}).on('error', function(e) {
respondWith("Got error: " + e.message);
});
});
// Run these before every transaction
hooks.beforeEach(function(transaction) {
// Remove trailing newline character in plain text bodies
if (transaction.expected.headers['Content-Type'] !== 'application/json') {
transaction.expected.body = trim(transaction.expected.body);
}
if (transaction.request.headers['Content-Type'] === 'text/plain') {
transaction.request.body = trim(transaction.request.body);
}
// Strip the trailing slashes off request paths, they're sometimes added for formatting
var fullPath = transaction.fullPath;
if (fullPath[fullPath.length - 1] === '/') {
transaction.fullPath = fullPath.substring(0, fullPath.length - 1);
}
});
hooks.beforeEachValidation(function(transaction) {
// Trim response bodies, role#create returns ' ' as text/html ...wtf?
transaction.real.body = trim(transaction.real.body);
// utf-8 is redundant for application/json
if ((transaction.real.headers['content-type']||"").match('application/json; charset=utf-8') ) {
transaction.real.headers['content-type'] = 'application/json';
}
});
//The API key is randomly generated, so only check for 200 on route
[ "Authentication > Login > Exchange a user login and password for an API key",
"Authentication > Rotate API Key > Rotate your own API key",
"Authentication > Rotate API Key > Rotate another user's API key"].forEach(function(name) {
hooks.beforeValidation(name, function(transaction) {
if (transaction.real.statusCode == '200') {
transaction.expected.body = transaction.real.body;
}
});
});
//The API key is randomly generated, so only check for 200 on route
hooks.beforeValidation("Authentication > Rotate API Key", function(transaction) {
if (transaction.real.statusCode == '200') {
transaction.expected.body = transaction.real.body;
}
});
// Retrieve the stashed API key and use it to obtain an API token
hooks.before("Authentication > Authenticate > Exchange a user login and API key for an access token", function(transaction) {
transaction.request.body = stash['apikey'];
});
// Stash the API token for future requests
hooks.after("Authentication > Authenticate > Exchange a user login and API key for an access token", function(transaction) {
stash['apitoken'] = new Buffer(trim(transaction.real.body)).toString('base64');
});
// Retrieve the stashed API token and use it in all future requests
hooks.beforeEach(function(transaction) {
if (stash['apitoken'] != undefined) {
if ( transaction.request['headers']['Authorization'] && !transaction.request['headers']['Authorization'].match(/^Basic/) ) {
transaction.request['headers']['Authorization'] = 'Token token="' + stash['apitoken'] + '"';
}
}
});
// If we get a already-created conflict, continue on
hooks.beforeEachValidation(function(transaction) {
if (transaction.real.statusCode === 409) {
console.log('Skipping create, object already created');
transaction.real.statusCode = transaction.expected.statusCode;
transaction.real.body = transaction.expected.body;
}
});
// Don't actually update the user's password, set it to the same value so tests can be re-run
hooks.before("Authentication > Update Password > Change your password", function(transaction) {
transaction.request.body = '9p8nfsdafbp';
});
// use localhost to check remote_health
hooks.before("Utilities > Remote Health > Perform a health check on a remote Conjur server", function (transaction) {
var url = transaction.fullPath;
transaction.fullPath = url.replace('conjur-master01.myorg.com', 'localhost');
});
// Stash the host-factory tokens on creation, so use them later
hooks.after('Host Factory > Create Token > Create a new host factory token', function(transaction) {
console.log(transaction.real.body);
var body = JSON.parse(transaction.real.body);
stash['hftoken1'] = body[0]['token'];
stash['hftoken2'] = body[1]['token'];
});
// Modify the token ID in the path of these routes to use the first HF token stashed during creation
[
"Host Factory > Show Token > Retrieve a host factory token's metadata",
'Host Factory > Revoke Token > Revoke a host factory token'
].forEach(function(route) {
hooks.before(route, function(transaction) {
var uriTokens = transaction.fullPath.split('/');
uriTokens[uriTokens.length -1] = stash['hftoken1'];
transaction.fullPath = uriTokens.join('/');
});
});
// Use a stashed host-factory token to create a host
hooks.before('Host Factory > Create Host > Create a new host using a host factory token', function(transaction) {
transaction.request['headers']['Authorization'] = 'Token token="' + stash['hftoken2'] + '"';
});
/*
Work-arounds for 'misbehaving' routes
*/
// API Blueprint doesn't like the brackets in the layers[] param, so they're encoded in the doc
// Unencode them before sending the request to Conjur
hooks.before('Host Factory > Create > Create a new host factory', function(transaction) {
transaction.fullPath = transaction.fullPath.replace('%5B%5D', '[]');
});
// role#create throws "405 Method not allowed" on duplicate insert, catch this and ignore it
hooks.beforeValidation('Role > Create > Create a new role', function(transaction) {
if (transaction.real.statusCode === 405) {
console.log('Skipping create, object already created');
transaction.real.statusCode = transaction.expected.statusCode;
transaction.real.body = transaction.expected.body;
}
});
// These routes throws "422 UnprocessableEntity" on duplicate insert, catch this and ignore it
[
'Resource > Create > Create a new resource',
'Host Factory > Create > Create a new host factory',
'Host Factory > Create Host > Create a new host using a host factory token'
].forEach(function(route) {
hooks.beforeValidation(route, function(transaction) {
if (transaction.real.statusCode === 422) {
console.log('Skipping create, object already created');
transaction.real.statusCode = transaction.expected.statusCode;
transaction.real.body = transaction.expected.body;
}
});
});
// Audit events don't have a standard format, so JSON validation doesn't work
['All > Fetch all audit events', 'Single > Fetch audit events for a single role/resource'].forEach(function(transactionName) {
hooks.beforeValidation('Audit > ' + transactionName, function(transaction) {
if (transaction.real.statusCode === 200) {
console.log('Skipping body comparison');
transaction.real.body = transaction.expected.body;
}
});
});
// This test expectation is fragile because new members of security_admin appear in the result list
hooks.beforeValidation('Resource > Permitted Roles > List roles that have a permission on a resource', function(transaction) {
if (transaction.real.statusCode === 200) {
console.log('Skipping body comparison');
transaction.real.body = transaction.expected.body;
}
});
// Role exists returns 200 with no body (as is expected with HEAD) but apiary fails parsing a body that doesn't exist
// because it sees the Content-type is set to json - change it to text/plain
hooks.beforeValidation('Role > Exists > Determine whether a role exists', function(transaction) {
if (transaction.real.statusCode === 200) {
console.log('Skipping header checks');
transaction.real.headers = transaction.expected.headers;
transaction.real.body = transaction.expected.body;
}
});
// Can't fetch LDAP Sync policy without an LDAP server
hooks.before('LDAP Sync > Policy > Retrieve the policy to synchronize LDAP with Conjur', function(transaction) {
transaction.skip = true;
});
/*
Helper functions
*/
// Trims newlines from a string
function trim(input) {
return input.replace(/^\s+|\s+$/g, "");
}