From 36352598aeeb833ca3a30b342e7c04c2186cf4ea Mon Sep 17 00:00:00 2001 From: Cameron White Date: Thu, 19 Dec 2013 04:05:26 -0800 Subject: [PATCH] Added onetime_password to create_authorization github.AuthenticatedUser.create_authorization has been modified to support two-factor authentication. When two-factor authentication is enabled create_authorization will throw an TwoFactorException. The onetime password can be then be passed to create_authorization on a subsequent call. --- github/AuthenticatedUser.py | 11 +++++++++-- github/GithubException.py | 6 +++++- github/Requester.py | 7 +++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/github/AuthenticatedUser.py b/github/AuthenticatedUser.py index 009437be..535f77b6 100644 --- a/github/AuthenticatedUser.py +++ b/github/AuthenticatedUser.py @@ -397,7 +397,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "/user/watched/" + watched._identity ) - def create_authorization(self, scopes=github.GithubObject.NotSet, note=github.GithubObject.NotSet, note_url=github.GithubObject.NotSet, client_id=github.GithubObject.NotSet, client_secret=github.GithubObject.NotSet): + def create_authorization(self, scopes=github.GithubObject.NotSet, note=github.GithubObject.NotSet, note_url=github.GithubObject.NotSet, client_id=github.GithubObject.NotSet, client_secret=github.GithubObject.NotSet, onetime_password=None): """ :calls: `POST /authorizations `_ :param scopes: list of string @@ -405,6 +405,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): :param note_url: string :param client_id: string :param client_secret: string + :param onetime_password: string :rtype: :class:`github.Authorization.Authorization` """ assert scopes is github.GithubObject.NotSet or all(isinstance(element, (str, unicode)) for element in scopes), scopes @@ -412,6 +413,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): assert note_url is github.GithubObject.NotSet or isinstance(note_url, (str, unicode)), note_url assert client_id is github.GithubObject.NotSet or isinstance(client_id, (str, unicode)), client_id assert client_secret is github.GithubObject.NotSet or isinstance(client_secret, (str, unicode)), client_secret + assert onetime_password is None or isinstance(onetime_password, (str, unicode)), onetime_password post_parameters = dict() if scopes is not github.GithubObject.NotSet: post_parameters["scopes"] = scopes @@ -423,10 +425,15 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): post_parameters["client_id"] = client_id if client_secret is not github.GithubObject.NotSet: post_parameters["client_secret"] = client_secret + if onetime_password is not None: + request_header = {'x-github-otp': onetime_password} + else: + request_header = None headers, data = self._requester.requestJsonAndCheck( "POST", "/authorizations", - input=post_parameters + input=post_parameters, + headers=request_header, ) return github.Authorization.Authorization(self._requester, headers, data, completed=True) diff --git a/github/GithubException.py b/github/GithubException.py index 26057f41..eb6d233c 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -60,7 +60,6 @@ class BadCredentialsException(GithubException): Exception raised in case of bad credentials (when Github API replies with a 401 or 403 HTML status) """ - class UnknownObjectException(GithubException): """ Exception raised when a non-existing object is requested (when Github API replies with a 404 HTML status) @@ -108,3 +107,8 @@ class BadAttributeException(Exception): The exception raised when PyGithub tried to parse the value """ return self.__transformationException + +class TwoFactorException(GithubException): + """ + Exception raised when Github requires a onetime password for two-factor authentication + """ diff --git a/github/Requester.py b/github/Requester.py index 9901d3dd..2e0a3510 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -39,6 +39,7 @@ import urllib import urlparse import sys import Consts +import re atLeastPython26 = sys.hexversion >= 0x02060000 atLeastPython3 = sys.hexversion >= 0x03000000 @@ -173,12 +174,14 @@ class Requester: def __check(self, status, responseHeaders, output): output = self.__structuredFromJson(output) if status >= 400: - raise self.__createException(status, output) + raise self.__createException(status, responseHeaders, output) return responseHeaders, output - def __createException(self, status, output): + def __createException(self, status, headers, output): if status == 401 and output.get("message") == "Bad credentials": cls = GithubException.BadCredentialsException + if status == 401 and 'x-github-otp' in headers and re.match(r'.*required.*', headers['x-github-otp']): + cls = GithubException.TwoFactorException elif status == 403 and output.get("message").startswith("Missing or invalid User Agent string"): cls = GithubException.BadUserAgentException elif status == 403 and output.get("message").startswith("API Rate Limit Exceeded"):