plain.oauth 0.2.0__tar.gz → 0.4.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.
Files changed (19) hide show
  1. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/PKG-INFO +8 -4
  2. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/README.md +3 -3
  3. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/README.md +3 -3
  4. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/migrations/0001_initial.py +0 -1
  5. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/models.py +5 -5
  6. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/providers.py +9 -6
  7. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/views.py +4 -4
  8. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/pyproject.toml +12 -11
  9. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/LICENSE +0 -0
  10. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/__init__.py +0 -0
  11. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/config.py +0 -0
  12. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/exceptions.py +0 -0
  13. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/migrations/0002_alter_oauthconnection_options_and_more.py +0 -0
  14. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/migrations/0003_alter_oauthconnection_access_token_and_more.py +0 -0
  15. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/migrations/0004_alter_oauthconnection_access_token_and_more.py +0 -0
  16. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/migrations/__init__.py +0 -0
  17. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/staff.py +0 -0
  18. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/templates/oauth/error.html +0 -0
  19. {plain_oauth-0.2.0 → plain_oauth-0.4.0}/plain/oauth/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plain.oauth
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: OAuth login and API access for Plain.
5
5
  Home-page: https://plainframework.com
6
6
  License: BSD-3-Clause
@@ -11,6 +11,10 @@ Classifier: License :: OSI Approved :: BSD License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: plain (<1.0.0)
15
+ Requires-Dist: plain.auth (<1.0.0)
16
+ Requires-Dist: plain.models (<1.0.0)
17
+ Requires-Dist: requests
14
18
  Project-URL: Documentation, https://plainframework.com/docs/
15
19
  Project-URL: Repository, https://github.com/dropseed/plain
16
20
  Description-Content-Type: text/markdown
@@ -108,9 +112,11 @@ class ExampleOAuthProvider(OAuthProvider):
108
112
  response.raise_for_status()
109
113
  data = response.json()
110
114
  return OAuthUser(
115
+ # The provider ID is required
111
116
  id=data["id"],
112
- username=data["username"],
117
+ # And you can populate any of your User model fields with additional kwargs
113
118
  email=data["email"],
119
+ username=data["username"],
114
120
  )
115
121
  ```
116
122
 
@@ -223,13 +229,11 @@ Hello {{ request.user }}!
223
229
  {% for connection in request.user.oauth_connections.all %}
224
230
  <li>
225
231
  {{ connection.provider_key }} [ID: {{ connection.provider_user_id }}]
226
- {% if connection.can_be_disconnected %}
227
232
  <form action="{% url 'oauth:disconnect' connection.provider_key %}" method="post">
228
233
  {{ csrf_input }}
229
234
  <input type="hidden" name="provider_user_id" value="{{ connection.provider_user_id }}">
230
235
  <button type="submit">Disconnect</button>
231
236
  </form>
232
- {% endif %}
233
237
  </li>
234
238
  {% endfor %}
235
239
  </ul>
@@ -91,9 +91,11 @@ class ExampleOAuthProvider(OAuthProvider):
91
91
  response.raise_for_status()
92
92
  data = response.json()
93
93
  return OAuthUser(
94
+ # The provider ID is required
94
95
  id=data["id"],
95
- username=data["username"],
96
+ # And you can populate any of your User model fields with additional kwargs
96
97
  email=data["email"],
98
+ username=data["username"],
97
99
  )
98
100
  ```
99
101
 
@@ -206,13 +208,11 @@ Hello {{ request.user }}!
206
208
  {% for connection in request.user.oauth_connections.all %}
207
209
  <li>
208
210
  {{ connection.provider_key }} [ID: {{ connection.provider_user_id }}]
209
- {% if connection.can_be_disconnected %}
210
211
  <form action="{% url 'oauth:disconnect' connection.provider_key %}" method="post">
211
212
  {{ csrf_input }}
212
213
  <input type="hidden" name="provider_user_id" value="{{ connection.provider_user_id }}">
213
214
  <button type="submit">Disconnect</button>
214
215
  </form>
215
- {% endif %}
216
216
  </li>
217
217
  {% endfor %}
218
218
  </ul>
@@ -89,9 +89,11 @@ class ExampleOAuthProvider(OAuthProvider):
89
89
  response.raise_for_status()
90
90
  data = response.json()
91
91
  return OAuthUser(
92
+ # The provider ID is required
92
93
  id=data["id"],
93
- username=data["username"],
94
+ # And you can populate any of your User model fields with additional kwargs
94
95
  email=data["email"],
96
+ username=data["username"],
95
97
  )
96
98
  ```
97
99
 
@@ -204,13 +206,11 @@ Hello {{ request.user }}!
204
206
  {% for connection in request.user.oauth_connections.all %}
205
207
  <li>
206
208
  {{ connection.provider_key }} [ID: {{ connection.provider_user_id }}]
207
- {% if connection.can_be_disconnected %}
208
209
  <form action="{% url 'oauth:disconnect' connection.provider_key %}" method="post">
209
210
  {{ csrf_input }}
210
211
  <input type="hidden" name="provider_user_id" value="{{ connection.provider_user_id }}">
211
212
  <button type="submit">Disconnect</button>
212
213
  </form>
213
- {% endif %}
214
214
  </li>
215
215
  {% endfor %}
216
216
  </ul>
@@ -22,7 +22,6 @@ class Migration(migrations.Migration):
22
22
  models.BigAutoField(
23
23
  auto_created=True,
24
24
  primary_key=True,
25
- serialize=False,
26
25
  ),
27
26
  ),
28
27
  ("created_at", models.DateTimeField(auto_now_add=True)),
@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
2
2
 
3
3
  from plain import models
4
4
  from plain.auth import get_user_model
5
+ from plain.exceptions import ValidationError
5
6
  from plain.models import transaction
6
7
  from plain.models.db import IntegrityError, OperationalError, ProgrammingError
7
8
  from plain.preflight import Error
@@ -14,7 +15,7 @@ if TYPE_CHECKING:
14
15
  from .providers import OAuthToken, OAuthUser
15
16
 
16
17
 
17
- # django check for deploy that ensures all provider keys in db are also in settings?
18
+ # TODO preflight check for deploy that ensures all provider keys in db are also in settings?
18
19
 
19
20
 
20
21
  class OAuthConnection(models.Model):
@@ -84,7 +85,7 @@ class OAuthConnection(models.Model):
84
85
  )
85
86
 
86
87
  @classmethod
87
- def get_or_createuser(
88
+ def get_or_create_user(
88
89
  cls, *, provider_key: str, oauth_token: "OAuthToken", oauth_user: "OAuthUser"
89
90
  ) -> "OAuthConnection":
90
91
  try:
@@ -101,12 +102,11 @@ class OAuthConnection(models.Model):
101
102
  # that to be taken care of on the user model itself
102
103
  try:
103
104
  user = get_user_model()(
104
- username=oauth_user.username,
105
- email=oauth_user.email,
105
+ **oauth_user.user_model_fields,
106
106
  )
107
107
  user.full_clean()
108
108
  user.save()
109
- except IntegrityError:
109
+ except (IntegrityError, ValidationError):
110
110
  raise OAuthUserAlreadyExistsError()
111
111
 
112
112
  return cls.connect(
@@ -33,13 +33,16 @@ class OAuthToken:
33
33
 
34
34
 
35
35
  class OAuthUser:
36
- def __init__(self, *, id: str, email: str, username: str = ""):
37
- self.id = id
38
- self.username = username
39
- self.email = email
36
+ def __init__(self, *, id: str, **user_model_fields: dict):
37
+ self.id = id # ID on the provider's system
38
+ self.user_model_fields = user_model_fields
40
39
 
41
40
  def __str__(self):
42
- return self.email
41
+ if "email" in self.user_model_fields:
42
+ return self.user_model_fields["email"]
43
+ if "username" in self.user_model_fields:
44
+ return self.user_model_fields["username"]
45
+ return str(self.id)
43
46
 
44
47
 
45
48
  class OAuthProvider:
@@ -157,7 +160,7 @@ class OAuthProvider:
157
160
  )
158
161
  user = connection.user
159
162
  else:
160
- connection = OAuthConnection.get_or_createuser(
163
+ connection = OAuthConnection.get_or_create_user(
161
164
  provider_key=self.provider_key,
162
165
  oauth_token=oauth_token,
163
166
  oauth_user=oauth_user,
@@ -2,7 +2,7 @@ import logging
2
2
 
3
3
  from plain.auth.views import AuthViewMixin
4
4
  from plain.http import ResponseBadRequest, ResponseRedirect
5
- from plain.templates import jinja
5
+ from plain.templates import Template
6
6
  from plain.views import View
7
7
 
8
8
  from .exceptions import (
@@ -38,7 +38,7 @@ class OAuthCallbackView(View):
38
38
  try:
39
39
  return provider_instance.handle_callback_request(request=request)
40
40
  except OAuthUserAlreadyExistsError:
41
- template = jinja.get_template("oauth/error.html")
41
+ template = Template("oauth/error.html")
42
42
  return ResponseBadRequest(
43
43
  template.render(
44
44
  {
@@ -47,7 +47,7 @@ class OAuthCallbackView(View):
47
47
  )
48
48
  )
49
49
  except OAuthStateMismatchError:
50
- template = jinja.get_template("oauth/error.html")
50
+ template = Template("oauth/error.html")
51
51
  return ResponseBadRequest(
52
52
  template.render(
53
53
  {
@@ -57,7 +57,7 @@ class OAuthCallbackView(View):
57
57
  )
58
58
  except OAuthError as e:
59
59
  logger.exception("OAuth error")
60
- template = jinja.get_template("oauth/error.html")
60
+ template = Template("oauth/error.html")
61
61
  return ResponseBadRequest(template.render({"oauth_error": str(e)}))
62
62
 
63
63
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "plain.oauth"
3
- version = "0.2.0"
3
+ version = "0.4.0"
4
4
  description = "OAuth login and API access for Plain."
5
5
  authors = ["Dave Gaeddert <dave.gaeddert@dropseed.dev>"]
6
6
  license = "BSD-3-Clause"
@@ -19,17 +19,18 @@ FAIL_INVALID_TEMPLATE_VARS = true
19
19
 
20
20
  [tool.poetry.dependencies]
21
21
  python = "^3.11"
22
+ plain = "<1.0.0"
23
+ "plain.auth" = "<1.0.0"
24
+ "plain.models" = "<1.0.0"
25
+ requests = "*"
22
26
 
23
- [tool.poetry.dev-dependencies]
24
- black = "^22.1.0"
25
- Django = "^4.0.0"
26
- pytest = "^7.1.0"
27
- pytest-django = "^4.5.2"
28
- ipdb = "^0.13.9"
29
- requests = "^2.27.1"
30
- mypy = "*"
31
- isort = "^5.10.1"
32
- python-dotenv = "^0.20.0"
27
+ [tool.poetry.group.dev.dependencies]
28
+ plain = {path = "../plain", develop = true}
29
+ "plain.auth" = {path = "../plain-auth", develop = true}
30
+ "plain.sessions" = {path = "../plain-sessions", develop = true}
31
+ "plain.models" = {path = "../plain-models", develop = true}
32
+ "plain.pytest" = {path = "../plain-pytest", develop = true}
33
+ coverage = "^7.6.0"
33
34
 
34
35
  [build-system]
35
36
  requires = ["poetry-core>=1.0.0"]
File without changes