Skip to content

Commit e0108df

Browse files
authored
Create owasp-juice-shop.ts
1 parent 190a6a4 commit e0108df

File tree

1 file changed

+271
-0
lines changed

1 file changed

+271
-0
lines changed

jwt/owasp-juice-shop.ts

+271
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
// https://github.com/juice-shop/juice-shop/blob/master/test/api/userApiSpec.ts
2+
3+
/*
4+
* Copyright (c) 2014-2021 Bjoern Kimminich & the OWASP Juice Shop contributors.
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
import frisby = require('frisby')
9+
const Joi = frisby.Joi
10+
const utils = require('../../lib/utils')
11+
const security = require('../../lib/insecurity')
12+
13+
const API_URL = 'http://localhost:3000/api'
14+
const REST_URL = 'http://localhost:3000/rest'
15+
16+
const authHeader = { Authorization: `Bearer ${security.authorize()}`, 'content-type': 'application/json' }
17+
const jsonHeader = { 'content-type': 'application/json' }
18+
19+
describe('/api/Users', () => {
20+
it('GET all users is forbidden via public API', () => {
21+
return frisby.get(`${API_URL}/Users`)
22+
.expect('status', 401)
23+
})
24+
25+
it('GET all users', () => {
26+
return frisby.get(`${API_URL}/Users`, { headers: authHeader })
27+
.expect('status', 200)
28+
})
29+
30+
it('GET all users doesnt include passwords', () => {
31+
return frisby.get(`${API_URL}/Users`, { headers: authHeader })
32+
.expect('status', 200)
33+
.expect('jsonTypes', 'data.*', {
34+
password: Joi.any().forbidden()
35+
})
36+
})
37+
38+
it('POST new user', () => {
39+
return frisby.post(`${API_URL}/Users`, {
40+
headers: jsonHeader,
41+
body: {
42+
43+
password: 'hooooorst'
44+
}
45+
})
46+
.expect('status', 201)
47+
.expect('header', 'content-type', /application\/json/)
48+
.expect('jsonTypes', 'data', {
49+
id: Joi.number(),
50+
createdAt: Joi.string(),
51+
updatedAt: Joi.string(),
52+
password: Joi.any().forbidden()
53+
})
54+
})
55+
56+
it('POST new admin', () => {
57+
return frisby.post(`${API_URL}/Users`, {
58+
headers: jsonHeader,
59+
body: {
60+
61+
password: 'hooooorst',
62+
role: 'admin'
63+
}
64+
})
65+
.expect('status', 201)
66+
.expect('header', 'content-type', /application\/json/)
67+
.expect('jsonTypes', 'data', {
68+
id: Joi.number(),
69+
createdAt: Joi.string(),
70+
updatedAt: Joi.string(),
71+
password: Joi.any().forbidden()
72+
})
73+
.expect('json', 'data', {
74+
role: 'admin'
75+
})
76+
})
77+
78+
it('POST new deluxe user', () => {
79+
return frisby.post(`${API_URL}/Users`, {
80+
headers: jsonHeader,
81+
body: {
82+
83+
password: 'hooooorst',
84+
role: 'deluxe'
85+
}
86+
})
87+
.expect('status', 201)
88+
.expect('header', 'content-type', /application\/json/)
89+
.expect('jsonTypes', 'data', {
90+
id: Joi.number(),
91+
createdAt: Joi.string(),
92+
updatedAt: Joi.string(),
93+
password: Joi.any().forbidden()
94+
})
95+
.expect('json', 'data', {
96+
role: 'deluxe'
97+
})
98+
})
99+
100+
it('POST new accounting user', () => {
101+
return frisby.post(`${API_URL}/Users`, {
102+
headers: jsonHeader,
103+
body: {
104+
105+
password: 'hooooorst',
106+
role: 'accounting'
107+
}
108+
})
109+
.expect('status', 201)
110+
.expect('header', 'content-type', /application\/json/)
111+
.expect('jsonTypes', 'data', {
112+
id: Joi.number(),
113+
createdAt: Joi.string(),
114+
updatedAt: Joi.string(),
115+
password: Joi.any().forbidden()
116+
})
117+
.expect('json', 'data', {
118+
role: 'accounting'
119+
})
120+
})
121+
122+
it('POST user not belonging to customer, deluxe, accounting, admin is forbidden', () => {
123+
return frisby.post(`${API_URL}/Users`, {
124+
headers: jsonHeader,
125+
body: {
126+
127+
password: 'hooooorst',
128+
role: 'accountinguser'
129+
}
130+
})
131+
.expect('status', 400)
132+
.expect('header', 'content-type', /application\/json/)
133+
.then(({ json }) => {
134+
expect(json.message).toBe('Validation error: Validation isIn on role failed')
135+
expect(json.errors[0].field).toBe('role')
136+
expect(json.errors[0].message).toBe('Validation isIn on role failed')
137+
})
138+
})
139+
140+
if (!utils.disableOnContainerEnv()) {
141+
it('POST new user with XSS attack in email address', () => {
142+
return frisby.post(`${API_URL}/Users`, {
143+
headers: jsonHeader,
144+
body: {
145+
email: '<iframe src="javascript:alert(`xss`)">',
146+
password: 'does.not.matter'
147+
}
148+
})
149+
.expect('status', 201)
150+
.expect('header', 'content-type', /application\/json/)
151+
.expect('json', 'data', { email: '<iframe src="javascript:alert(`xss`)">' })
152+
})
153+
}
154+
})
155+
156+
describe('/api/Users/:id', () => {
157+
it('GET existing user by id is forbidden via public API', () => {
158+
return frisby.get(`${API_URL}/Users/1`)
159+
.expect('status', 401)
160+
})
161+
162+
it('PUT update existing user is forbidden via public API', () => {
163+
return frisby.put(`${API_URL}/Users/1`, {
164+
header: jsonHeader,
165+
body: { email: '[email protected]' }
166+
})
167+
.expect('status', 401)
168+
})
169+
170+
it('DELETE existing user is forbidden via public API', () => {
171+
return frisby.del(`${API_URL}/Users/1`)
172+
.expect('status', 401)
173+
})
174+
175+
it('GET existing user by id', () => {
176+
return frisby.get(`${API_URL}/Users/1`, { headers: authHeader })
177+
.expect('status', 200)
178+
})
179+
180+
it('PUT update existing user is forbidden via API even when authenticated', () => {
181+
return frisby.put(`${API_URL}/Users/1`, {
182+
headers: authHeader,
183+
body: { email: '[email protected]' }
184+
})
185+
.expect('status', 401)
186+
})
187+
188+
it('DELETE existing user is forbidden via API even when authenticated', () => {
189+
return frisby.del(`${API_URL}/Users/1`, { headers: authHeader })
190+
.expect('status', 401)
191+
})
192+
})
193+
194+
describe('/rest/user/authentication-details', () => {
195+
it('GET all users decorated with attribute for authentication token', () => {
196+
return frisby.get(`${REST_URL}/user/authentication-details`, { headers: authHeader })
197+
.expect('status', 200)
198+
.expect('jsonTypes', 'data.?', {
199+
token: Joi.string()
200+
})
201+
})
202+
203+
it('GET all users with password replaced by asterisks', () => {
204+
return frisby.get(`${REST_URL}/user/authentication-details`, { headers: authHeader })
205+
.expect('status', 200)
206+
.expect('json', 'data.?', {
207+
password: '********************************'
208+
})
209+
})
210+
})
211+
212+
describe('/rest/user/whoami', () => {
213+
it('GET own user id and email on who-am-i request', () => {
214+
return frisby.post(`${REST_URL}/user/login`, {
215+
headers: jsonHeader,
216+
body: {
217+
218+
password: 'bW9jLmxpYW1nQGhjaW5pbW1pay5ucmVvamI='
219+
}
220+
})
221+
.expect('status', 200)
222+
.then(({ json }) => {
223+
return frisby.get(`${REST_URL}/user/whoami`, { headers: { Cookie: `token=${json.authentication.token}` } })
224+
.expect('status', 200)
225+
.expect('header', 'content-type', /application\/json/)
226+
.expect('jsonTypes', 'user', {
227+
id: Joi.number(),
228+
email: Joi.string()
229+
})
230+
.expect('json', 'user', {
231+
232+
})
233+
})
234+
})
235+
236+
it('GET who-am-i request returns nothing on missing auth token', () => {
237+
return frisby.get(`${REST_URL}/user/whoami`)
238+
.expect('status', 200)
239+
.expect('header', 'content-type', /application\/json/)
240+
.expect('json', {
241+
user: {}
242+
})
243+
})
244+
245+
it('GET who-am-i request returns nothing on invalid auth token', () => {
246+
return frisby.get(`${REST_URL}/user/whoami`, { headers: { Authorization: 'Bearer InvalidAuthToken' } })
247+
.expect('status', 200)
248+
.expect('header', 'content-type', /application\/json/)
249+
.expect('json', {
250+
user: {}
251+
})
252+
})
253+
254+
it('GET who-am-i request returns nothing on broken auth token', () => {
255+
return frisby.get(`${REST_URL}/user/whoami`, { headers: { Authorization: 'BoarBeatsBear' } })
256+
.expect('status', 200)
257+
.expect('header', 'content-type', /application\/json/)
258+
.expect('json', {
259+
user: {}
260+
})
261+
})
262+
263+
it('GET who-am-i request returns nothing on expired auth token', () => {
264+
return frisby.get(`${REST_URL}/user/whoami`, { headers: { Authorization: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6MSwidXNlcm5hbWUiOiIiLCJlbWFpbCI6ImFkbWluQGp1aWNlLXNoLm9wIiwicGFzc3dvcmQiOiIwMTkyMDIzYTdiYmQ3MzI1MDUxNmYwNjlkZjE4YjUwMCIsInJvbGUiOiJhZG1pbiIsImxhc3RMb2dpbklwIjoiMC4wLjAuMCIsInByb2ZpbGVJbWFnZSI6ImRlZmF1bHQuc3ZnIiwidG90cFNlY3JldCI6IiIsImlzQWN0aXZlIjp0cnVlLCJjcmVhdGVkQXQiOiIyMDE5LTA4LTE5IDE1OjU2OjE1LjYyOSArMDA6MDAiLCJ1cGRhdGVkQXQiOiIyMDE5LTA4LTE5IDE1OjU2OjE1LjYyOSArMDA6MDAiLCJkZWxldGVkQXQiOm51bGx9LCJpYXQiOjE1NjYyMzAyMjQsImV4cCI6MTU2NjI0ODIyNH0.FL0kkcInY5sDMGKeLHfEOYDTQd3BjR6_mK7Tcm_RH6iCLotTSRRoRxHpLkbtIQKqBFIt14J4BpLapkzG7ppRWcEley5nego-4iFOmXQvCBz5ISS3HdtM0saJnOe0agyVUen3huFp4F2UCth_y2ScjMn_4AgW66cz8NSFPRVpC8g' } })
265+
.expect('status', 200)
266+
.expect('header', 'content-type', /application\/json/)
267+
.expect('json', {
268+
user: {}
269+
})
270+
})
271+
})

0 commit comments

Comments
 (0)