Coverage for fastapi/security/http.py: 100%

88 statements  

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

1import binascii 1abcde

2from base64 import b64decode 1abcde

3from typing import Optional 1abcde

4 

5from fastapi.exceptions import HTTPException 1abcde

6from fastapi.openapi.models import HTTPBase as HTTPBaseModel 1abcde

7from fastapi.openapi.models import HTTPBearer as HTTPBearerModel 1abcde

8from fastapi.security.base import SecurityBase 1abcde

9from fastapi.security.utils import get_authorization_scheme_param 1abcde

10from pydantic import BaseModel 1abcde

11from starlette.requests import Request 1abcde

12from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN 1abcde

13from typing_extensions import Annotated, Doc 1abcde

14 

15 

16class HTTPBasicCredentials(BaseModel): 1abcde

17 """ 

18 The HTTP Basic credentials given as the result of using `HTTPBasic` in a 

19 dependency. 

20 

21 Read more about it in the 

22 [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/). 

23 """ 

24 

25 username: Annotated[str, Doc("The HTTP Basic username.")] 1abcde

26 password: Annotated[str, Doc("The HTTP Basic password.")] 1abcde

27 

28 

29class HTTPAuthorizationCredentials(BaseModel): 1abcde

30 """ 

31 The HTTP authorization credentials in the result of using `HTTPBearer` or 

32 `HTTPDigest` in a dependency. 

33 

34 The HTTP authorization header value is split by the first space. 

35 

36 The first part is the `scheme`, the second part is the `credentials`. 

37 

38 For example, in an HTTP Bearer token scheme, the client will send a header 

39 like: 

40 

41 ``` 

42 Authorization: Bearer deadbeef12346 

43 ``` 

44 

45 In this case: 

46 

47 * `scheme` will have the value `"Bearer"` 

48 * `credentials` will have the value `"deadbeef12346"` 

49 """ 

50 

51 scheme: Annotated[ 1abcde

52 str, 

53 Doc( 

54 """ 

55 The HTTP authorization scheme extracted from the header value. 

56 """ 

57 ), 

58 ] 

59 credentials: Annotated[ 1abcde

60 str, 

61 Doc( 

62 """ 

63 The HTTP authorization credentials extracted from the header value. 

64 """ 

65 ), 

66 ] 

67 

68 

69class HTTPBase(SecurityBase): 1abcde

70 def __init__( 1abcde

71 self, 

72 *, 

73 scheme: str, 

74 scheme_name: Optional[str] = None, 

75 description: Optional[str] = None, 

76 auto_error: bool = True, 

77 ): 

78 self.model = HTTPBaseModel(scheme=scheme, description=description) 1abcde

79 self.scheme_name = scheme_name or self.__class__.__name__ 1abcde

80 self.auto_error = auto_error 1abcde

81 

82 async def __call__( 1abcde

83 self, request: Request 

84 ) -> Optional[HTTPAuthorizationCredentials]: 

85 authorization = request.headers.get("Authorization") 1abcde

86 scheme, credentials = get_authorization_scheme_param(authorization) 1abcde

87 if not (authorization and scheme and credentials): 1abcde

88 if self.auto_error: 1abcde

89 raise HTTPException( 1abcde

90 status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" 

91 ) 

92 else: 

93 return None 1abcde

94 return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) 1abcde

95 

96 

97class HTTPBasic(HTTPBase): 1abcde

98 """ 

99 HTTP Basic authentication. 

100 

101 ## Usage 

102 

103 Create an instance object and use that object as the dependency in `Depends()`. 

104 

105 The dependency result will be an `HTTPBasicCredentials` object containing the 

106 `username` and the `password`. 

107 

108 Read more about it in the 

109 [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/). 

110 

111 ## Example 

112 

113 ```python 

114 from typing import Annotated 

115 

116 from fastapi import Depends, FastAPI 

117 from fastapi.security import HTTPBasic, HTTPBasicCredentials 

118 

119 app = FastAPI() 

120 

121 security = HTTPBasic() 

122 

123 

124 @app.get("/users/me") 

125 def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]): 

126 return {"username": credentials.username, "password": credentials.password} 

127 ``` 

128 """ 

129 

130 def __init__( 1abcde

131 self, 

132 *, 

133 scheme_name: Annotated[ 

134 Optional[str], 

135 Doc( 

136 """ 

137 Security scheme name. 

138 

139 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

140 """ 

141 ), 

142 ] = None, 

143 realm: Annotated[ 

144 Optional[str], 

145 Doc( 

146 """ 

147 HTTP Basic authentication realm. 

148 """ 

149 ), 

150 ] = None, 

151 description: Annotated[ 

152 Optional[str], 

153 Doc( 

154 """ 

155 Security scheme description. 

156 

157 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

158 """ 

159 ), 

160 ] = None, 

161 auto_error: Annotated[ 

162 bool, 

163 Doc( 

164 """ 

165 By default, if the HTTP Basic authentication is not provided (a 

166 header), `HTTPBasic` will automatically cancel the request and send the 

167 client an error. 

168 

169 If `auto_error` is set to `False`, when the HTTP Basic authentication 

170 is not available, instead of erroring out, the dependency result will 

171 be `None`. 

172 

173 This is useful when you want to have optional authentication. 

174 

175 It is also useful when you want to have authentication that can be 

176 provided in one of multiple optional ways (for example, in HTTP Basic 

177 authentication or in an HTTP Bearer token). 

178 """ 

179 ), 

180 ] = True, 

181 ): 

182 self.model = HTTPBaseModel(scheme="basic", description=description) 1abcde

183 self.scheme_name = scheme_name or self.__class__.__name__ 1abcde

184 self.realm = realm 1abcde

185 self.auto_error = auto_error 1abcde

186 

187 async def __call__( # type: ignore 1abcde

188 self, request: Request 

189 ) -> Optional[HTTPBasicCredentials]: 

190 authorization = request.headers.get("Authorization") 1abcde

191 scheme, param = get_authorization_scheme_param(authorization) 1abcde

192 if self.realm: 1abcde

193 unauthorized_headers = {"WWW-Authenticate": f'Basic realm="{self.realm}"'} 1abcde

194 else: 

195 unauthorized_headers = {"WWW-Authenticate": "Basic"} 1abcde

196 if not authorization or scheme.lower() != "basic": 1abcde

197 if self.auto_error: 1abcde

198 raise HTTPException( 1abcde

199 status_code=HTTP_401_UNAUTHORIZED, 

200 detail="Not authenticated", 

201 headers=unauthorized_headers, 

202 ) 

203 else: 

204 return None 1abcde

205 invalid_user_credentials_exc = HTTPException( 1abcde

206 status_code=HTTP_401_UNAUTHORIZED, 

207 detail="Invalid authentication credentials", 

208 headers=unauthorized_headers, 

209 ) 

210 try: 1abcde

211 data = b64decode(param).decode("ascii") 1abcde

212 except (ValueError, UnicodeDecodeError, binascii.Error): 1abcde

213 raise invalid_user_credentials_exc # noqa: B904 1abcde

214 username, separator, password = data.partition(":") 1abcde

215 if not separator: 1abcde

216 raise invalid_user_credentials_exc 1abcde

217 return HTTPBasicCredentials(username=username, password=password) 1abcde

218 

219 

220class HTTPBearer(HTTPBase): 1abcde

221 """ 

222 HTTP Bearer token authentication. 

223 

224 ## Usage 

225 

226 Create an instance object and use that object as the dependency in `Depends()`. 

227 

228 The dependency result will be an `HTTPAuthorizationCredentials` object containing 

229 the `scheme` and the `credentials`. 

230 

231 ## Example 

232 

233 ```python 

234 from typing import Annotated 

235 

236 from fastapi import Depends, FastAPI 

237 from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer 

238 

239 app = FastAPI() 

240 

241 security = HTTPBearer() 

242 

243 

244 @app.get("/users/me") 

245 def read_current_user( 

246 credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)] 

247 ): 

248 return {"scheme": credentials.scheme, "credentials": credentials.credentials} 

249 ``` 

250 """ 

251 

252 def __init__( 1abcde

253 self, 

254 *, 

255 bearerFormat: Annotated[Optional[str], Doc("Bearer token format.")] = None, 

256 scheme_name: Annotated[ 

257 Optional[str], 

258 Doc( 

259 """ 

260 Security scheme name. 

261 

262 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

263 """ 

264 ), 

265 ] = None, 

266 description: Annotated[ 

267 Optional[str], 

268 Doc( 

269 """ 

270 Security scheme description. 

271 

272 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

273 """ 

274 ), 

275 ] = None, 

276 auto_error: Annotated[ 

277 bool, 

278 Doc( 

279 """ 

280 By default, if the HTTP Bearer token not provided (in an 

281 `Authorization` header), `HTTPBearer` will automatically cancel the 

282 request and send the client an error. 

283 

284 If `auto_error` is set to `False`, when the HTTP Bearer token 

285 is not available, instead of erroring out, the dependency result will 

286 be `None`. 

287 

288 This is useful when you want to have optional authentication. 

289 

290 It is also useful when you want to have authentication that can be 

291 provided in one of multiple optional ways (for example, in an HTTP 

292 Bearer token or in a cookie). 

293 """ 

294 ), 

295 ] = True, 

296 ): 

297 self.model = HTTPBearerModel(bearerFormat=bearerFormat, description=description) 1abcde

298 self.scheme_name = scheme_name or self.__class__.__name__ 1abcde

299 self.auto_error = auto_error 1abcde

300 

301 async def __call__( 1abcde

302 self, request: Request 

303 ) -> Optional[HTTPAuthorizationCredentials]: 

304 authorization = request.headers.get("Authorization") 1abcde

305 scheme, credentials = get_authorization_scheme_param(authorization) 1abcde

306 if not (authorization and scheme and credentials): 1abcde

307 if self.auto_error: 1abcde

308 raise HTTPException( 1abcde

309 status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" 

310 ) 

311 else: 

312 return None 1abcde

313 if scheme.lower() != "bearer": 1abcde

314 if self.auto_error: 1abcde

315 raise HTTPException( 1abcde

316 status_code=HTTP_403_FORBIDDEN, 

317 detail="Invalid authentication credentials", 

318 ) 

319 else: 

320 return None 1abcde

321 return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) 1abcde

322 

323 

324class HTTPDigest(HTTPBase): 1abcde

325 """ 

326 HTTP Digest authentication. 

327 

328 ## Usage 

329 

330 Create an instance object and use that object as the dependency in `Depends()`. 

331 

332 The dependency result will be an `HTTPAuthorizationCredentials` object containing 

333 the `scheme` and the `credentials`. 

334 

335 ## Example 

336 

337 ```python 

338 from typing import Annotated 

339 

340 from fastapi import Depends, FastAPI 

341 from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest 

342 

343 app = FastAPI() 

344 

345 security = HTTPDigest() 

346 

347 

348 @app.get("/users/me") 

349 def read_current_user( 

350 credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)] 

351 ): 

352 return {"scheme": credentials.scheme, "credentials": credentials.credentials} 

353 ``` 

354 """ 

355 

356 def __init__( 1abcde

357 self, 

358 *, 

359 scheme_name: Annotated[ 

360 Optional[str], 

361 Doc( 

362 """ 

363 Security scheme name. 

364 

365 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

366 """ 

367 ), 

368 ] = None, 

369 description: Annotated[ 

370 Optional[str], 

371 Doc( 

372 """ 

373 Security scheme description. 

374 

375 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

376 """ 

377 ), 

378 ] = None, 

379 auto_error: Annotated[ 

380 bool, 

381 Doc( 

382 """ 

383 By default, if the HTTP Digest not provided, `HTTPDigest` will 

384 automatically cancel the request and send the client an error. 

385 

386 If `auto_error` is set to `False`, when the HTTP Digest is not 

387 available, instead of erroring out, the dependency result will 

388 be `None`. 

389 

390 This is useful when you want to have optional authentication. 

391 

392 It is also useful when you want to have authentication that can be 

393 provided in one of multiple optional ways (for example, in HTTP 

394 Digest or in a cookie). 

395 """ 

396 ), 

397 ] = True, 

398 ): 

399 self.model = HTTPBaseModel(scheme="digest", description=description) 1abcde

400 self.scheme_name = scheme_name or self.__class__.__name__ 1abcde

401 self.auto_error = auto_error 1abcde

402 

403 async def __call__( 1abcde

404 self, request: Request 

405 ) -> Optional[HTTPAuthorizationCredentials]: 

406 authorization = request.headers.get("Authorization") 1abcde

407 scheme, credentials = get_authorization_scheme_param(authorization) 1abcde

408 if not (authorization and scheme and credentials): 1abcde

409 if self.auto_error: 1abcde

410 raise HTTPException( 1abcde

411 status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" 

412 ) 

413 else: 

414 return None 1abcde

415 if scheme.lower() != "digest": 1abcde

416 raise HTTPException( 1abcde

417 status_code=HTTP_403_FORBIDDEN, 

418 detail="Invalid authentication credentials", 

419 ) 

420 return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) 1abcde