Coverage for tests/test_security_oauth2.py: 100%

51 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-08 03:53 +0000

1from dirty_equals import IsDict 1abcde

2from fastapi import Depends, FastAPI, Security 1abcde

3from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict 1abcde

4from fastapi.testclient import TestClient 1abcde

5from pydantic import BaseModel 1abcde

6 

7app = FastAPI() 1abcde

8 

9reusable_oauth2 = OAuth2( 1abcde

10 flows={ 

11 "password": { 

12 "tokenUrl": "token", 

13 "scopes": {"read:users": "Read the users", "write:users": "Create users"}, 

14 } 

15 } 

16) 

17 

18 

19class User(BaseModel): 1abcde

20 username: str 1abcde

21 

22 

23# Here we use string annotations to test them 

24def get_current_user(oauth_header: "str" = Security(reusable_oauth2)): 1abcde

25 user = User(username=oauth_header) 1abcde

26 return user 1abcde

27 

28 

29@app.post("/login") 1abcde

30# Here we use string annotations to test them 

31def login(form_data: "OAuth2PasswordRequestFormStrict" = Depends()): 1abcde

32 return form_data 1abcde

33 

34 

35@app.get("/users/me") 1abcde

36# Here we use string annotations to test them 

37def read_current_user(current_user: "User" = Depends(get_current_user)): 1abcde

38 return current_user 1abcde

39 

40 

41client = TestClient(app) 1abcde

42 

43 

44def test_security_oauth2(): 1abcde

45 response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) 1abcde

46 assert response.status_code == 200, response.text 1abcde

47 assert response.json() == {"username": "Bearer footokenbar"} 1abcde

48 

49 

50def test_security_oauth2_password_other_header(): 1abcde

51 response = client.get("/users/me", headers={"Authorization": "Other footokenbar"}) 1abcde

52 assert response.status_code == 200, response.text 1abcde

53 assert response.json() == {"username": "Other footokenbar"} 1abcde

54 

55 

56def test_security_oauth2_password_bearer_no_header(): 1abcde

57 response = client.get("/users/me") 1abcde

58 assert response.status_code == 403, response.text 1abcde

59 assert response.json() == {"detail": "Not authenticated"} 1abcde

60 

61 

62def test_strict_login_no_data(): 1abcde

63 response = client.post("/login") 1abcde

64 assert response.status_code == 422 1abcde

65 assert response.json() == IsDict( 1abcde

66 { 

67 "detail": [ 

68 { 

69 "type": "missing", 

70 "loc": ["body", "grant_type"], 

71 "msg": "Field required", 

72 "input": None, 

73 }, 

74 { 

75 "type": "missing", 

76 "loc": ["body", "username"], 

77 "msg": "Field required", 

78 "input": None, 

79 }, 

80 { 

81 "type": "missing", 

82 "loc": ["body", "password"], 

83 "msg": "Field required", 

84 "input": None, 

85 }, 

86 ] 

87 } 

88 ) | IsDict( 

89 # TODO: remove when deprecating Pydantic v1 

90 { 

91 "detail": [ 

92 { 

93 "loc": ["body", "grant_type"], 

94 "msg": "field required", 

95 "type": "value_error.missing", 

96 }, 

97 { 

98 "loc": ["body", "username"], 

99 "msg": "field required", 

100 "type": "value_error.missing", 

101 }, 

102 { 

103 "loc": ["body", "password"], 

104 "msg": "field required", 

105 "type": "value_error.missing", 

106 }, 

107 ] 

108 } 

109 ) 

110 

111 

112def test_strict_login_no_grant_type(): 1abcde

113 response = client.post("/login", data={"username": "johndoe", "password": "secret"}) 1abcde

114 assert response.status_code == 422 1abcde

115 assert response.json() == IsDict( 1abcde

116 { 

117 "detail": [ 

118 { 

119 "type": "missing", 

120 "loc": ["body", "grant_type"], 

121 "msg": "Field required", 

122 "input": None, 

123 } 

124 ] 

125 } 

126 ) | IsDict( 

127 # TODO: remove when deprecating Pydantic v1 

128 { 

129 "detail": [ 

130 { 

131 "loc": ["body", "grant_type"], 

132 "msg": "field required", 

133 "type": "value_error.missing", 

134 } 

135 ] 

136 } 

137 ) 

138 

139 

140def test_strict_login_incorrect_grant_type(): 1abcde

141 response = client.post( 1abcde

142 "/login", 

143 data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"}, 

144 ) 

145 assert response.status_code == 422 1abcde

146 assert response.json() == IsDict( 1abcde

147 { 

148 "detail": [ 

149 { 

150 "type": "string_pattern_mismatch", 

151 "loc": ["body", "grant_type"], 

152 "msg": "String should match pattern 'password'", 

153 "input": "incorrect", 

154 "ctx": {"pattern": "password"}, 

155 } 

156 ] 

157 } 

158 ) | IsDict( 

159 # TODO: remove when deprecating Pydantic v1 

160 { 

161 "detail": [ 

162 { 

163 "loc": ["body", "grant_type"], 

164 "msg": 'string does not match regex "password"', 

165 "type": "value_error.str.regex", 

166 "ctx": {"pattern": "password"}, 

167 } 

168 ] 

169 } 

170 ) 

171 

172 

173def test_strict_login_correct_grant_type(): 1abcde

174 response = client.post( 1abcde

175 "/login", 

176 data={"username": "johndoe", "password": "secret", "grant_type": "password"}, 

177 ) 

178 assert response.status_code == 200 1abcde

179 assert response.json() == { 1abcde

180 "grant_type": "password", 

181 "username": "johndoe", 

182 "password": "secret", 

183 "scopes": [], 

184 "client_id": None, 

185 "client_secret": None, 

186 } 

187 

188 

189def test_openapi_schema(): 1abcde

190 response = client.get("/openapi.json") 1abcde

191 assert response.status_code == 200, response.text 1abcde

192 assert response.json() == { 1abcde

193 "openapi": "3.1.0", 

194 "info": {"title": "FastAPI", "version": "0.1.0"}, 

195 "paths": { 

196 "/login": { 

197 "post": { 

198 "responses": { 

199 "200": { 

200 "description": "Successful Response", 

201 "content": {"application/json": {"schema": {}}}, 

202 }, 

203 "422": { 

204 "description": "Validation Error", 

205 "content": { 

206 "application/json": { 

207 "schema": { 

208 "$ref": "#/components/schemas/HTTPValidationError" 

209 } 

210 } 

211 }, 

212 }, 

213 }, 

214 "summary": "Login", 

215 "operationId": "login_login_post", 

216 "requestBody": { 

217 "content": { 

218 "application/x-www-form-urlencoded": { 

219 "schema": { 

220 "$ref": "#/components/schemas/Body_login_login_post" 

221 } 

222 } 

223 }, 

224 "required": True, 

225 }, 

226 } 

227 }, 

228 "/users/me": { 

229 "get": { 

230 "responses": { 

231 "200": { 

232 "description": "Successful Response", 

233 "content": {"application/json": {"schema": {}}}, 

234 } 

235 }, 

236 "summary": "Read Current User", 

237 "operationId": "read_current_user_users_me_get", 

238 "security": [{"OAuth2": []}], 

239 } 

240 }, 

241 }, 

242 "components": { 

243 "schemas": { 

244 "Body_login_login_post": { 

245 "title": "Body_login_login_post", 

246 "required": ["grant_type", "username", "password"], 

247 "type": "object", 

248 "properties": { 

249 "grant_type": { 

250 "title": "Grant Type", 

251 "pattern": "password", 

252 "type": "string", 

253 }, 

254 "username": {"title": "Username", "type": "string"}, 

255 "password": {"title": "Password", "type": "string"}, 

256 "scope": {"title": "Scope", "type": "string", "default": ""}, 

257 "client_id": IsDict( 

258 { 

259 "title": "Client Id", 

260 "anyOf": [{"type": "string"}, {"type": "null"}], 

261 } 

262 ) 

263 | IsDict( 

264 # TODO: remove when deprecating Pydantic v1 

265 {"title": "Client Id", "type": "string"} 

266 ), 

267 "client_secret": IsDict( 

268 { 

269 "title": "Client Secret", 

270 "anyOf": [{"type": "string"}, {"type": "null"}], 

271 } 

272 ) 

273 | IsDict( 

274 # TODO: remove when deprecating Pydantic v1 

275 {"title": "Client Secret", "type": "string"} 

276 ), 

277 }, 

278 }, 

279 "ValidationError": { 

280 "title": "ValidationError", 

281 "required": ["loc", "msg", "type"], 

282 "type": "object", 

283 "properties": { 

284 "loc": { 

285 "title": "Location", 

286 "type": "array", 

287 "items": { 

288 "anyOf": [{"type": "string"}, {"type": "integer"}] 

289 }, 

290 }, 

291 "msg": {"title": "Message", "type": "string"}, 

292 "type": {"title": "Error Type", "type": "string"}, 

293 }, 

294 }, 

295 "HTTPValidationError": { 

296 "title": "HTTPValidationError", 

297 "type": "object", 

298 "properties": { 

299 "detail": { 

300 "title": "Detail", 

301 "type": "array", 

302 "items": {"$ref": "#/components/schemas/ValidationError"}, 

303 } 

304 }, 

305 }, 

306 }, 

307 "securitySchemes": { 

308 "OAuth2": { 

309 "type": "oauth2", 

310 "flows": { 

311 "password": { 

312 "scopes": { 

313 "read:users": "Read the users", 

314 "write:users": "Create users", 

315 }, 

316 "tokenUrl": "token", 

317 } 

318 }, 

319 } 

320 }, 

321 }, 

322 }