plain.oauth 0.20.0__tar.gz → 0.22.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.20.0 → plain_oauth-0.22.0}/PKG-INFO +7 -5
- plain_oauth-0.22.0/plain/oauth/CHANGELOG.md +11 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/README.md +6 -4
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/models.py +25 -28
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/providers.py +7 -7
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/views.py +3 -2
- {plain_oauth-0.20.0/tests/providers → plain_oauth-0.22.0/provider_examples}/bitbucket.py +5 -3
- {plain_oauth-0.20.0/tests/providers → plain_oauth-0.22.0/provider_examples}/github.py +5 -3
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/provider_examples/gitlab.py +5 -3
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/pyproject.toml +1 -1
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/app/settings.py +3 -5
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/provider_tests/test_github.py +5 -3
- {plain_oauth-0.20.0/provider_examples → plain_oauth-0.22.0/tests/providers}/bitbucket.py +5 -3
- {plain_oauth-0.20.0/provider_examples → plain_oauth-0.22.0/tests/providers}/github.py +5 -3
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/providers/gitlab.py +5 -3
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/test_backends.py +5 -3
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/test_checks.py +2 -2
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/test_providers.py +5 -3
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/.gitignore +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/LICENSE +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/README.md +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/__init__.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/admin.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/config.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/default_settings.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/exceptions.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/migrations/0001_initial.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/migrations/0002_alter_oauthconnection_options_and_more.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/migrations/0003_alter_oauthconnection_access_token_and_more.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/migrations/0004_alter_oauthconnection_access_token_and_more.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/migrations/0005_alter_oauthconnection_unique_together_and_more.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/migrations/0006_remove_oauthconnection_unique_oauth_provider_user_id_and_more.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/migrations/0007_alter_oauthconnection_provider_key_and_more.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/migrations/__init__.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/templates/oauth/callback.html +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/plain/oauth/urls.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/provider_examples/__init__.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/app/templates/base.html +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/app/templates/index.html +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/app/templates/login.html +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/app/urls.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/app/users/migrations/0001_initial.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/app/users/migrations/__init__.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/app/users/models.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/provider_tests/__init__.py +0 -0
- {plain_oauth-0.20.0 → plain_oauth-0.22.0}/tests/providers/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: plain.oauth
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.22.0
|
4
4
|
Summary: OAuth login and API access for Plain.
|
5
5
|
Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
|
6
6
|
License-Expression: BSD-3-Clause
|
@@ -103,10 +103,12 @@ class ExampleOAuthProvider(OAuthProvider):
|
|
103
103
|
data = response.json()
|
104
104
|
return OAuthUser(
|
105
105
|
# The provider ID is required
|
106
|
-
|
107
|
-
#
|
108
|
-
|
109
|
-
|
106
|
+
provider_id=data["id"],
|
107
|
+
# Populate your User model fields using the user_model_fields dict
|
108
|
+
user_model_fields={
|
109
|
+
"email": data["email"],
|
110
|
+
"username": data["username"],
|
111
|
+
},
|
110
112
|
)
|
111
113
|
```
|
112
114
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# plain-oauth changelog
|
2
|
+
|
3
|
+
## [0.22.0](https://github.com/dropseed/plain/releases/plain-oauth@0.22.0) (2025-06-23)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- Updated `OAuthConnection.check()` to accept a single `database` argument instead of the older `databases` list, matching the new single `DATABASE` setting used across the Plain stack ([d346d81](https://github.com/dropseed/plain/commit/d346d81))
|
8
|
+
|
9
|
+
### Upgrade instructions
|
10
|
+
|
11
|
+
- No changes required.
|
@@ -89,10 +89,12 @@ class ExampleOAuthProvider(OAuthProvider):
|
|
89
89
|
data = response.json()
|
90
90
|
return OAuthUser(
|
91
91
|
# The provider ID is required
|
92
|
-
|
93
|
-
#
|
94
|
-
|
95
|
-
|
92
|
+
provider_id=data["id"],
|
93
|
+
# Populate your User model fields using the user_model_fields dict
|
94
|
+
user_model_fields={
|
95
|
+
"email": data["email"],
|
96
|
+
"username": data["username"],
|
97
|
+
},
|
96
98
|
)
|
97
99
|
```
|
98
100
|
|
@@ -76,7 +76,7 @@ class OAuthConnection(models.Model):
|
|
76
76
|
self.refresh_token_expires_at = oauth_token.refresh_token_expires_at
|
77
77
|
|
78
78
|
def set_user_fields(self, oauth_user: "OAuthUser"):
|
79
|
-
self.provider_user_id = oauth_user.
|
79
|
+
self.provider_user_id = oauth_user.provider_id
|
80
80
|
|
81
81
|
def access_token_expired(self) -> bool:
|
82
82
|
return (
|
@@ -97,7 +97,7 @@ class OAuthConnection(models.Model):
|
|
97
97
|
try:
|
98
98
|
connection = cls.objects.get(
|
99
99
|
provider_key=provider_key,
|
100
|
-
provider_user_id=oauth_user.
|
100
|
+
provider_user_id=oauth_user.provider_id,
|
101
101
|
)
|
102
102
|
connection.set_token_fields(oauth_token)
|
103
103
|
connection.save()
|
@@ -137,7 +137,7 @@ class OAuthConnection(models.Model):
|
|
137
137
|
connection = cls.objects.get(
|
138
138
|
user=user,
|
139
139
|
provider_key=provider_key,
|
140
|
-
provider_user_id=oauth_user.
|
140
|
+
provider_user_id=oauth_user.provider_id,
|
141
141
|
)
|
142
142
|
except cls.DoesNotExist:
|
143
143
|
# Create our own instance (not using get_or_create)
|
@@ -145,7 +145,7 @@ class OAuthConnection(models.Model):
|
|
145
145
|
connection = cls(
|
146
146
|
user=user,
|
147
147
|
provider_key=provider_key,
|
148
|
-
provider_user_id=oauth_user.
|
148
|
+
provider_user_id=oauth_user.provider_id,
|
149
149
|
)
|
150
150
|
|
151
151
|
connection.set_user_fields(oauth_user)
|
@@ -164,34 +164,31 @@ class OAuthConnection(models.Model):
|
|
164
164
|
"""
|
165
165
|
errors = super().check(**kwargs)
|
166
166
|
|
167
|
-
|
168
|
-
if not
|
167
|
+
database = kwargs.get("database", False)
|
168
|
+
if not database:
|
169
169
|
return errors
|
170
170
|
|
171
171
|
from .providers import get_provider_keys
|
172
172
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
", ".join(keys_in_db - keys_in_settings)
|
192
|
-
),
|
193
|
-
id="plain.oauth.E001",
|
194
|
-
)
|
173
|
+
try:
|
174
|
+
keys_in_db = set(
|
175
|
+
cls.objects.values_list("provider_key", flat=True).distinct()
|
176
|
+
)
|
177
|
+
except (OperationalError, ProgrammingError):
|
178
|
+
# Check runs on manage.py migrate, and the table may not exist yet
|
179
|
+
# or it may not be installed on the particular database intentionally
|
180
|
+
return errors
|
181
|
+
|
182
|
+
keys_in_settings = set(get_provider_keys())
|
183
|
+
|
184
|
+
if keys_in_db - keys_in_settings:
|
185
|
+
errors.append(
|
186
|
+
Error(
|
187
|
+
"The following OAuth providers are in the database but not in the settings: {}".format(
|
188
|
+
", ".join(keys_in_db - keys_in_settings)
|
189
|
+
),
|
190
|
+
id="plain.oauth.E001",
|
195
191
|
)
|
192
|
+
)
|
196
193
|
|
197
194
|
return errors
|
@@ -24,8 +24,8 @@ class OAuthToken:
|
|
24
24
|
*,
|
25
25
|
access_token: str,
|
26
26
|
refresh_token: str = "",
|
27
|
-
access_token_expires_at: datetime.datetime = None,
|
28
|
-
refresh_token_expires_at: datetime.datetime = None,
|
27
|
+
access_token_expires_at: datetime.datetime | None = None,
|
28
|
+
refresh_token_expires_at: datetime.datetime | None = None,
|
29
29
|
):
|
30
30
|
self.access_token = access_token
|
31
31
|
self.refresh_token = refresh_token
|
@@ -34,16 +34,16 @@ class OAuthToken:
|
|
34
34
|
|
35
35
|
|
36
36
|
class OAuthUser:
|
37
|
-
def __init__(self, *,
|
38
|
-
self.
|
39
|
-
self.user_model_fields = user_model_fields
|
37
|
+
def __init__(self, *, provider_id: str, user_model_fields: dict | None = None):
|
38
|
+
self.provider_id = provider_id # ID on the provider's system
|
39
|
+
self.user_model_fields = user_model_fields or {}
|
40
40
|
|
41
41
|
def __str__(self):
|
42
42
|
if "email" in self.user_model_fields:
|
43
43
|
return self.user_model_fields["email"]
|
44
44
|
if "username" in self.user_model_fields:
|
45
45
|
return self.user_model_fields["username"]
|
46
|
-
return str(self.
|
46
|
+
return str(self.provider_id)
|
47
47
|
|
48
48
|
|
49
49
|
class OAuthProvider:
|
@@ -181,7 +181,7 @@ class OAuthProvider:
|
|
181
181
|
redirect_url = self.get_login_redirect_url(request=request)
|
182
182
|
return self.get_redirect_response(redirect_url)
|
183
183
|
|
184
|
-
def login(self, *, request: HttpRequest, user: Any) ->
|
184
|
+
def login(self, *, request: HttpRequest, user: Any) -> None:
|
185
185
|
auth_login(request=request, user=user)
|
186
186
|
|
187
187
|
def get_login_redirect_url(self, *, request: HttpRequest) -> str:
|
@@ -39,8 +39,9 @@ class OAuthCallbackView(TemplateView):
|
|
39
39
|
logger.exception("OAuth error")
|
40
40
|
self.oauth_error = e
|
41
41
|
|
42
|
-
|
43
|
-
|
42
|
+
response = super().get()
|
43
|
+
response.status_code = 400
|
44
|
+
return response
|
44
45
|
|
45
46
|
def get_template_context(self) -> dict:
|
46
47
|
context = super().get_template_context()
|
@@ -69,7 +69,9 @@ class BitbucketOAuthProvider(OAuthProvider):
|
|
69
69
|
][0]
|
70
70
|
|
71
71
|
return OAuthUser(
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
provider_id=user_id,
|
73
|
+
user_model_fields={
|
74
|
+
"email": confirmed_primary_email,
|
75
|
+
"username": username,
|
76
|
+
},
|
75
77
|
)
|
@@ -95,7 +95,9 @@ class GitHubOAuthProvider(OAuthProvider):
|
|
95
95
|
raise OAuthError("A verified primary email address is required on GitHub")
|
96
96
|
|
97
97
|
return OAuthUser(
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
provider_id=user_id,
|
99
|
+
user_model_fields={
|
100
|
+
"email": verified_primary_email,
|
101
|
+
"username": username,
|
102
|
+
},
|
101
103
|
)
|
@@ -51,7 +51,9 @@ class GitLabOAuthProvider(OAuthProvider):
|
|
51
51
|
response.raise_for_status()
|
52
52
|
data = response.json()
|
53
53
|
return OAuthUser(
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
provider_id=data["id"],
|
55
|
+
user_model_fields={
|
56
|
+
"email": data["email"],
|
57
|
+
"username": data["username"],
|
58
|
+
},
|
57
59
|
)
|
@@ -9,11 +9,9 @@ INSTALLED_PACKAGES = [
|
|
9
9
|
"plain.oauth",
|
10
10
|
"app.users",
|
11
11
|
]
|
12
|
-
|
13
|
-
"
|
14
|
-
|
15
|
-
"NAME": ":memory:",
|
16
|
-
}
|
12
|
+
DATABASE = {
|
13
|
+
"ENGINE": "plain.models.backends.sqlite3",
|
14
|
+
"NAME": ":memory:",
|
17
15
|
}
|
18
16
|
MIDDLEWARE = [
|
19
17
|
"plain.sessions.middleware.SessionMiddleware",
|
@@ -13,9 +13,11 @@ class DummyGitHubOAuthProvider(GitHubOAuthProvider):
|
|
13
13
|
|
14
14
|
def get_oauth_user(self, oauth_token):
|
15
15
|
return OAuthUser(
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
provider_id="99",
|
17
|
+
user_model_fields={
|
18
|
+
"username": "userone",
|
19
|
+
"email": "user@example.com",
|
20
|
+
},
|
19
21
|
)
|
20
22
|
|
21
23
|
|
@@ -69,7 +69,9 @@ class BitbucketOAuthProvider(OAuthProvider):
|
|
69
69
|
][0]
|
70
70
|
|
71
71
|
return OAuthUser(
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
provider_id=user_id,
|
73
|
+
user_model_fields={
|
74
|
+
"email": confirmed_primary_email,
|
75
|
+
"username": username,
|
76
|
+
},
|
75
77
|
)
|
@@ -95,7 +95,9 @@ class GitHubOAuthProvider(OAuthProvider):
|
|
95
95
|
raise OAuthError("A verified primary email address is required on GitHub")
|
96
96
|
|
97
97
|
return OAuthUser(
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
provider_id=user_id,
|
99
|
+
user_model_fields={
|
100
|
+
"email": verified_primary_email,
|
101
|
+
"username": username,
|
102
|
+
},
|
101
103
|
)
|
@@ -51,7 +51,9 @@ class GitLabOAuthProvider(OAuthProvider):
|
|
51
51
|
response.raise_for_status()
|
52
52
|
data = response.json()
|
53
53
|
return OAuthUser(
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
provider_id=data["id"],
|
55
|
+
user_model_fields={
|
56
|
+
"email": data["email"],
|
57
|
+
"username": data["username"],
|
58
|
+
},
|
57
59
|
)
|
@@ -10,9 +10,11 @@ class DummyProvider(OAuthProvider):
|
|
10
10
|
|
11
11
|
def get_oauth_user(self, *, oauth_token):
|
12
12
|
return OAuthUser(
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
provider_id="dummy_user_id",
|
14
|
+
user_model_fields={
|
15
|
+
"username": "dummy_username",
|
16
|
+
"email": "dummy@example.com",
|
17
|
+
},
|
16
18
|
)
|
17
19
|
|
18
20
|
def check_request_state(self, *, request):
|
@@ -25,7 +25,7 @@ def test_oauth_provider_keys_check_pass(db, settings):
|
|
25
25
|
access_token="test",
|
26
26
|
)
|
27
27
|
|
28
|
-
errors = OAuthConnection.check(
|
28
|
+
errors = OAuthConnection.check(database=True)
|
29
29
|
assert len(errors) == 0
|
30
30
|
|
31
31
|
|
@@ -58,7 +58,7 @@ def test_oauth_provider_keys_check_fail(db, settings):
|
|
58
58
|
access_token="test",
|
59
59
|
)
|
60
60
|
|
61
|
-
errors = OAuthConnection.check(
|
61
|
+
errors = OAuthConnection.check(database=True)
|
62
62
|
assert len(errors) == 1
|
63
63
|
assert (
|
64
64
|
errors[0].msg
|
@@ -38,9 +38,11 @@ class DummyProvider(OAuthProvider):
|
|
38
38
|
|
39
39
|
def get_oauth_user(self, *, oauth_token: OAuthToken) -> OAuthUser:
|
40
40
|
return OAuthUser(
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
provider_id="dummy_id",
|
42
|
+
user_model_fields={
|
43
|
+
"email": "dummy@example.com",
|
44
|
+
"username": "dummy_username",
|
45
|
+
},
|
44
46
|
)
|
45
47
|
|
46
48
|
|
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
|