Repository: PyGithub/PyGithub

Issue:
TwoFactorException is not raised as expected
### Description

If a user has 2FA enabled, when she initialises a `Github` instance with her password instead of her personal access token and then tries to perform some actions like `get_repo`, `GithubException` rather than `TwoFactorException` will be raised.

### How to reproduce

```python
>>> from github import Github
>>> name = 'username'  # username of someone with 2FA enabled
>>> password = 'password'  # password instead of personal access token
>>> gh = Github(name, password)
>>> repo = gh.get_repo('span-example')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/changhc/Desktop/projects/span/venv/lib/python3.7/site-packages/github/MainClass.py", line 294, in get_repo
    "%s%s" % (url_base, full_name_or_id)
  File "/home/changhc/Desktop/projects/span/venv/lib/python3.7/site-packages/github/Requester.py", line 276, in requestJsonAndCheck
    return self.__check(*self.requestJson(verb, url, parameters, headers, input, self.__customConnection(url)))
  File "/home/changhc/Desktop/projects/span/venv/lib/python3.7/site-packages/github/Requester.py", line 287, in __check
    raise self.__createException(status, responseHeaders, output)
github.GithubException.GithubException: 401 {'message': 'Must specify two-factor authentication OTP code.', 'documentation_url': 'https://developer.github.com/v3/auth#working-with-two-factor-authentication'}
```

### Root cause

In the error handler of `Requester`, it checks whether a certain key is in the response headers. For 2FA-related error, it looks for `Consts.headerOTP`. The `requests` library converts all header keys to lower case, but `Consts.headerOTP` still keeps capital letters. Therefore, in the error handler this case cannot be captured as expected.


Discussion:

Resolving patch:
diff --git a/github/Consts.py b/github/Consts.py
--- a/github/Consts.py
+++ b/github/Consts.py
@@ -49,7 +49,7 @@
 headerRateRemaining = "x-ratelimit-remaining"
 headerRateReset = "x-ratelimit-reset"
 headerOAuthScopes = "x-oauth-scopes"
-headerOTP = "X-GitHub-OTP"
+headerOTP = "x-github-otp"
 
 defaultMediaType = "application/octet-stream"
 
diff --git a/github/Requester.py b/github/Requester.py
--- a/github/Requester.py
+++ b/github/Requester.py
@@ -296,7 +296,7 @@ def __createException(self, status, headers, output):
         if status == 401 and output.get("message") == "Bad credentials":
             cls = GithubException.BadCredentialsException
         elif status == 401 and Consts.headerOTP in headers and re.match(r'.*required.*', headers[Consts.headerOTP]):
-            cls = GithubException.TwoFactorException  # pragma no cover (Should be covered)
+            cls = GithubException.TwoFactorException
         elif status == 403 and output.get("message").startswith("Missing or invalid User Agent string"):
             cls = GithubException.BadUserAgentException
         elif status == 403 and (


Test patch:
diff --git a/tests/Exceptions.py b/tests/Exceptions.py
--- a/tests/Exceptions.py
+++ b/tests/Exceptions.py
@@ -100,6 +100,9 @@ class SpecificExceptions(Framework.TestCase):
     def testBadCredentials(self):
         self.assertRaises(github.BadCredentialsException, lambda: github.Github("BadUser", "BadPassword").get_user().login)
 
+    def test2FARequired(self):
+        self.assertRaises(github.TwoFactorException, lambda: github.Github("2fauser", "password").get_user().login)
+
     def testUnknownObject(self):
         self.assertRaises(github.UnknownObjectException, lambda: self.g.get_user().get_repo("Xxx"))
 
diff --git a/tests/ReplayData/SpecificExceptions.test2FARequired.txt b/tests/ReplayData/SpecificExceptions.test2FARequired.txt
new file mode 100644
--- /dev/null
+++ b/tests/ReplayData/SpecificExceptions.test2FARequired.txt
@@ -0,0 +1,10 @@
+https
+GET
+api.github.com
+None
+/user
+{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+None
+401
+[('date', 'Fri, 25 Oct 2019 07:56:02 GMT'), ('content-type', 'application/json; charset=utf-8'), ('content-length', '160'), ('server', 'GitHub.com'), ('status', '401 Unauthorized'), ('x-github-otp', 'required; app'), ('x-github-media-type', 'github.v3; format=json'), ('x-ratelimit-limit', '60'), ('x-ratelimit-remaining', '58'), ('x-ratelimit-reset', '1571993689'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '1; mode=block'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('content-security-policy', "default-src 'none'"), ('x-github-request-id', 'B376:7075:202D9C:250D5E:5DB2AA92')]
+{"message":"Must specify two-factor authentication OTP code.", "documentation_url": "https://developer.github.com/v3/auth#working-with-two-factor-authentication"}