From 55addfc68f26f50425e3af84972f4d342a99939f Mon Sep 17 00:00:00 2001 From: Marcello Stanisci Date: Thu, 9 Jan 2020 20:18:59 +0100 Subject: Switching to HTTP basic authentication. --- talerbank/app/tests.py | 67 +++++++++++++++++++++----------------------------- talerbank/app/views.py | 32 +++++++++++++----------- 2 files changed, 46 insertions(+), 53 deletions(-) (limited to 'talerbank/app') diff --git a/talerbank/app/tests.py b/talerbank/app/tests.py index 692577a..87d7283 100644 --- a/talerbank/app/tests.py +++ b/talerbank/app/tests.py @@ -23,6 +23,7 @@ import zlib import timeit import logging import unittest +import base64 from urllib.parse import unquote from django.db import connection from django.test import TestCase, Client @@ -42,6 +43,12 @@ LOGGER.setLevel(logging.INFO) logging.disable(logging.CRITICAL) # reenable: logging.disable(logging.NOTSET) +def make_auth_line(username, password): + credentials = "%s:%s" % (username, password) + b64enc = base64.b64encode(bytes(credentials, "utf-8")) + header_line = "Basic %s" % b64enc.decode() + return header_line + def clear_db(): User.objects.all().delete() BankAccount.objects.all().delete() @@ -259,8 +266,7 @@ class LoginTestCase(TestCase): def test_failing_login(self): response = self.client.get( reverse("history", urlconf=urls), {"auth": "basic"}, **{ - "HTTP_X_TALER_BANK_USERNAME": "Wrong", - "HTTP_X_TALER_BANK_PASSWORD": "Credentials" + "HTTP_AUTHORIZATION": make_auth_line("Wrong", "Credentials") } ) data = response.content.decode("utf-8") @@ -316,8 +322,7 @@ class RejectTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "rejected_user", - "HTTP_X_TALER_BANK_PASSWORD": "rejected_password" + "HTTP_AUTHORIZATION": make_auth_line("rejected_user", "rejected_password"), } ) self.assertEqual(response.status_code, 200) @@ -332,8 +337,9 @@ class RejectTestCase(TestCase): % (jdata["row_id"], rejected.bankaccount.account_no), content_type="application/json", - **{"HTTP_X_TALER_BANK_USERNAME": "rejecting_user", - "HTTP_X_TALER_BANK_PASSWORD": "rejecting_password"}) + **{ + "HTTP_AUTHORIZATION": make_auth_line("rejecting_user", "rejecting_password"), + }) self.assertEqual(response.status_code, 204) @@ -366,8 +372,8 @@ class WithdrawHeadlessTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "headless_wallet", - "HTTP_X_TALER_BANK_PASSWORD": "headless_password" + "HTTP_AUTHORIZATION": make_auth_line("headless_wallet", "headless_password") + } ) self.assertEqual(200, response.status_code) @@ -382,8 +388,7 @@ class WithdrawHeadlessTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "headless_wallet", - "HTTP_X_TALER_BANK_PASSWORD": "headless_password" + "HTTP_AUTHORIZATION": make_auth_line("headless_wallet", "headless_password") } ) self.assertEqual(406, response.status_code) @@ -399,8 +404,7 @@ class WithdrawHeadlessTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "headless_wallet", - "HTTP_X_TALER_BANK_PASSWORD": "headless_password" + "HTTP_AUTHORIZATION": make_auth_line("headless_wallet", "headless_password") } ) self.assertEqual(200, response.status_code) @@ -416,8 +420,7 @@ class WithdrawHeadlessTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "headless_wallet", - "HTTP_X_TALER_BANK_PASSWORD": "headless_password" + "HTTP_AUTHORIZATION": make_auth_line("headless_wallet", "headless_password") } ) self.assertEqual(404, response.status_code) @@ -432,8 +435,7 @@ class WithdrawHeadlessTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "headless_wallet", - "HTTP_X_TALER_BANK_PASSWORD": "headless_password" + "HTTP_AUTHORIZATION": make_auth_line("headless_wallet", "headless_password") } ) self.assertEqual(400, response.status_code) @@ -471,8 +473,7 @@ class AddIncomingTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "user_user", - "HTTP_X_TALER_BANK_PASSWORD": "user_password" + "HTTP_AUTHORIZATION": make_auth_line("user_user", "user_password") } ) self.assertEqual(200, response.status_code) @@ -485,8 +486,7 @@ class AddIncomingTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "user_user", - "HTTP_X_TALER_BANK_PASSWORD": "user_password", + "HTTP_AUTHORIZATION": make_auth_line("user_user", "user_password"), "HTTP_CONTENT_ENCODING": "deflate" } ) @@ -504,8 +504,7 @@ class AddIncomingTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "user_user", - "HTTP_X_TALER_BANK_PASSWORD": "user_password" + "HTTP_AUTHORIZATION": make_auth_line("user_user", "user_password") } ) # note: a bad currency request gets 400. @@ -525,8 +524,7 @@ class AddIncomingTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "user_user", - "HTTP_X_TALER_BANK_PASSWORD": "user_password" + "HTTP_AUTHORIZATION": make_auth_line("user_user", "user_password") } ) self.assertEqual(406, response.status_code) @@ -542,8 +540,7 @@ class AddIncomingTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "user_user", - "HTTP_X_TALER_BANK_PASSWORD": "user_password" + "HTTP_AUTHORIZATION": make_auth_line("user_user", "user_password") } ) self.assertEqual(404, response.status_code) @@ -595,8 +592,7 @@ class HistoryTestCase(TestCase): content_type="application/json", follow=True, **{ - "HTTP_X_TALER_BANK_USERNAME": "User0", - "HTTP_X_TALER_BANK_PASSWORD": "Password0" + "HTTP_AUTHORIZATION": make_auth_line("User0", "Password0") } ) @@ -657,8 +653,7 @@ class HistoryTestCase(TestCase): response = self.client.get( reverse("history-range", urlconf=urls), ctx.urlargs, **{ - "HTTP_X_TALER_BANK_USERNAME": "User", - "HTTP_X_TALER_BANK_PASSWORD": "Password" + "HTTP_AUTHORIZATION": make_auth_line("User", "Password") } ) @@ -735,11 +730,9 @@ class HistoryTestCase(TestCase): response = self.client.get( reverse("history", urlconf=urls), ctx.urlargs, **{ - "HTTP_X_TALER_BANK_USERNAME": "User", - "HTTP_X_TALER_BANK_PASSWORD": "Password" + "HTTP_AUTHORIZATION": make_auth_line("User", "Password") } ) - self.assert_result(response, ctx) @@ -759,12 +752,9 @@ class DBAmountSubtraction(TestCase): ) user_bankaccount.amount.subtract(Amount(settings.TALER_CURRENCY, 2)) self.assertEqual( - Amount.cmp( - Amount(settings.TALER_CURRENCY, 1), user_bankaccount.amount - ), 0 + Amount.cmp(Amount(settings.TALER_CURRENCY, 1), user_bankaccount.amount), 0 ) - class DBCustomColumnTestCase(TestCase): def setUp(self): BankAccount(user=User.objects.create_user(username='U')).save() @@ -954,8 +944,7 @@ class BalanceTestCase(TestCase): "account_number": 55 }, # unused **{ - "HTTP_X_TALER_BANK_USERNAME": "U0", - "HTTP_X_TALER_BANK_PASSWORD": "U0PASS" + "HTTP_AUTHORIZATION": make_auth_line("U0", "U0PASS") } ) data = response.content.decode("utf-8") diff --git a/talerbank/app/views.py b/talerbank/app/views.py index 33146ff..a6902be 100644 --- a/talerbank/app/views.py +++ b/talerbank/app/views.py @@ -25,6 +25,7 @@ import logging import hashlib import random import re +import base64 from urllib.parse import urlparse import django.contrib.auth import django.contrib.auth.views @@ -626,7 +627,7 @@ def serve_public_accounts(request, name=None, page=None): # @return FIXME. def login_via_headers(view_func): def _decorator(request, *args, **kwargs): - user_account = auth_and_login(request) + user_account = basic_auth(request) if not user_account: raise LoginFailed("authentication failed") return view_func(request, user_account, *args, **kwargs) @@ -814,24 +815,27 @@ def serve_history(request, user_account): return HttpResponse(status=204) return JsonResponse(dict(data=history), status=200) - ## -# Helper function that authenticates a user by fetching the -# credentials from the HTTP headers. Typically called from -# decorators. +# Implements the HTTP basic auth schema. # # @param request Django-specific HTTP request object. # @return Django-specific "authentication object". -def auth_and_login(request): - """Return user instance after checking authentication - credentials, False if errors occur""" - - username = request.META.get("HTTP_X_TALER_BANK_USERNAME") - password = request.META.get("HTTP_X_TALER_BANK_PASSWORD") - if not username or not password: - raise LoginFailed("missing user/password") - return django.contrib.auth.authenticate(username=username, password=password) +def basic_auth(request): + auth_header = request.META.get("HTTP_AUTHORIZATION") + + if not auth_header: + raise LoginFailed("missing Authorization header") + tokens = auth_header.split(" ") + if len(tokens) != 2: + raise LoginFailed("invalid Authorization header") + + # decode the base64 content. + if tokens[0] != "Basic": + raise LoginFailed("Not supporting '%s' authorization method" % tokens[0]) + + username, password = base64.b64decode(tokens[1]).decode("utf-8").split(":") + return django.contrib.auth.authenticate(username=username, password=password) ## # Serve a request of /reject (for rejecting wire transfers). -- cgit v1.2.3