plain.oauth 0.7.5__tar.gz → 0.8.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 (43) hide show
  1. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/PKG-INFO +1 -5
  2. plain_oauth-0.8.0/README.md +1 -0
  3. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/README.md +0 -2
  4. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/staff.py +2 -2
  5. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/pyproject.toml +1 -1
  6. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/uv.lock +2 -2
  7. plain_oauth-0.7.5/README.md +0 -303
  8. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/.gitignore +0 -0
  9. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/LICENSE +0 -0
  10. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/__init__.py +0 -0
  11. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/config.py +0 -0
  12. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/default_settings.py +0 -0
  13. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/exceptions.py +0 -0
  14. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/migrations/0001_initial.py +0 -0
  15. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/migrations/0002_alter_oauthconnection_options_and_more.py +0 -0
  16. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/migrations/0003_alter_oauthconnection_access_token_and_more.py +0 -0
  17. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/migrations/0004_alter_oauthconnection_access_token_and_more.py +0 -0
  18. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/migrations/0005_alter_oauthconnection_unique_together_and_more.py +0 -0
  19. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/migrations/__init__.py +0 -0
  20. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/models.py +0 -0
  21. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/providers.py +0 -0
  22. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/templates/oauth/error.html +0 -0
  23. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/urls.py +0 -0
  24. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/plain/oauth/views.py +0 -0
  25. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/provider_examples/__init__.py +0 -0
  26. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/provider_examples/bitbucket.py +0 -0
  27. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/provider_examples/github.py +0 -0
  28. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/provider_examples/gitlab.py +0 -0
  29. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/app/settings.py +0 -0
  30. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/app/templates/base.html +0 -0
  31. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/app/templates/index.html +0 -0
  32. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/app/templates/login.html +0 -0
  33. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/app/urls.py +0 -0
  34. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/app/users/models.py +0 -0
  35. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/provider_tests/__init__.py +0 -0
  36. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/provider_tests/test_github.py +0 -0
  37. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/providers/__init__.py +0 -0
  38. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/providers/bitbucket.py +0 -0
  39. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/providers/github.py +0 -0
  40. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/providers/gitlab.py +0 -0
  41. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/test_backends.py +0 -0
  42. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/test_checks.py +0 -0
  43. {plain_oauth-0.7.5 → plain_oauth-0.8.0}/tests/test_providers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.oauth
3
- Version: 0.7.5
3
+ Version: 0.8.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
@@ -12,10 +12,6 @@ Requires-Dist: plain<1.0.0
12
12
  Requires-Dist: requests>=2.0.0
13
13
  Description-Content-Type: text/markdown
14
14
 
15
- <!-- This file is compiled from plain-oauth/plain/oauth/README.md. Do not edit this file directly. -->
16
-
17
- <!-- This file is compiled from plain-oauth/plain/oauth/README.md. Do not edit this file directly. -->
18
-
19
15
  # plain.oauth
20
16
 
21
17
  Let users log in with OAuth providers.
@@ -0,0 +1 @@
1
+ ./plain/oauth/README.md
@@ -1,5 +1,3 @@
1
- <!-- This file is compiled from plain-oauth/plain/oauth/README.md. Do not edit this file directly. -->
2
-
3
1
  # plain.oauth
4
2
 
5
3
  Let users log in with OAuth providers.
@@ -3,7 +3,7 @@ from plain.staff.cards import ChartCard
3
3
  from plain.staff.views import (
4
4
  StaffModelDetailView,
5
5
  StaffModelListView,
6
- StaffModelViewset,
6
+ StaffViewset,
7
7
  register_viewset,
8
8
  )
9
9
 
@@ -34,7 +34,7 @@ class ProvidersChartCard(ChartCard):
34
34
 
35
35
 
36
36
  @register_viewset
37
- class OAuthConnectionViewset(StaffModelViewset):
37
+ class OAuthConnectionViewset(StaffViewset):
38
38
  class ListView(StaffModelListView):
39
39
  nav_section = "OAuth"
40
40
  model = OAuthConnection
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain.oauth"
3
- version = "0.7.5"
3
+ version = "0.8.0"
4
4
  description = "OAuth login and API access for Plain."
5
5
  authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
6
6
  license = "BSD-3-Clause"
@@ -214,7 +214,7 @@ wheels = [
214
214
 
215
215
  [[package]]
216
216
  name = "plain-oauth"
217
- version = "0.7.4"
217
+ version = "0.7.5"
218
218
  source = { editable = "." }
219
219
  dependencies = [
220
220
  { name = "plain" },
@@ -241,7 +241,7 @@ dev = [{ name = "plain-pytest", editable = "../plain-pytest" }]
241
241
 
242
242
  [[package]]
243
243
  name = "plain-pytest"
244
- version = "0.3.0"
244
+ version = "0.3.1"
245
245
  source = { editable = "../plain-pytest" }
246
246
  dependencies = [
247
247
  { name = "click" },
@@ -1,303 +0,0 @@
1
- <!-- This file is compiled from plain-oauth/plain/oauth/README.md. Do not edit this file directly. -->
2
-
3
- <!-- This file is compiled from plain-oauth/plain/oauth/README.md. Do not edit this file directly. -->
4
-
5
- # plain.oauth
6
-
7
- Let users log in with OAuth providers.
8
-
9
- [Watch on YouTube (3 mins) →](https://www.youtube.com/watch?v=UxbxBa6AFsU)
10
-
11
- This library is intentionally minimal.
12
- It has no dependencies and a single database model.
13
- If you simply want users to log in with GitHub, Google, Twitter, etc. (and maybe use that access token for API calls),
14
- then this is the library for you.
15
-
16
- There are three OAuth flows that it makes possible:
17
-
18
- 1. Signup via OAuth (new user, new OAuth connection)
19
- 2. Login via OAuth (existing user, existing OAuth connection)
20
- 3. Connect/disconnect OAuth accounts to a user (existing user, new OAuth connection)
21
-
22
-
23
- ## Usage
24
-
25
- Install the package from PyPi:
26
-
27
- ```sh
28
- pip install plain-oauth
29
- ```
30
-
31
- Add `plain.oauth` to your `INSTALLED_PACKAGES` in `settings.py`:
32
-
33
- ```python
34
- INSTALLED_PACKAGES = [
35
- ...
36
- "plain.oauth",
37
- ]
38
- ```
39
-
40
- In your `urls.py`, include `plain.oauth.urls`:
41
-
42
- ```python
43
- urlpatterns = [
44
- path("oauth/", include("plain.oauth.urls")),
45
- ...
46
- ]
47
- ```
48
-
49
- Then run migrations:
50
-
51
- ```sh
52
- python manage.py migrate plain.oauth
53
- ```
54
-
55
- Create a new OAuth provider ([or copy one from our examples](https://github.com/forgepackages/plain-oauth/tree/master/provider_examples)):
56
-
57
- ```python
58
- # yourapp/oauth.py
59
- import requests
60
-
61
- from plain.oauth.providers import OAuthProvider, OAuthToken, OAuthUser
62
-
63
-
64
- class ExampleOAuthProvider(OAuthProvider):
65
- authorization_url = "https://example.com/login/oauth/authorize"
66
-
67
- def get_oauth_token(self, *, code, request):
68
- response = requests.post(
69
- "https://example.com/login/oauth/token",
70
- headers={
71
- "Accept": "application/json",
72
- },
73
- data={
74
- "client_id": self.get_client_id(),
75
- "client_secret": self.get_client_secret(),
76
- "code": code,
77
- },
78
- )
79
- response.raise_for_status()
80
- data = response.json()
81
- return OAuthToken(
82
- access_token=data["access_token"],
83
- )
84
-
85
- def get_oauth_user(self, *, oauth_token):
86
- response = requests.get(
87
- "https://example.com/api/user",
88
- headers={
89
- "Accept": "application/json",
90
- "Authorization": f"token {oauth_token.access_token}",
91
- },
92
- )
93
- response.raise_for_status()
94
- data = response.json()
95
- return OAuthUser(
96
- # The provider ID is required
97
- id=data["id"],
98
- # And you can populate any of your User model fields with additional kwargs
99
- email=data["email"],
100
- username=data["username"],
101
- )
102
- ```
103
-
104
- Create your OAuth app/consumer on the provider's site (GitHub, Google, etc.).
105
- When setting it up, you'll likely need to give it a callback URL.
106
- In development this can be `http://localhost:8000/oauth/github/callback/` (if you name it `"github"` like in the example below).
107
- At the end you should get some sort of "client id" and "client secret" which you can then use in your `settings.py`:
108
-
109
- ```python
110
- OAUTH_LOGIN_PROVIDERS = {
111
- "github": {
112
- "class": "yourapp.oauth.GitHubOAuthProvider",
113
- "kwargs": {
114
- "client_id": environ["GITHUB_CLIENT_ID"],
115
- "client_secret": environ["GITHUB_CLIENT_SECRET"],
116
- # "scope" is optional, defaults to ""
117
-
118
- # You can add other fields if you have additional kwargs in your class __init__
119
- # def __init__(self, *args, custom_arg="default", **kwargs):
120
- # self.custom_arg = custom_arg
121
- # super().__init__(*args, **kwargs)
122
- },
123
- },
124
- }
125
- ```
126
-
127
- Then add a login button (which is a form using POST rather than a basic link, for security purposes):
128
-
129
- ```html
130
- <h1>Login</h1>
131
- <form action="{% url 'oauth:login' 'github' %}" method="post">
132
- {{ csrf_input }}
133
- <button type="submit">Login with GitHub</button>
134
- </form>
135
- ```
136
-
137
- Depending on your URL and provider names,
138
- your OAuth callback will be something like `https://example.com/oauth/{provider}/callback/`.
139
-
140
- That's pretty much it!
141
-
142
- ## Advanced usage
143
-
144
- ### Handling OAuth errors
145
-
146
- The most common error you'll run into is if an existing user clicks a login button,
147
- but they haven't yet connected that provider to their account.
148
- For security reasons,
149
- the required flow here is that the user actually logs in with another method (however they signed up) and then *connects* the OAuth provider from a settings page.
150
-
151
- For this error (and a couple others),
152
- there is an error template that is rendered.
153
- You can customize this by copying `oauth/error.html` to one of your own template directories:
154
-
155
- ```html
156
- {% extends "base.html" %}
157
-
158
- {% block content %}
159
- <h1>OAuth Error</h1>
160
- <p>{{ oauth_error }}</p>
161
- {% endblock %}
162
- ```
163
-
164
- ![Django OAuth duplicate email address error](https://user-images.githubusercontent.com/649496/159065848-b4ee6e63-9aa0-47b5-94e8-7bee9b509e60.png)
165
-
166
- ### Connecting and disconnecting OAuth accounts
167
-
168
- To connect and disconnect OAuth accounts,
169
- you can add a series of forms to a user/profile settings page.
170
- Here's an very basic example:
171
-
172
- ```html
173
- {% extends "base.html" %}
174
-
175
- {% block content %}
176
- Hello {{ request.user }}!
177
-
178
- <h2>Existing connections</h2>
179
- <ul>
180
- {% for connection in request.user.oauth_connections.all %}
181
- <li>
182
- {{ connection.provider_key }} [ID: {{ connection.provider_user_id }}]
183
- <form action="{% url 'oauth:disconnect' connection.provider_key %}" method="post">
184
- {{ csrf_input }}
185
- <input type="hidden" name="provider_user_id" value="{{ connection.provider_user_id }}">
186
- <button type="submit">Disconnect</button>
187
- </form>
188
- </li>
189
- {% endfor %}
190
- </ul>
191
-
192
- <h2>Add a connection</h2>
193
- <ul>
194
- {% for provider_key in oauth_provider_keys %}
195
- <li>
196
- {{ provider_key}}
197
- <form action="{% url 'oauth:connect' provider_key %}" method="post">
198
- {{ csrf_input }}
199
- <button type="submit">Connect</button>
200
- </form>
201
- </li>
202
- {% endfor %}
203
- </ul>
204
-
205
- {% endblock %}
206
- ```
207
-
208
- The `get_provider_keys` function can help populate the list of options:
209
-
210
- ```python
211
- from plain.oauth.providers import get_provider_keys
212
-
213
- class ExampleView(TemplateView):
214
- template_name = "index.html"
215
-
216
- def get_context(self, **kwargs):
217
- context = super().get_context(**kwargs)
218
- context["oauth_provider_keys"] = get_provider_keys()
219
- return context
220
- ```
221
-
222
- ![Connecting and disconnecting Django OAuth accounts](https://user-images.githubusercontent.com/649496/159065096-30239a1f-62f6-4ee2-a944-45140f45af6f.png)
223
-
224
- ### Using a saved access token
225
-
226
- ```python
227
- import requests
228
-
229
- # Get the OAuth connection for a user
230
- connection = user.oauth_connections.get(provider_key="github")
231
-
232
- # If the token can expire, check and refresh it
233
- if connection.access_token_expired():
234
- connection.refresh_access_token()
235
-
236
- # Use the token in an API call
237
- token = connection.access_token
238
- response = requests.get(...)
239
- ```
240
-
241
- ### Using the Django system check
242
-
243
- This library comes with a Django system check to ensure you don't *remove* a provider from `settings.py` that is still in use in your database.
244
- You do need to specify the `--database` for this to run when using the check command by itself:
245
-
246
- ```sh
247
- python manage.py check --database default
248
- ```
249
-
250
- ## FAQs
251
-
252
- ### How is this different from [Django OAuth libraries](https://djangopackages.org/grids/g/oauth/)?
253
-
254
- The short answer is that *it does less*.
255
-
256
- In [django-allauth](https://github.com/pennersr/django-allauth)
257
- (maybe the most popular alternative)
258
- you get all kinds of other features like managing multiple email addresses,
259
- email verification,
260
- a long list of supported providers,
261
- and a whole suite of forms/urls/views/templates/signals/tags.
262
- And in my experience,
263
- it's too much.
264
- It often adds more complexity to your app than you actually need (or want) and honestly it can just be a lot to wrap your head around.
265
- Personally, I don't like the way that your OAuth settings are stored in the database vs when you use `settings.py`,
266
- and the implications for doing it one way or another.
267
-
268
- The other popular OAuth libraries have similar issues,
269
- and I think their *weight* outweighs their usefulness for 80% of the use cases.
270
-
271
- ### Why aren't providers included in the library itself?
272
-
273
- One thing you'll notice is that we don't have a long list of pre-configured providers in this library.
274
- Instead, we have some examples (which you can usually just copy, paste, and use) and otherwise encourage you to wire up the provider yourself.
275
- Often times all this means is finding the two OAuth URLs ("oauth/authorize" and "oauth/token") in their docs,
276
- and writing two class methods that do the actual work of getting the user's data (which is often customized anyway).
277
-
278
- We've written examples for the following providers:
279
-
280
- - [GitHub](https://github.com/forgepackages/plain-oauth/tree/master/provider_examples/github.py)
281
- - [GitLab](https://github.com/forgepackages/plain-oauth/tree/master/provider_examples/gitlab.py)
282
- - [Bitbucket](https://github.com/forgepackages/plain-oauth/tree/master/provider_examples/bitbucket.py)
283
-
284
- Just copy that code and paste it in your project.
285
- Tweak as necessary!
286
-
287
- This might sound strange at first.
288
- But in the long run we think it's actually *much* more maintainable for both us (as library authors) and you (as app author).
289
- If something breaks with a provider, you can fix it immediately!
290
- You don't need to try to run changes through us or wait for an upstream update.
291
- You're welcome to contribute an example to this repo,
292
- and there won't be an expectation that it "works perfectly for every use case until the end of time".
293
-
294
- ### Redirect/callback URL mismatch in local development?
295
-
296
- If you're doing local development through a proxy/tunnel like [ngrok](https://ngrok.com/),
297
- then the callback URL might be automatically built as `http` instead of `https`.
298
-
299
- This is the Django setting you're probably looking for:
300
-
301
- ```python
302
- HTTPS_PROXY_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
303
- ```
File without changes
File without changes