From d94f7a498840b4b7ac7785e76d95d3c0d185a112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Wed, 26 Aug 2020 15:37:15 +0200 Subject: [PATCH] Client unit tests --- TODO.md | 2 +- tests/conftest.py | 20 ++++++++ tests/test_admin.py | 84 ++++++++++++++++++++++++++++++++++ web/clients.py | 10 ++-- web/flaskutils.py | 4 +- web/models.py | 1 + web/oauth.py | 2 +- web/templates/client_edit.html | 26 ++++++----- 8 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 tests/test_admin.py diff --git a/TODO.md b/TODO.md index afd7fb59..46747ff0 100644 --- a/TODO.md +++ b/TODO.md @@ -4,5 +4,5 @@ - User "Manage my permissions" screen - Admin tokens screen - Admin codes screen -- Admin client screen unit tests - Limit login attempts by time interval +- Cleanup LDAP connections diff --git a/tests/conftest.py b/tests/conftest.py index cd9661d1..7d91b2de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -87,6 +87,7 @@ def app(slapd_server): "BIND_DN": slapd_server.root_dn, "BIND_PW": slapd_server.root_pw, "USER_FILTER": "(|(uid={login})(cn={login}))", + "ADMIN_FILTER": "uid=admin", }, "JWT": { "KEY": "secret-key", @@ -155,6 +156,18 @@ def user(app, slapd_connection): return u +@pytest.fixture +def admin(app, slapd_connection): + u = User( + cn="Jane Doe", + sn="Doe", + uid="admin", + userpassword="{SSHA}Vmgh2jkD0idX3eZHf8RzGos31oerjGiU", + ) + u.save(slapd_connection) + return u + + @pytest.fixture def token(slapd_connection, client, user): t = Token( @@ -176,3 +189,10 @@ def logged_user(user, testclient): with testclient.session_transaction() as sess: sess["user_dn"] = user.dn return user + + +@pytest.fixture +def logged_admin(admin, testclient): + with testclient.session_transaction() as sess: + sess["user_dn"] = admin.dn + return admin diff --git a/tests/test_admin.py b/tests/test_admin.py new file mode 100644 index 00000000..6b7ed7eb --- /dev/null +++ b/tests/test_admin.py @@ -0,0 +1,84 @@ +from web.models import Client + + +def test_no_logged_no_access(testclient): + testclient.get("/client", status=403) + + +def test_no_admin_no_access(testclient, logged_user): + testclient.get("/client", status=403) + + +def test_client_list(testclient, client, logged_admin): + res = testclient.get("/client") + assert client.oauthClientName in res.text + + +def test_client_add(testclient, logged_admin, slapd_connection): + res = testclient.get("/client/add") + data = { + "oauthClientName": "foobar", + "oauthClientContact": "foo@bar.com", + "oauthClientURI": "https://foo.bar", + "oauthRedirectURIs": ["https:/foo.bar/callback"], + "oauthGrantType": ["password", "authorization_code"], + "oauthScope": "openid profile", + "oauthResponseType": ["code", "token"], + "oauthTokenEndpointAuthMethod": "none", + "oauthLogoURI": "https://foo.bar/logo.png", + "oauthTermsOfServiceURI": "https://foo.bar/tos", + "oauthPolicyURI": "https://foo.bar/policy", + "oauthSoftwareID": "software", + "oauthSoftwareVersion": "1", + "oauthJWK": "jwk", + "oauthJWKURI": "https://foo.bar/jwks.json", + } + for k, v in data.items(): + res.form[k] = v + res = res.form.submit() + + assert 302 == res.status_code + res = res.follow() + + assert 200 == res.status_code + client_id = res.forms["readonly"]["oauthClientID"].value + client = Client.get(client_id, conn=slapd_connection) + for k, v in data.items(): + client_value = getattr(client, k) + if k == "oauthScope": + assert v == " ".join(client_value) + else: + assert v == client_value + + +def test_client_edit(testclient, client, logged_admin, slapd_connection): + res = testclient.get("/client/edit/" + client.oauthClientID) + data = { + "oauthClientName": "foobar", + "oauthClientContact": "foo@bar.com", + "oauthClientURI": "https://foo.bar", + "oauthRedirectURIs": ["https:/foo.bar/callback"], + "oauthGrantType": ["password", "authorization_code"], + "oauthScope": "openid profile", + "oauthResponseType": ["code", "token"], + "oauthTokenEndpointAuthMethod": "none", + "oauthLogoURI": "https://foo.bar/logo.png", + "oauthTermsOfServiceURI": "https://foo.bar/tos", + "oauthPolicyURI": "https://foo.bar/policy", + "oauthSoftwareID": "software", + "oauthSoftwareVersion": "1", + "oauthJWK": "jwk", + "oauthJWKURI": "https://foo.bar/jwks.json", + } + for k, v in data.items(): + res.forms["clientadd"][k] = v + res = res.forms["clientadd"].submit() + + assert 200 == res.status_code + client.reload(conn=slapd_connection) + for k, v in data.items(): + client_value = getattr(client, k) + if k == "oauthScope": + assert v == " ".join(client_value) + else: + assert v == client_value diff --git a/web/clients.py b/web/clients.py index ca634056..552c6b0c 100644 --- a/web/clients.py +++ b/web/clients.py @@ -20,7 +20,7 @@ def index(): class ClientAdd(FlaskForm): - oauthClientName = wtforms.TextField( + oauthClientName = wtforms.StringField( gettext("Name"), validators=[wtforms.validators.DataRequired()], render_kw={"placeholder": "Client Name"}, @@ -52,7 +52,7 @@ class ClientAdd(FlaskForm): ], default=["authorization_code", "refresh_token"], ) - oauthScope = wtforms.TextField( + oauthScope = wtforms.StringField( gettext("Scope"), validators=[wtforms.validators.Optional()], default="openid profile", @@ -89,17 +89,17 @@ class ClientAdd(FlaskForm): validators=[wtforms.validators.Optional()], render_kw={"placeholder": "https://mydomain.tld/policy.html"}, ) - oauthSoftwareID = wtforms.TextField( + oauthSoftwareID = wtforms.StringField( gettext("Software ID"), validators=[wtforms.validators.Optional()], render_kw={"placeholder": "xyz"}, ) - oauthSoftwareVersion = wtforms.TextField( + oauthSoftwareVersion = wtforms.StringField( gettext("Software Version"), validators=[wtforms.validators.Optional()], render_kw={"placeholder": "1.0"}, ) - oauthJWK = wtforms.TextField( + oauthJWK = wtforms.StringField( gettext("JWK"), validators=[wtforms.validators.Optional()], render_kw={"placeholder": ""}, diff --git a/web/flaskutils.py b/web/flaskutils.py index 7f103f2e..fb340c48 100644 --- a/web/flaskutils.py +++ b/web/flaskutils.py @@ -27,10 +27,8 @@ def admin_needed(): @wraps(view_function) def decorator(*args, **kwargs): user = current_user() - if not user: + if not user or not user.admin: abort(403) - if not user.admin: - abort(404) return view_function(*args, **kwargs) return decorator diff --git a/web/models.py b/web/models.py index dfaab0e5..6eb5db6a 100644 --- a/web/models.py +++ b/web/models.py @@ -26,6 +26,7 @@ class User(LDAPObjectHelper): if ( admin_filter and user + and user.dn and conn.search_s(user.dn, ldap.SCOPE_SUBTREE, admin_filter) ): diff --git a/web/oauth.py b/web/oauth.py index 63bd9a10..a9b14f31 100644 --- a/web/oauth.py +++ b/web/oauth.py @@ -71,7 +71,7 @@ def revoke_token(): @bp.route("/jwks.json") def jwks(): - #TODO: Do not share secrets here! + # TODO: Do not share secrets here! key = urlsafe_b64encode(current_app.config["JWT"]["KEY"].encode("utf-8")).decode( "utf-8" ) diff --git a/web/templates/client_edit.html b/web/templates/client_edit.html index 1259a1ef..e83cb93c 100644 --- a/web/templates/client_edit.html +++ b/web/templates/client_edit.html @@ -15,18 +15,20 @@ {% endwith %}
-
- - -
-
- - -
-
- - -
+
+
+ + +
+
+ + +
+
+ + +
+
{{ sui.render_form(form, _("Confirm")) }}