udata 9.1.5.dev31504__py2.py3-none-any.whl → 9.1.5.dev31515__py2.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.
Potentially problematic release.
This version of udata might be problematic. Click here for more details.
- udata/api/commands.py +10 -2
- udata/api/oauth2.py +4 -3
- udata/tests/api/test_auth_api.py +88 -0
- {udata-9.1.5.dev31504.dist-info → udata-9.1.5.dev31515.dist-info}/METADATA +2 -1
- {udata-9.1.5.dev31504.dist-info → udata-9.1.5.dev31515.dist-info}/RECORD +9 -9
- {udata-9.1.5.dev31504.dist-info → udata-9.1.5.dev31515.dist-info}/LICENSE +0 -0
- {udata-9.1.5.dev31504.dist-info → udata-9.1.5.dev31515.dist-info}/WHEEL +0 -0
- {udata-9.1.5.dev31504.dist-info → udata-9.1.5.dev31515.dist-info}/entry_points.txt +0 -0
- {udata-9.1.5.dev31504.dist-info → udata-9.1.5.dev31515.dist-info}/top_level.txt +0 -0
udata/api/commands.py
CHANGED
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import click
|
|
5
5
|
from flask import current_app, json
|
|
6
6
|
from flask_restx import schemas
|
|
7
|
+
from werkzeug.security import gen_salt
|
|
7
8
|
|
|
8
9
|
from udata.api import api
|
|
9
10
|
from udata.api.oauth2 import OAuth2Client
|
|
@@ -77,12 +78,15 @@ def validate():
|
|
|
77
78
|
@click.option(
|
|
78
79
|
"-r", "--response-types", multiple=True, default=["code"], help="Client's response types"
|
|
79
80
|
)
|
|
80
|
-
|
|
81
|
+
@click.option("-p", "--public", is_flag=True, help="Public client (SPA)")
|
|
82
|
+
def create_oauth_client(client_name, user_email, uri, grant_types, scope, response_types, public):
|
|
81
83
|
"""Creates an OAuth2Client instance in DB"""
|
|
82
84
|
user = User.objects(email=user_email).first()
|
|
83
85
|
if user is None:
|
|
84
86
|
exit_with_error("No matching user to email")
|
|
85
87
|
|
|
88
|
+
client_secret = gen_salt(50) if not public else None
|
|
89
|
+
|
|
86
90
|
client = OAuth2Client.objects.create(
|
|
87
91
|
name=client_name,
|
|
88
92
|
owner=user,
|
|
@@ -90,11 +94,15 @@ def create_oauth_client(client_name, user_email, uri, grant_types, scope, respon
|
|
|
90
94
|
scope=scope,
|
|
91
95
|
response_types=response_types,
|
|
92
96
|
redirect_uris=uri,
|
|
97
|
+
secret=client_secret,
|
|
93
98
|
)
|
|
94
99
|
|
|
95
100
|
click.echo(f"New OAuth client: {client.name}")
|
|
96
101
|
click.echo(f"Client's ID {client.id}")
|
|
97
|
-
|
|
102
|
+
if public:
|
|
103
|
+
click.echo("Client is public and has no secret.")
|
|
104
|
+
else:
|
|
105
|
+
click.echo(f"Client's secret {client.secret}")
|
|
98
106
|
click.echo(f"Client's grant_types {client.grant_types}")
|
|
99
107
|
click.echo(f"Client's response_types {client.response_types}")
|
|
100
108
|
click.echo(f"Client's URI {client.redirect_uris}")
|
udata/api/oauth2.py
CHANGED
|
@@ -32,7 +32,6 @@ from bson import ObjectId
|
|
|
32
32
|
from flask import current_app, render_template, request
|
|
33
33
|
from flask_security.utils import verify_password
|
|
34
34
|
from werkzeug.exceptions import Unauthorized
|
|
35
|
-
from werkzeug.security import gen_salt
|
|
36
35
|
|
|
37
36
|
from udata.app import csrf
|
|
38
37
|
from udata.auth import current_user, login_required, login_user
|
|
@@ -60,7 +59,7 @@ SCOPES = {"default": _("Default scope"), "admin": _("System administrator rights
|
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
class OAuth2Client(ClientMixin, db.Datetimed, db.Document):
|
|
63
|
-
secret = db.StringField(default=
|
|
62
|
+
secret = db.StringField(default=None)
|
|
64
63
|
|
|
65
64
|
name = db.StringField(required=True)
|
|
66
65
|
description = db.StringField()
|
|
@@ -209,7 +208,7 @@ class OAuth2Code(db.Document):
|
|
|
209
208
|
|
|
210
209
|
|
|
211
210
|
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
|
|
212
|
-
TOKEN_ENDPOINT_AUTH_METHODS = ["client_secret_basic", "client_secret_post"]
|
|
211
|
+
TOKEN_ENDPOINT_AUTH_METHODS = ["none", "client_secret_basic", "client_secret_post"]
|
|
213
212
|
|
|
214
213
|
def save_authorization_code(self, code, request):
|
|
215
214
|
code_challenge = request.data.get("code_challenge")
|
|
@@ -265,6 +264,8 @@ class RefreshTokenGrant(grants.RefreshTokenGrant):
|
|
|
265
264
|
|
|
266
265
|
|
|
267
266
|
class RevokeToken(RevocationEndpoint):
|
|
267
|
+
CLIENT_AUTH_METHODS = ["none", "client_secret_basic"]
|
|
268
|
+
|
|
268
269
|
def query_token(self, token_string, token_type_hint):
|
|
269
270
|
qs = OAuth2Token.objects()
|
|
270
271
|
if token_type_hint == "access_token":
|
udata/tests/api/test_auth_api.py
CHANGED
|
@@ -59,6 +59,7 @@ def oauth(app, request):
|
|
|
59
59
|
name="test-client",
|
|
60
60
|
owner=UserFactory(),
|
|
61
61
|
redirect_uris=["https://test.org/callback"],
|
|
62
|
+
secret="suchs3cr3t",
|
|
62
63
|
)
|
|
63
64
|
kwargs.update(custom_kwargs)
|
|
64
65
|
return OAuth2Client.objects.create(**kwargs)
|
|
@@ -456,6 +457,93 @@ class APIAuthTest:
|
|
|
456
457
|
assert response.content_type == "application/json"
|
|
457
458
|
assert response.json == {"success": True}
|
|
458
459
|
|
|
460
|
+
@pytest.mark.oauth(secret=None)
|
|
461
|
+
def test_s256_code_challenge_success_no_client_secret(self, client, oauth):
|
|
462
|
+
"""Authenticate through an OAuth client that has no secret associated (public client)"""
|
|
463
|
+
code_verifier = generate_token(48)
|
|
464
|
+
code_challenge = create_s256_code_challenge(code_verifier)
|
|
465
|
+
|
|
466
|
+
client.login()
|
|
467
|
+
|
|
468
|
+
response = client.post(
|
|
469
|
+
url_for(
|
|
470
|
+
"oauth.authorize",
|
|
471
|
+
response_type="code",
|
|
472
|
+
client_id=oauth.client_id,
|
|
473
|
+
code_challenge=code_challenge,
|
|
474
|
+
code_challenge_method="S256",
|
|
475
|
+
),
|
|
476
|
+
{
|
|
477
|
+
"scope": "default",
|
|
478
|
+
"accept": "",
|
|
479
|
+
},
|
|
480
|
+
)
|
|
481
|
+
assert "code=" in response.location
|
|
482
|
+
|
|
483
|
+
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
484
|
+
code = params["code"]
|
|
485
|
+
|
|
486
|
+
response = client.post(
|
|
487
|
+
url_for("oauth.token"),
|
|
488
|
+
{
|
|
489
|
+
"grant_type": "authorization_code",
|
|
490
|
+
"code": code,
|
|
491
|
+
"code_verifier": code_verifier,
|
|
492
|
+
"client_id": oauth.client_id,
|
|
493
|
+
},
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
assert200(response)
|
|
497
|
+
assert response.content_type == "application/json"
|
|
498
|
+
assert "access_token" in response.json
|
|
499
|
+
|
|
500
|
+
token = response.json["access_token"]
|
|
501
|
+
|
|
502
|
+
response = client.post(
|
|
503
|
+
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", token])}
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
assert200(response)
|
|
507
|
+
assert response.content_type == "application/json"
|
|
508
|
+
assert response.json == {"success": True}
|
|
509
|
+
|
|
510
|
+
def test_s256_code_challenge_missing_client_secret(self, client, oauth):
|
|
511
|
+
"""Fail authentication through an OAuth client with missing secret"""
|
|
512
|
+
code_verifier = generate_token(48)
|
|
513
|
+
code_challenge = create_s256_code_challenge(code_verifier)
|
|
514
|
+
|
|
515
|
+
client.login()
|
|
516
|
+
|
|
517
|
+
response = client.post(
|
|
518
|
+
url_for(
|
|
519
|
+
"oauth.authorize",
|
|
520
|
+
response_type="code",
|
|
521
|
+
client_id=oauth.client_id,
|
|
522
|
+
code_challenge=code_challenge,
|
|
523
|
+
code_challenge_method="S256",
|
|
524
|
+
),
|
|
525
|
+
{
|
|
526
|
+
"scope": "default",
|
|
527
|
+
"accept": "",
|
|
528
|
+
},
|
|
529
|
+
)
|
|
530
|
+
assert "code=" in response.location
|
|
531
|
+
|
|
532
|
+
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
533
|
+
code = params["code"]
|
|
534
|
+
|
|
535
|
+
response = client.post(
|
|
536
|
+
url_for("oauth.token"),
|
|
537
|
+
{
|
|
538
|
+
"grant_type": "authorization_code",
|
|
539
|
+
"code": code,
|
|
540
|
+
"code_verifier": code_verifier,
|
|
541
|
+
"client_id": oauth.client_id,
|
|
542
|
+
},
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
assert401(response)
|
|
546
|
+
|
|
459
547
|
def test_authorization_multiple_grant_token(self, client, oauth):
|
|
460
548
|
for i in range(3):
|
|
461
549
|
client.login()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: udata
|
|
3
|
-
Version: 9.1.5.
|
|
3
|
+
Version: 9.1.5.dev31515
|
|
4
4
|
Summary: Open data portal
|
|
5
5
|
Home-page: https://github.com/opendatateam/udata
|
|
6
6
|
Author: Opendata Team
|
|
@@ -140,6 +140,7 @@ It is collectively taken care of by members of the
|
|
|
140
140
|
|
|
141
141
|
## Current (in progress)
|
|
142
142
|
|
|
143
|
+
- Allow OAuth clients without secrets [#3138](https://github.com/opendatateam/udata/pull/3138)
|
|
143
144
|
- Add a `archived` button for datasets and reuses on frontend admin [#3104](https://github.com/opendatateam/udata/pull/3104)
|
|
144
145
|
- **breaking change** Return all the reuses available to a user on the /reuses endpoint, including the private and deleted ones they own [#3140](https://github.com/opendatateam/udata/pull/3140).
|
|
145
146
|
- Fix undelete reuse and dataservices [#3141](https://github.com/opendatateam/udata/pull/3141)
|
|
@@ -26,10 +26,10 @@ udata/wsgi.py,sha256=MY8en9K9eDluvJYUxTdzqSDoYaDgCVZ69ZcUvxAvgqA,77
|
|
|
26
26
|
udata/admin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
udata/admin/views.py,sha256=00fI1O55apOlwIc0li2BdvEEGMd5l5isd5A_eHvzu3Y,331
|
|
28
28
|
udata/api/__init__.py,sha256=p2FcJ1I4k4HERFvYYQNitO23FnfBc-kXpMlWb7u__RM,10644
|
|
29
|
-
udata/api/commands.py,sha256=
|
|
29
|
+
udata/api/commands.py,sha256=LJ4u-yhGij7QuH7OR92u3n7BEptqWMjDwMnG8pqdBxo,3523
|
|
30
30
|
udata/api/errors.py,sha256=P_UigBf6HAL73LbXKULagmp5Cuw-H1Ms6LmXxOmFIeg,211
|
|
31
31
|
udata/api/fields.py,sha256=BO2bHPWN6CT2bNpEkkosBGVUSV383nl8MUed_wlxLmw,4176
|
|
32
|
-
udata/api/oauth2.py,sha256=
|
|
32
|
+
udata/api/oauth2.py,sha256=_sHQjcj67y601vXVhw6DAGJeZcj8itSx1myhsnFcNk4,12025
|
|
33
33
|
udata/api/parsers.py,sha256=iQEwgMwdVz_gXlVZYaRCGrUFWvcf7_8uCIxw7Iw-hpw,1405
|
|
34
34
|
udata/api/signals.py,sha256=t8s7tF1KYeAygqbR4GdA76_UYvthAsyRbJaqsMmFsK4,162
|
|
35
35
|
udata/auth/__init__.py,sha256=RRMYs8rYIwXncx3Z62NFtVhuQzxQrFQpC1lBhl68WJI,1992
|
|
@@ -598,7 +598,7 @@ udata/tests/test_uris.py,sha256=MxafZ0SyzSNRomVpZnH1ChzWaHOi1MQiXe1gmKnBc6o,8517
|
|
|
598
598
|
udata/tests/test_utils.py,sha256=3BGnlvw-GOE6tLHQteo-uUeYuzq4rsjePOuytFGkpOg,7967
|
|
599
599
|
udata/tests/api/__init__.py,sha256=y4sL7LD1-KwONHF0q_Rhk2W6BmGUlp7Uz2JnX3e27sk,1218
|
|
600
600
|
udata/tests/api/test_activities_api.py,sha256=RjDDeNle3T-ydVnh6BRypqxAE_244-DXaKkuUCT0HVU,2823
|
|
601
|
-
udata/tests/api/test_auth_api.py,sha256=
|
|
601
|
+
udata/tests/api/test_auth_api.py,sha256=OMRlY0OQt60j5N4A-N3HdWTuffOjRlFHkz5a3jJFieI,25987
|
|
602
602
|
udata/tests/api/test_base_api.py,sha256=2w_vz0eEuq3P3aN-ByvxGc3VZAo7XtgatFfcrzf2uEU,2244
|
|
603
603
|
udata/tests/api/test_contact_points.py,sha256=pyakkKnM4lH_asuEoXq9lQyJC9to6ZxSp4QQrHh1c1U,1041
|
|
604
604
|
udata/tests/api/test_dataservices_api.py,sha256=SoiAOv3q0z41iEYXkZwnvr9KzULtfD_MlIQkLcfdf-E,15337
|
|
@@ -697,9 +697,9 @@ udata/translations/pt/LC_MESSAGES/udata.mo,sha256=WpPzAqVd2Onv_kz45ULUySKPLrpjcc
|
|
|
697
697
|
udata/translations/pt/LC_MESSAGES/udata.po,sha256=18Op9RUITewoDRewlOdYzzq6gjsf1lsvepACV1d7zxs,44976
|
|
698
698
|
udata/translations/sr/LC_MESSAGES/udata.mo,sha256=NIYRNhVoETZUvIvWm3cCW7DtMBAnS2vXzZjMF5ZzD_c,28500
|
|
699
699
|
udata/translations/sr/LC_MESSAGES/udata.po,sha256=rQB-4V4WJ7bURj6g2j653vItr5TMHadcLQxec7_fDmg,51545
|
|
700
|
-
udata-9.1.5.
|
|
701
|
-
udata-9.1.5.
|
|
702
|
-
udata-9.1.5.
|
|
703
|
-
udata-9.1.5.
|
|
704
|
-
udata-9.1.5.
|
|
705
|
-
udata-9.1.5.
|
|
700
|
+
udata-9.1.5.dev31515.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
|
|
701
|
+
udata-9.1.5.dev31515.dist-info/METADATA,sha256=o0DNHKHXEyq3K4H2-5D3AeNBdxE1w7BvjSYs1fW-KP8,130737
|
|
702
|
+
udata-9.1.5.dev31515.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
|
|
703
|
+
udata-9.1.5.dev31515.dist-info/entry_points.txt,sha256=3SKiqVy4HUqxf6iWspgMqH8d88Htk6KoLbG1BU-UddQ,451
|
|
704
|
+
udata-9.1.5.dev31515.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
|
|
705
|
+
udata-9.1.5.dev31515.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|