fastapi

Форк
0
/
test_security_oauth2.py 
328 строк · 11.1 Кб
1
from dirty_equals import IsDict
2
from fastapi import Depends, FastAPI, Security
3
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
4
from fastapi.testclient import TestClient
5
from fastapi.utils import match_pydantic_error_url
6
from pydantic import BaseModel
7

8
app = FastAPI()
9

10
reusable_oauth2 = OAuth2(
11
    flows={
12
        "password": {
13
            "tokenUrl": "token",
14
            "scopes": {"read:users": "Read the users", "write:users": "Create users"},
15
        }
16
    }
17
)
18

19

20
class User(BaseModel):
21
    username: str
22

23

24
# Here we use string annotations to test them
25
def get_current_user(oauth_header: "str" = Security(reusable_oauth2)):
26
    user = User(username=oauth_header)
27
    return user
28

29

30
@app.post("/login")
31
# Here we use string annotations to test them
32
def login(form_data: "OAuth2PasswordRequestFormStrict" = Depends()):
33
    return form_data
34

35

36
@app.get("/users/me")
37
# Here we use string annotations to test them
38
def read_current_user(current_user: "User" = Depends(get_current_user)):
39
    return current_user
40

41

42
client = TestClient(app)
43

44

45
def test_security_oauth2():
46
    response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"})
47
    assert response.status_code == 200, response.text
48
    assert response.json() == {"username": "Bearer footokenbar"}
49

50

51
def test_security_oauth2_password_other_header():
52
    response = client.get("/users/me", headers={"Authorization": "Other footokenbar"})
53
    assert response.status_code == 200, response.text
54
    assert response.json() == {"username": "Other footokenbar"}
55

56

57
def test_security_oauth2_password_bearer_no_header():
58
    response = client.get("/users/me")
59
    assert response.status_code == 403, response.text
60
    assert response.json() == {"detail": "Not authenticated"}
61

62

63
def test_strict_login_no_data():
64
    response = client.post("/login")
65
    assert response.status_code == 422
66
    assert response.json() == IsDict(
67
        {
68
            "detail": [
69
                {
70
                    "type": "missing",
71
                    "loc": ["body", "grant_type"],
72
                    "msg": "Field required",
73
                    "input": None,
74
                    "url": match_pydantic_error_url("missing"),
75
                },
76
                {
77
                    "type": "missing",
78
                    "loc": ["body", "username"],
79
                    "msg": "Field required",
80
                    "input": None,
81
                    "url": match_pydantic_error_url("missing"),
82
                },
83
                {
84
                    "type": "missing",
85
                    "loc": ["body", "password"],
86
                    "msg": "Field required",
87
                    "input": None,
88
                    "url": match_pydantic_error_url("missing"),
89
                },
90
            ]
91
        }
92
    ) | IsDict(
93
        # TODO: remove when deprecating Pydantic v1
94
        {
95
            "detail": [
96
                {
97
                    "loc": ["body", "grant_type"],
98
                    "msg": "field required",
99
                    "type": "value_error.missing",
100
                },
101
                {
102
                    "loc": ["body", "username"],
103
                    "msg": "field required",
104
                    "type": "value_error.missing",
105
                },
106
                {
107
                    "loc": ["body", "password"],
108
                    "msg": "field required",
109
                    "type": "value_error.missing",
110
                },
111
            ]
112
        }
113
    )
114

115

116
def test_strict_login_no_grant_type():
117
    response = client.post("/login", data={"username": "johndoe", "password": "secret"})
118
    assert response.status_code == 422
119
    assert response.json() == IsDict(
120
        {
121
            "detail": [
122
                {
123
                    "type": "missing",
124
                    "loc": ["body", "grant_type"],
125
                    "msg": "Field required",
126
                    "input": None,
127
                    "url": match_pydantic_error_url("missing"),
128
                }
129
            ]
130
        }
131
    ) | IsDict(
132
        # TODO: remove when deprecating Pydantic v1
133
        {
134
            "detail": [
135
                {
136
                    "loc": ["body", "grant_type"],
137
                    "msg": "field required",
138
                    "type": "value_error.missing",
139
                }
140
            ]
141
        }
142
    )
143

144

145
def test_strict_login_incorrect_grant_type():
146
    response = client.post(
147
        "/login",
148
        data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
149
    )
150
    assert response.status_code == 422
151
    assert response.json() == IsDict(
152
        {
153
            "detail": [
154
                {
155
                    "type": "string_pattern_mismatch",
156
                    "loc": ["body", "grant_type"],
157
                    "msg": "String should match pattern 'password'",
158
                    "input": "incorrect",
159
                    "ctx": {"pattern": "password"},
160
                    "url": match_pydantic_error_url("string_pattern_mismatch"),
161
                }
162
            ]
163
        }
164
    ) | IsDict(
165
        # TODO: remove when deprecating Pydantic v1
166
        {
167
            "detail": [
168
                {
169
                    "loc": ["body", "grant_type"],
170
                    "msg": 'string does not match regex "password"',
171
                    "type": "value_error.str.regex",
172
                    "ctx": {"pattern": "password"},
173
                }
174
            ]
175
        }
176
    )
177

178

179
def test_strict_login_correct_grant_type():
180
    response = client.post(
181
        "/login",
182
        data={"username": "johndoe", "password": "secret", "grant_type": "password"},
183
    )
184
    assert response.status_code == 200
185
    assert response.json() == {
186
        "grant_type": "password",
187
        "username": "johndoe",
188
        "password": "secret",
189
        "scopes": [],
190
        "client_id": None,
191
        "client_secret": None,
192
    }
193

194

195
def test_openapi_schema():
196
    response = client.get("/openapi.json")
197
    assert response.status_code == 200, response.text
198
    assert response.json() == {
199
        "openapi": "3.1.0",
200
        "info": {"title": "FastAPI", "version": "0.1.0"},
201
        "paths": {
202
            "/login": {
203
                "post": {
204
                    "responses": {
205
                        "200": {
206
                            "description": "Successful Response",
207
                            "content": {"application/json": {"schema": {}}},
208
                        },
209
                        "422": {
210
                            "description": "Validation Error",
211
                            "content": {
212
                                "application/json": {
213
                                    "schema": {
214
                                        "$ref": "#/components/schemas/HTTPValidationError"
215
                                    }
216
                                }
217
                            },
218
                        },
219
                    },
220
                    "summary": "Login",
221
                    "operationId": "login_login_post",
222
                    "requestBody": {
223
                        "content": {
224
                            "application/x-www-form-urlencoded": {
225
                                "schema": {
226
                                    "$ref": "#/components/schemas/Body_login_login_post"
227
                                }
228
                            }
229
                        },
230
                        "required": True,
231
                    },
232
                }
233
            },
234
            "/users/me": {
235
                "get": {
236
                    "responses": {
237
                        "200": {
238
                            "description": "Successful Response",
239
                            "content": {"application/json": {"schema": {}}},
240
                        }
241
                    },
242
                    "summary": "Read Current User",
243
                    "operationId": "read_current_user_users_me_get",
244
                    "security": [{"OAuth2": []}],
245
                }
246
            },
247
        },
248
        "components": {
249
            "schemas": {
250
                "Body_login_login_post": {
251
                    "title": "Body_login_login_post",
252
                    "required": ["grant_type", "username", "password"],
253
                    "type": "object",
254
                    "properties": {
255
                        "grant_type": {
256
                            "title": "Grant Type",
257
                            "pattern": "password",
258
                            "type": "string",
259
                        },
260
                        "username": {"title": "Username", "type": "string"},
261
                        "password": {"title": "Password", "type": "string"},
262
                        "scope": {"title": "Scope", "type": "string", "default": ""},
263
                        "client_id": IsDict(
264
                            {
265
                                "title": "Client Id",
266
                                "anyOf": [{"type": "string"}, {"type": "null"}],
267
                            }
268
                        )
269
                        | IsDict(
270
                            # TODO: remove when deprecating Pydantic v1
271
                            {"title": "Client Id", "type": "string"}
272
                        ),
273
                        "client_secret": IsDict(
274
                            {
275
                                "title": "Client Secret",
276
                                "anyOf": [{"type": "string"}, {"type": "null"}],
277
                            }
278
                        )
279
                        | IsDict(
280
                            # TODO: remove when deprecating Pydantic v1
281
                            {"title": "Client Secret", "type": "string"}
282
                        ),
283
                    },
284
                },
285
                "ValidationError": {
286
                    "title": "ValidationError",
287
                    "required": ["loc", "msg", "type"],
288
                    "type": "object",
289
                    "properties": {
290
                        "loc": {
291
                            "title": "Location",
292
                            "type": "array",
293
                            "items": {
294
                                "anyOf": [{"type": "string"}, {"type": "integer"}]
295
                            },
296
                        },
297
                        "msg": {"title": "Message", "type": "string"},
298
                        "type": {"title": "Error Type", "type": "string"},
299
                    },
300
                },
301
                "HTTPValidationError": {
302
                    "title": "HTTPValidationError",
303
                    "type": "object",
304
                    "properties": {
305
                        "detail": {
306
                            "title": "Detail",
307
                            "type": "array",
308
                            "items": {"$ref": "#/components/schemas/ValidationError"},
309
                        }
310
                    },
311
                },
312
            },
313
            "securitySchemes": {
314
                "OAuth2": {
315
                    "type": "oauth2",
316
                    "flows": {
317
                        "password": {
318
                            "scopes": {
319
                                "read:users": "Read the users",
320
                                "write:users": "Create users",
321
                            },
322
                            "tokenUrl": "token",
323
                        }
324
                    },
325
                }
326
            },
327
        },
328
    }
329

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.