djvg
djvg

Reputation: 14255

Is there any way to force authentication with QNetworkAccessManager in PyQt4/5?

In my implementation, it seems as if QNetworkAccessManager caches the credentials used for a specific url, after successful authentication.

A subsequent request to the same url using different credentials will succeed, but only because it uses the cached credentials instead of the new ones (the authenticationRequired signal is not emitted).

Is there some way in (Py)Qt4 (also PyQt 5.6) to clear the cache or otherwise force an update of the cached credentials? Should I just delete the manager and construct a new one? Or is there something else I am doing wrong?

Qt 5 offers a clearAccessCache() method, but Qt 4 does not, as far as I can see.

This small example illustrates the problem:

import sys
import json
from PyQt4 import QtNetwork, QtGui, QtCore

def show_reply_content(reply):
    content = json.loads(str(reply.readAll()))
    if 'authenticated' in content and content['authenticated']:
        print 'authentication successful for user {}'.format(
            content['user'])
        reply.manager().replies_authenticated += 1

    # Quit when all replies are finished
    reply.deleteLater()
    reply.manager().replies_unfinished -= 1
    if not reply.manager().replies_unfinished:
        print 'number of successful authentications: {}'.format(
            reply.manager().replies_authenticated)
        app.quit()


def provide_credentials(reply, authenticator):
    print 'Setting credentials for {}:\nusername: {}\n' \
          'password: {}\n\n'.format(reply.url(),
                                    reply.credentials['username'],
                                    reply.credentials['password'])
    authenticator.setUser(reply.credentials['username'])
    authenticator.setPassword(reply.credentials['password'])

# Some initialization
app = QtGui.QApplication(sys.argv)
manager = QtNetwork.QNetworkAccessManager()
manager.finished.connect(show_reply_content)
manager.authenticationRequired.connect(provide_credentials)
manager.replies_unfinished = 0
manager.replies_authenticated = 0

# Specify credentials
all_credentials = [dict(username='aap',
                        password='njkrfnq'),
                   dict(username='noot',
                        password='asdfber')]

# url authenticates successfully only for the specified credentials
url = 'http://httpbin.org/basic-auth/{}/{}'.format(
    *all_credentials[1].values())

# Schedule requests
replies = []
for credentials in all_credentials:
    replies.append(
        manager.get(QtNetwork.QNetworkRequest(QtCore.QUrl(url))))

    # Add credentials as dynamic attribute
    replies[-1].credentials = credentials

    manager.replies_unfinished += 1

# Start event loop
app.exec_()

As soon as one request is successfully authenticated, the next one is as well, even though it should not be.

Upvotes: 2

Views: 996

Answers (1)

djvg
djvg

Reputation: 14255

As it turns out there are several similar Qt bug reports that are still open: QTBUG-16835, QTBUG-30433...

I guess it should be possible to use QNetworkRequest.AuthenticationReuseAttribute, but I could not make that work with multiple requests to the same url with different (valid) sets of credentials.

A possible workaround would be to forget all about the authenticationRequired signal/slot, and add a raw header (RFC 7617) to each request, exactly as described here.

In the example that would mean removing the authenticationRequired signal/slot connection and replacing the manager.get() part by the following lines (for Python 2.7 with PyQt4):

request = QtNetwork.QNetworkRequest(QtCore.QUrl(url))
header_data = QtCore.QByteArray('{}:{}'.format(
    credentials['username'], credentials['password'])).toBase64()
request.setRawHeader('Authorization', 'Basic {}'.format(header_data))
replies.append(manager.get(request))

However, as @ekhumoro pointed out in the comments above: not sure how safe this is.

Upvotes: 2

Related Questions