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 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
- def create_oauth_client(client_name, user_email, uri, grant_types, scope, response_types):
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
- click.echo(f"Client's secret {client.secret}")
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=lambda: gen_salt(50))
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":
@@ -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.dev31504
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=SwZYuANyGoI-lplWg18d0Ej8e7znmIA5EUKIU0jBA6U,3227
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=4emtZKqVoNrWBNQsg1AEW-K5BMcQabMp7GCY3sF6EO4,12013
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=buWlvWOKLhEH-hS_bDyHePyyitRg8fX86hYy2Euo6AY,23200
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.dev31504.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
701
- udata-9.1.5.dev31504.dist-info/METADATA,sha256=Gb-WpN-DaXnNzJ7rQhx0kM687zUBsoa7TEqvBDDiuoM,130642
702
- udata-9.1.5.dev31504.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
703
- udata-9.1.5.dev31504.dist-info/entry_points.txt,sha256=3SKiqVy4HUqxf6iWspgMqH8d88Htk6KoLbG1BU-UddQ,451
704
- udata-9.1.5.dev31504.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
705
- udata-9.1.5.dev31504.dist-info/RECORD,,
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,,