3

I've got a flask app that implements a REST api. For reasons, I'm using HTTP Digest Authentication. I've used the Flask-HTTPAuth library to implement the digest authentication and it works; however, I am unable to authenticate in the unit tests.

For the unit tests, prior to setting up the authentication, I'm doing something like this:

class FooTestCase(unittest.TestCase):
    def setUp(self):
        self.app = foo.app.test_client()

    def test_root(self):
        response = self.app.get('/')
        # self.assert.... blah blah blah

Prior to implementing the authentication, this was fine. Now I get a 401, which is expected as the initial response for a digest auth request. I've searched and searched and followed a few suggestions related to http basic auth (using parameters data = { #various stuff} and follow_redirects=True) but I've had no success.

Does anyone have a clue how to implement unittests in this case?

Mike Lane
  • 1,134
  • 7
  • 19

2 Answers2

6

Unfortunately digest authentication is harder to test or bypass in Flask-HTTPAuth.

One option is to actually calculate the correct hashes and perform the full authentication during tests. You can see a few examples of this in the Flask-HTTPAuth unit tests. Here is one:

def test_digest_auth_login_valid(self):
    response = self.client.get('/digest')
    self.assertTrue(response.status_code == 401)
    header = response.headers.get('WWW-Authenticate')
    auth_type, auth_info = header.split(None, 1)
    d = parse_dict_header(auth_info)

    a1 = 'john:' + d['realm'] + ':bye'
    ha1 = md5(a1).hexdigest()
    a2 = 'GET:/digest'
    ha2 = md5(a2).hexdigest()
    a3 = ha1 + ':' + d['nonce'] + ':' + ha2
    auth_response = md5(a3).hexdigest()

    response = self.client.get(
        '/digest', headers={
            'Authorization': 'Digest username="john",realm="{0}",'
                             'nonce="{1}",uri="/digest",response="{2}",'
                             'opaque="{3}"'.format(d['realm'],
                                                   d['nonce'],
                                                   auth_response,
                                                   d['opaque'])})
    self.assertEqual(response.data, b'digest_auth:john')

In this example, the username is john and the password is bye. Presumably you have some predetermined credentials for a user that can be used in unit tests, so you would plug those in the a1 variable above. This authentication dance can be included in an auxiliary function that wraps the sending of a request during tests, that way you don't have to repeat this in every test.

Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152
  • Yup. I need to make the request, get the 401, construct the auth digest headers based on the return (which I can find in the response object in pycharm). Just like the code you copied and pasted is doing... On the money, thanks. – Mike Lane Aug 15 '16 at 03:41
0

It's not neccessary to use real-world authentication in tests. Either disable authentication when running tests or create test user and use this test user in tests.

For example:

    @digest_auth.get_password
    def get_pw(username):
        if running_from_tests():
            if username == 'test':
                return 'testpw'
            else:
                return None
        else:
            # ... normal auth that you have

Of couse this test user must never be active in production :) For implementation of running_from_tests() see Test if code is executed from within a py.test session (if you are using py.test).

Community
  • 1
  • 1
Messa
  • 24,321
  • 6
  • 68
  • 92
  • Thanks for the answer. I'm all set with setting up a test user and getting the password. I can do that right from the DB. My issue is with the test_client().get() function. If I were to run the flask server, and then from a different python function if I were to use the requests library, I can do something like this: from requests.auth import HTTPDigestAuth result = requests.get('http://127.0.0.1:5000/', auth=HTTPDigestAuth('test', 'mytestpw')) That'll utilize the digest auth set up on the server to log in just fine. But how do I do that process in test_client().get()? – Mike Lane Aug 12 '16 at 21:31
  • Sorry, just looked up formatting in comments. The code from above if not clear: `from requests.auth import HTTPDigestAuth result = requests.get('127.0.0.1:5000/', auth=HTTPDigestAuth('test', 'mytestpw'))` – Mike Lane Aug 12 '16 at 21:38
  • thanks for taking the time to search. I have run across that a few times during my search but I'm unclear how to make this work for digest auth. With [basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication), a handshake is not required and simply sending the authenticate in the header as that code is doing works fine. With [digest auth](https://en.wikipedia.org/wiki/Digest_access_authentication#Example_with_explanation), a handshake is required. .... Oh... For Pete's sake. Of course. I'll get the response with all the info I need to do the calculations myself. derp. – Mike Lane Aug 12 '16 at 23:48