plain.oauth 0.27.0__tar.gz → 0.29.0__tar.gz
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-0.27.0 → plain_oauth-0.29.0}/PKG-INFO +1 -1
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/CHANGELOG.md +23 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/providers.py +28 -21
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/views.py +2 -1
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/pyproject.toml +1 -1
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/app/settings.py +0 -1
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/app/templates/index.html +2 -2
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/app/urls.py +1 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/.gitignore +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/LICENSE +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/README.md +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/README.md +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/__init__.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/admin.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/config.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/default_settings.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/exceptions.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/migrations/0001_initial.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/migrations/__init__.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/models.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/templates/oauth/callback.html +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/plain/oauth/urls.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/provider_examples/__init__.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/provider_examples/bitbucket.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/provider_examples/github.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/provider_examples/gitlab.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/app/templates/base.html +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/app/templates/login.html +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/app/users/migrations/0001_initial.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/app/users/migrations/__init__.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/app/users/models.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/provider_tests/__init__.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/provider_tests/test_github.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/providers/__init__.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/providers/bitbucket.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/providers/github.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/providers/gitlab.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/test_backends.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/test_checks.py +0 -0
- {plain_oauth-0.27.0 → plain_oauth-0.29.0}/tests/test_providers.py +0 -0
@@ -1,5 +1,28 @@
|
|
1
1
|
# plain-oauth changelog
|
2
2
|
|
3
|
+
## [0.29.0](https://github.com/dropseed/plain/releases/plain-oauth@0.29.0) (2025-10-02)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- Removed direct access to `request.user` and `request.session` attributes in favor of using `get_request_user()` and `get_request_session()` functions ([154ee10](https://github.com/dropseed/plain/commit/154ee10375))
|
8
|
+
- Removed dependency on `AuthenticationMiddleware` from test settings ([154ee10](https://github.com/dropseed/plain/commit/154ee10375))
|
9
|
+
|
10
|
+
### Upgrade instructions
|
11
|
+
|
12
|
+
- If you have custom OAuth providers or views that access `request.user`, update them to use `get_request_user(request)` from `plain.auth`
|
13
|
+
- If you have custom OAuth providers that access `request.session`, update them to use `get_request_session(request)` from `plain.sessions`
|
14
|
+
|
15
|
+
## [0.28.0](https://github.com/dropseed/plain/releases/plain-oauth@0.28.0) (2025-09-30)
|
16
|
+
|
17
|
+
### What's changed
|
18
|
+
|
19
|
+
- `HttpRequest` has been renamed to `Request` throughout the OAuth provider classes ([cd46ff2](https://github.com/dropseed/plain/commit/cd46ff2003))
|
20
|
+
|
21
|
+
### Upgrade instructions
|
22
|
+
|
23
|
+
- If you have custom OAuth providers that override methods like `get_authorization_url_params`, `get_oauth_token`, `get_callback_url`, or any other methods that accept a request parameter, update the type hint from `HttpRequest` to `Request`
|
24
|
+
- Update any imports of `HttpRequest` in custom OAuth provider code to import `Request` instead from `plain.http`
|
25
|
+
|
3
26
|
## [0.27.0](https://github.com/dropseed/plain/releases/plain-oauth@0.27.0) (2025-09-25)
|
4
27
|
|
5
28
|
### What's changed
|
@@ -4,8 +4,10 @@ from typing import Any
|
|
4
4
|
from urllib.parse import urlencode
|
5
5
|
|
6
6
|
from plain.auth import login as auth_login
|
7
|
-
from plain.
|
7
|
+
from plain.auth.requests import get_request_user
|
8
|
+
from plain.http import Request, Response, ResponseRedirect
|
8
9
|
from plain.runtime import settings
|
10
|
+
from plain.sessions import get_request_session
|
9
11
|
from plain.urls import reverse
|
10
12
|
from plain.utils.cache import add_never_cache_headers
|
11
13
|
from plain.utils.crypto import get_random_string
|
@@ -65,7 +67,7 @@ class OAuthProvider:
|
|
65
67
|
self.client_secret = client_secret
|
66
68
|
self.scope = scope
|
67
69
|
|
68
|
-
def get_authorization_url_params(self, *, request:
|
70
|
+
def get_authorization_url_params(self, *, request: Request) -> dict:
|
69
71
|
return {
|
70
72
|
"redirect_uri": self.get_callback_url(request=request),
|
71
73
|
"client_id": self.get_client_id(),
|
@@ -77,13 +79,13 @@ class OAuthProvider:
|
|
77
79
|
def refresh_oauth_token(self, *, oauth_token: OAuthToken) -> OAuthToken:
|
78
80
|
raise NotImplementedError()
|
79
81
|
|
80
|
-
def get_oauth_token(self, *, code: str, request:
|
82
|
+
def get_oauth_token(self, *, code: str, request: Request) -> OAuthToken:
|
81
83
|
raise NotImplementedError()
|
82
84
|
|
83
85
|
def get_oauth_user(self, *, oauth_token: OAuthToken) -> OAuthUser:
|
84
86
|
raise NotImplementedError()
|
85
87
|
|
86
|
-
def get_authorization_url(self, *, request:
|
88
|
+
def get_authorization_url(self, *, request: Request) -> str:
|
87
89
|
return self.authorization_url
|
88
90
|
|
89
91
|
def get_client_id(self) -> str:
|
@@ -95,14 +97,14 @@ class OAuthProvider:
|
|
95
97
|
def get_scope(self) -> str:
|
96
98
|
return self.scope
|
97
99
|
|
98
|
-
def get_callback_url(self, *, request:
|
100
|
+
def get_callback_url(self, *, request: Request) -> str:
|
99
101
|
url = reverse("oauth:callback", provider=self.provider_key)
|
100
102
|
return request.build_absolute_uri(url)
|
101
103
|
|
102
104
|
def generate_state(self) -> str:
|
103
105
|
return get_random_string(length=32)
|
104
106
|
|
105
|
-
def check_request_state(self, *, request:
|
107
|
+
def check_request_state(self, *, request: Request) -> None:
|
106
108
|
if error := request.query_params.get("error"):
|
107
109
|
raise OAuthError(error)
|
108
110
|
|
@@ -111,26 +113,29 @@ class OAuthProvider:
|
|
111
113
|
except KeyError as e:
|
112
114
|
raise OAuthStateMissingError() from e
|
113
115
|
|
114
|
-
|
115
|
-
|
116
|
+
session = get_request_session(request)
|
117
|
+
expected_state = session.pop(SESSION_STATE_KEY)
|
118
|
+
session.save() # Make sure the pop is saved (won't save on an exception)
|
116
119
|
if not secrets.compare_digest(state, expected_state):
|
117
120
|
raise OAuthStateMismatchError()
|
118
121
|
|
119
122
|
def handle_login_request(
|
120
|
-
self, *, request:
|
123
|
+
self, *, request: Request, redirect_to: str = ""
|
121
124
|
) -> Response:
|
122
125
|
authorization_url = self.get_authorization_url(request=request)
|
123
126
|
authorization_params = self.get_authorization_url_params(request=request)
|
124
127
|
|
128
|
+
session = get_request_session(request)
|
129
|
+
|
125
130
|
if "state" in authorization_params:
|
126
131
|
# Store the state in the session so we can check on callback
|
127
|
-
|
132
|
+
session[SESSION_STATE_KEY] = authorization_params["state"]
|
128
133
|
|
129
134
|
# Store next url in session so we can get it on the callback request
|
130
135
|
if redirect_to:
|
131
|
-
|
136
|
+
session[SESSION_NEXT_KEY] = redirect_to
|
132
137
|
elif "next" in request.data:
|
133
|
-
|
138
|
+
session[SESSION_NEXT_KEY] = request.data["next"]
|
134
139
|
|
135
140
|
# Sort authorization params for consistency
|
136
141
|
sorted_authorization_params = sorted(authorization_params.items())
|
@@ -138,11 +143,11 @@ class OAuthProvider:
|
|
138
143
|
return self.get_redirect_response(redirect_url)
|
139
144
|
|
140
145
|
def handle_connect_request(
|
141
|
-
self, *, request:
|
146
|
+
self, *, request: Request, redirect_to: str = ""
|
142
147
|
) -> Response:
|
143
148
|
return self.handle_login_request(request=request, redirect_to=redirect_to)
|
144
149
|
|
145
|
-
def handle_disconnect_request(self, *, request:
|
150
|
+
def handle_disconnect_request(self, *, request: Request) -> Response:
|
146
151
|
provider_user_id = request.data["provider_user_id"]
|
147
152
|
connection = OAuthConnection.query.get(
|
148
153
|
provider_key=self.provider_key, provider_user_id=provider_user_id
|
@@ -151,7 +156,7 @@ class OAuthProvider:
|
|
151
156
|
redirect_url = self.get_disconnect_redirect_url(request=request)
|
152
157
|
return self.get_redirect_response(redirect_url)
|
153
158
|
|
154
|
-
def handle_callback_request(self, *, request:
|
159
|
+
def handle_callback_request(self, *, request: Request) -> Response:
|
155
160
|
self.check_request_state(request=request)
|
156
161
|
|
157
162
|
oauth_token = self.get_oauth_token(
|
@@ -159,9 +164,10 @@ class OAuthProvider:
|
|
159
164
|
)
|
160
165
|
oauth_user = self.get_oauth_user(oauth_token=oauth_token)
|
161
166
|
|
162
|
-
|
167
|
+
user = get_request_user(request)
|
168
|
+
if user:
|
163
169
|
connection = OAuthConnection.connect(
|
164
|
-
user=
|
170
|
+
user=user,
|
165
171
|
provider_key=self.provider_key,
|
166
172
|
oauth_token=oauth_token,
|
167
173
|
oauth_user=oauth_user,
|
@@ -181,13 +187,14 @@ class OAuthProvider:
|
|
181
187
|
redirect_url = self.get_login_redirect_url(request=request)
|
182
188
|
return self.get_redirect_response(redirect_url)
|
183
189
|
|
184
|
-
def login(self, *, request:
|
190
|
+
def login(self, *, request: Request, user: Any) -> None:
|
185
191
|
auth_login(request=request, user=user)
|
186
192
|
|
187
|
-
def get_login_redirect_url(self, *, request:
|
188
|
-
|
193
|
+
def get_login_redirect_url(self, *, request: Request) -> str:
|
194
|
+
session = get_request_session(request)
|
195
|
+
return session.pop(SESSION_NEXT_KEY, "/")
|
189
196
|
|
190
|
-
def get_disconnect_redirect_url(self, *, request:
|
197
|
+
def get_disconnect_redirect_url(self, *, request: Request) -> str:
|
191
198
|
return request.data.get("next", "/")
|
192
199
|
|
193
200
|
def get_redirect_response(self, redirect_url: str) -> Response:
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
|
3
|
+
from plain.auth.requests import get_request_user
|
3
4
|
from plain.auth.views import AuthViewMixin
|
4
5
|
from plain.http import ResponseRedirect
|
5
6
|
from plain.views import TemplateView, View
|
@@ -16,7 +17,7 @@ class OAuthLoginView(View):
|
|
16
17
|
def post(self):
|
17
18
|
request = self.request
|
18
19
|
provider = self.url_kwargs["provider"]
|
19
|
-
if request
|
20
|
+
if get_request_user(request):
|
20
21
|
return ResponseRedirect("/")
|
21
22
|
|
22
23
|
provider_instance = get_oauth_provider_instance(provider_key=provider)
|
@@ -1,11 +1,11 @@
|
|
1
1
|
{% extends "base.html" %}
|
2
2
|
|
3
3
|
{% block content %}
|
4
|
-
Hello {{
|
4
|
+
Hello {{ user }}!
|
5
5
|
|
6
6
|
<h2>Existing connections</h2>
|
7
7
|
<ul>
|
8
|
-
{% for connection in
|
8
|
+
{% for connection in user.oauth_connections.query.all() %}
|
9
9
|
<li>
|
10
10
|
{{ connection.provider_key }} [ID: {{ connection.provider_user_id }}]
|
11
11
|
<form action="{{ url('oauth:disconnect', connection.provider_key) }}" method="post">
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|