Since there's a lot of code in common between this and requests/requests-kerberos, I believe requests-gssapi has some concurrency bugs as well.
requests/requests-kerberos#113 has details, and requests/requests-kerberos#114 has a fix which can be adapted.
The problem stems from how we index gssapi.SecurityContext objects by hostname. If you have multiple threads sharing an HTTPSPNEGOAuth object, and they both send requests to the same host:
- thread1 generates a
SecurityContext to authenticate a request destined to foo.com and caches it in self.context["foo.com"] and uses it to step().
- thread2 generates a
SecurityContext to authenticate another request, also destined to foo.com and caches it in self.context["foo.com"], overwriting the SecurityContext placed there by thread1, and uses it to step().
- thread1 receives a response, retrieves the
SecurityContext (generated by thread2) to authenticate the response from foo.com, attempts to step() with the token it received, and ...
At this point, we're likely to either get a mutual authentication exception because the SecurityContext was used for a different request, or it may happen to work due to implementation details (though probably not). And even if it did work, when thread2 receives its response and attempts to authenticate it, it's likely to get a mutual authentication exception because the SecurityContext was fully established.
And the potential is also there for there to be a concurrency issue within generate_request_header alone, since it always accesses the SecurityContext via the self.context dictionary. Depending on how frequently the interpreter re-schedules threads (and the GIL is released), it's possible for thread1 to generate its context, then have the interpreter schedule thread2 which will replace it with another context. Then whichever one calls step() on it first will succeed, and whichever one calls step() on it second will likely fail since they'll both use the same context since they're accessing it via the self.context dictionary.
The solution for this is to index by the request (or PreparedRequest) object, not the hostname in the url in the request as we currently do.
As the original author of the code in question, my bad.
Since there's a lot of code in common between this and requests/requests-kerberos, I believe requests-gssapi has some concurrency bugs as well.
requests/requests-kerberos#113 has details, and requests/requests-kerberos#114 has a fix which can be adapted.
The problem stems from how we index
gssapi.SecurityContextobjects byhostname. If you have multiple threads sharing anHTTPSPNEGOAuthobject, and they both send requests to the same host:SecurityContextto authenticate a request destined tofoo.comand caches it inself.context["foo.com"]and uses it tostep().SecurityContextto authenticate another request, also destined tofoo.comand caches it inself.context["foo.com"], overwriting theSecurityContextplaced there by thread1, and uses it tostep().SecurityContext(generated by thread2) to authenticate the response fromfoo.com, attempts tostep()with the token it received, and ...At this point, we're likely to either get a mutual authentication exception because the
SecurityContextwas used for a different request, or it may happen to work due to implementation details (though probably not). And even if it did work, when thread2 receives its response and attempts to authenticate it, it's likely to get a mutual authentication exception because theSecurityContextwas fully established.And the potential is also there for there to be a concurrency issue within
generate_request_headeralone, since it always accesses theSecurityContextvia theself.contextdictionary. Depending on how frequently the interpreter re-schedules threads (and the GIL is released), it's possible for thread1 to generate its context, then have the interpreter schedule thread2 which will replace it with another context. Then whichever one callsstep()on it first will succeed, and whichever one callsstep()on it second will likely fail since they'll both use the same context since they're accessing it via theself.contextdictionary.The solution for this is to index by the request (or PreparedRequest) object, not the hostname in the url in the request as we currently do.
As the original author of the code in question, my bad.