plain.oauth 0.19.0__py3-none-any.whl → 0.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain/oauth/exceptions.py +7 -3
- plain/oauth/migrations/0005_alter_oauthconnection_unique_together_and_more.py +0 -1
- plain/oauth/providers.py +19 -5
- plain/oauth/templates/oauth/callback.html +8 -0
- plain/oauth/views.py +15 -28
- {plain_oauth-0.19.0.dist-info → plain_oauth-0.20.0.dist-info}/METADATA +1 -1
- {plain_oauth-0.19.0.dist-info → plain_oauth-0.20.0.dist-info}/RECORD +9 -9
- plain/oauth/templates/oauth/error.html +0 -6
- {plain_oauth-0.19.0.dist-info → plain_oauth-0.20.0.dist-info}/WHEEL +0 -0
- {plain_oauth-0.19.0.dist-info → plain_oauth-0.20.0.dist-info}/licenses/LICENSE +0 -0
plain/oauth/exceptions.py
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
class OAuthError(Exception):
|
2
2
|
"""Base class for OAuth errors"""
|
3
3
|
|
4
|
-
|
4
|
+
message = "An error occurred during the OAuth process."
|
5
|
+
|
6
|
+
|
7
|
+
class OAuthStateMissingError(OAuthError):
|
8
|
+
message = "The state parameter is missing. Please try again."
|
5
9
|
|
6
10
|
|
7
11
|
class OAuthStateMismatchError(OAuthError):
|
8
|
-
|
12
|
+
message = "The state parameter did not match. Please try again."
|
9
13
|
|
10
14
|
|
11
15
|
class OAuthUserAlreadyExistsError(OAuthError):
|
12
|
-
|
16
|
+
message = "A user already exists with this email address. Please log in first and then connect this OAuth provider to the existing account."
|
plain/oauth/providers.py
CHANGED
@@ -7,10 +7,11 @@ from plain.auth import login as auth_login
|
|
7
7
|
from plain.http import HttpRequest, Response, ResponseRedirect
|
8
8
|
from plain.runtime import settings
|
9
9
|
from plain.urls import reverse
|
10
|
+
from plain.utils.cache import add_never_cache_headers
|
10
11
|
from plain.utils.crypto import get_random_string
|
11
12
|
from plain.utils.module_loading import import_string
|
12
13
|
|
13
|
-
from .exceptions import OAuthError, OAuthStateMismatchError
|
14
|
+
from .exceptions import OAuthError, OAuthStateMismatchError, OAuthStateMissingError
|
14
15
|
from .models import OAuthConnection
|
15
16
|
|
16
17
|
SESSION_STATE_KEY = "plainoauth_state"
|
@@ -105,7 +106,11 @@ class OAuthProvider:
|
|
105
106
|
if error := request.query_params.get("error"):
|
106
107
|
raise OAuthError(error)
|
107
108
|
|
108
|
-
|
109
|
+
try:
|
110
|
+
state = request.query_params["state"]
|
111
|
+
except KeyError as e:
|
112
|
+
raise OAuthStateMissingError() from e
|
113
|
+
|
109
114
|
expected_state = request.session.pop(SESSION_STATE_KEY)
|
110
115
|
request.session.save() # Make sure the pop is saved (won't save on an exception)
|
111
116
|
if not secrets.compare_digest(state, expected_state):
|
@@ -130,7 +135,7 @@ class OAuthProvider:
|
|
130
135
|
# Sort authorization params for consistency
|
131
136
|
sorted_authorization_params = sorted(authorization_params.items())
|
132
137
|
redirect_url = authorization_url + "?" + urlencode(sorted_authorization_params)
|
133
|
-
return
|
138
|
+
return self.get_redirect_response(redirect_url)
|
134
139
|
|
135
140
|
def handle_connect_request(
|
136
141
|
self, *, request: HttpRequest, redirect_to: str = ""
|
@@ -144,7 +149,7 @@ class OAuthProvider:
|
|
144
149
|
)
|
145
150
|
connection.delete()
|
146
151
|
redirect_url = self.get_disconnect_redirect_url(request=request)
|
147
|
-
return
|
152
|
+
return self.get_redirect_response(redirect_url)
|
148
153
|
|
149
154
|
def handle_callback_request(self, *, request: HttpRequest) -> Response:
|
150
155
|
self.check_request_state(request=request)
|
@@ -174,7 +179,7 @@ class OAuthProvider:
|
|
174
179
|
self.login(request=request, user=user)
|
175
180
|
|
176
181
|
redirect_url = self.get_login_redirect_url(request=request)
|
177
|
-
return
|
182
|
+
return self.get_redirect_response(redirect_url)
|
178
183
|
|
179
184
|
def login(self, *, request: HttpRequest, user: Any) -> Response:
|
180
185
|
auth_login(request=request, user=user)
|
@@ -185,6 +190,15 @@ class OAuthProvider:
|
|
185
190
|
def get_disconnect_redirect_url(self, *, request: HttpRequest) -> str:
|
186
191
|
return request.data.get("next", "/")
|
187
192
|
|
193
|
+
def get_redirect_response(self, redirect_url: str) -> Response:
|
194
|
+
"""
|
195
|
+
Returns a redirect response to the given URL.
|
196
|
+
This is a utility method to ensure consistent redirect handling.
|
197
|
+
"""
|
198
|
+
response = ResponseRedirect(redirect_url)
|
199
|
+
add_never_cache_headers(response)
|
200
|
+
return response
|
201
|
+
|
188
202
|
|
189
203
|
def get_oauth_provider_instance(*, provider_key: str) -> OAuthProvider:
|
190
204
|
OAUTH_LOGIN_PROVIDERS = getattr(settings, "OAUTH_LOGIN_PROVIDERS", {})
|
plain/oauth/views.py
CHANGED
@@ -1,14 +1,11 @@
|
|
1
1
|
import logging
|
2
2
|
|
3
3
|
from plain.auth.views import AuthViewMixin
|
4
|
-
from plain.http import
|
5
|
-
from plain.
|
6
|
-
from plain.views import View
|
4
|
+
from plain.http import ResponseRedirect
|
5
|
+
from plain.views import TemplateView, View
|
7
6
|
|
8
7
|
from .exceptions import (
|
9
8
|
OAuthError,
|
10
|
-
OAuthStateMismatchError,
|
11
|
-
OAuthUserAlreadyExistsError,
|
12
9
|
)
|
13
10
|
from .providers import get_oauth_provider_instance
|
14
11
|
|
@@ -26,39 +23,29 @@ class OAuthLoginView(View):
|
|
26
23
|
return provider_instance.handle_login_request(request=request)
|
27
24
|
|
28
25
|
|
29
|
-
class OAuthCallbackView(
|
26
|
+
class OAuthCallbackView(TemplateView):
|
30
27
|
"""
|
31
28
|
The callback view is used for signup, login, and connect.
|
32
29
|
"""
|
33
30
|
|
31
|
+
template_name = "oauth/callback.html"
|
32
|
+
|
34
33
|
def get(self):
|
35
|
-
request = self.request
|
36
34
|
provider = self.url_kwargs["provider"]
|
37
35
|
provider_instance = get_oauth_provider_instance(provider_key=provider)
|
38
36
|
try:
|
39
|
-
return provider_instance.handle_callback_request(request=request)
|
40
|
-
except OAuthUserAlreadyExistsError:
|
41
|
-
template = Template("oauth/error.html")
|
42
|
-
return ResponseBadRequest(
|
43
|
-
template.render(
|
44
|
-
{
|
45
|
-
"oauth_error": "A user already exists with this email address. Please log in first and then connect this OAuth provider to the existing account."
|
46
|
-
}
|
47
|
-
)
|
48
|
-
)
|
49
|
-
except OAuthStateMismatchError:
|
50
|
-
template = Template("oauth/error.html")
|
51
|
-
return ResponseBadRequest(
|
52
|
-
template.render(
|
53
|
-
{
|
54
|
-
"oauth_error": "The state parameter did not match. Please try again."
|
55
|
-
}
|
56
|
-
)
|
57
|
-
)
|
37
|
+
return provider_instance.handle_callback_request(request=self.request)
|
58
38
|
except OAuthError as e:
|
59
39
|
logger.exception("OAuth error")
|
60
|
-
|
61
|
-
|
40
|
+
self.oauth_error = e
|
41
|
+
|
42
|
+
# Return a regular template response with the error
|
43
|
+
return 400, super().get()
|
44
|
+
|
45
|
+
def get_template_context(self) -> dict:
|
46
|
+
context = super().get_template_context()
|
47
|
+
context["oauth_error"] = getattr(self, "oauth_error", None)
|
48
|
+
return context
|
62
49
|
|
63
50
|
|
64
51
|
class OAuthConnectView(AuthViewMixin, View):
|
@@ -3,21 +3,21 @@ plain/oauth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
plain/oauth/admin.py,sha256=rqrGRRUVxOlG9wiuXtndIyQxd1VT67cvFy6qWo7LF-Q,1278
|
4
4
|
plain/oauth/config.py,sha256=0Q4IILBKQbIaxqeL9WRTH5Cka-BO3c3SOj1AdQIAJgc,167
|
5
5
|
plain/oauth/default_settings.py,sha256=dlN1J9vSOjjxPNLp-0qe-cLTqwM4E69ZAx_8lpxMhaM,28
|
6
|
-
plain/oauth/exceptions.py,sha256=
|
6
|
+
plain/oauth/exceptions.py,sha256=yoZsq8XgzstuwbE2ihoet0nzpw_sVZgDrwUauh6hhUs,546
|
7
7
|
plain/oauth/models.py,sha256=ceSw9AKZMXffE4ZKZLxIbOlSyndTCHL22GCuSF3Hpmk,6880
|
8
|
-
plain/oauth/providers.py,sha256=
|
8
|
+
plain/oauth/providers.py,sha256=F5w1ZE0iEF8QZT4cdO6L7v5p2zHxby5_n-f6J0MM12E,7729
|
9
9
|
plain/oauth/urls.py,sha256=FYzpQwhvZdcat8n3f7RyA-1Q21finKb8JEyakSOjXXg,696
|
10
|
-
plain/oauth/views.py,sha256=
|
10
|
+
plain/oauth/views.py,sha256=I27UHQ-MgWVFRoeQUIsZZ81u2V8JXGJ6R3m2TO1lc8U,2408
|
11
11
|
plain/oauth/migrations/0001_initial.py,sha256=B9Finbn7ijEIUbkDy_B7UsKQLfMWaXd0Kx3oZrUENWc,1753
|
12
12
|
plain/oauth/migrations/0002_alter_oauthconnection_options_and_more.py,sha256=3Mb0IU9KDRQfog0PjVbzuNv_AxCs7UVHnA0F263AKNo,581
|
13
13
|
plain/oauth/migrations/0003_alter_oauthconnection_access_token_and_more.py,sha256=FyLfwxc2pRzF-CbdRFQRRSQTOCxc9l1womgStygm_lo,629
|
14
14
|
plain/oauth/migrations/0004_alter_oauthconnection_access_token_and_more.py,sha256=ho9CG-lf7OVg1vBnjn7miihoioNmOIz7ObxB2QkPeSo,652
|
15
|
-
plain/oauth/migrations/0005_alter_oauthconnection_unique_together_and_more.py,sha256=
|
15
|
+
plain/oauth/migrations/0005_alter_oauthconnection_unique_together_and_more.py,sha256=MdTjX5Awff0QPesdAT5JuToitnbphgIh-nsJnZEAAa0,544
|
16
16
|
plain/oauth/migrations/0006_remove_oauthconnection_unique_oauth_provider_user_id_and_more.py,sha256=UjWRezZoAzjVS70oCRQ38k_w6YJtcd_uSYT4Kc3H1pg,713
|
17
17
|
plain/oauth/migrations/0007_alter_oauthconnection_provider_key_and_more.py,sha256=B_LW6xG1o_uA13tqUs0KniXl1JBNbQu4wMh2pW8rq5I,675
|
18
18
|
plain/oauth/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
-
plain/oauth/templates/oauth/
|
20
|
-
plain_oauth-0.
|
21
|
-
plain_oauth-0.
|
22
|
-
plain_oauth-0.
|
23
|
-
plain_oauth-0.
|
19
|
+
plain/oauth/templates/oauth/callback.html,sha256=4CJG0oAN0xYjw2IPkjaL7B4hwlf9um9LI4CTu50E-yE,173
|
20
|
+
plain_oauth-0.20.0.dist-info/METADATA,sha256=oBC0SBDaGuX3RV27Wfa-VHBymwrHRLqfjoImzOvKfIM,10304
|
21
|
+
plain_oauth-0.20.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
22
|
+
plain_oauth-0.20.0.dist-info/licenses/LICENSE,sha256=cvKM3OlqHx3ijD6e34zsSUkPvzl-ya3Dd63A6EHL94U,1500
|
23
|
+
plain_oauth-0.20.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|