Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14114] ファイルの概要

このコミットは、lib/codereview/codereview.py ファイルにおける認証エラーハンドリング時のクラッシュを修正するものです。具体的には、Pythonのバージョンアップに伴い、urllib2.HTTPErrorreason プロパティが読み取り専用になったことによる問題を解決しています。

コミット

commit df7b720708e9da9e1bd00fe2424d765095427831
Author: Uriel Mangado <uriel@berlinblue.org>
Date:   Thu Oct 11 00:23:53 2012 +0800

    codereview.py: Fix crash on auth error handling.
    
    In recent Python versions .reason is a read-only property that simply gives you the msg value.
    
    Fixes #4024
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/6545052
---
 lib/codereview/codereview.py | 20 +++++++++++---------\n 1 file changed, 11 insertions(+), 9 deletions(-)\n

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/df7b720708e9da9e1bd00fe2424d765095427831

元コミット内容

codereview.py における認証エラーハンドリング時のクラッシュを修正します。最近のPythonバージョンでは、.reason は読み取り専用のプロパティであり、単に msg の値を提供するだけです。

変更の背景

この変更は、Go言語のコードレビューシステムで使用されるPythonスクリプト codereview.py が、特定のPythonバージョン(おそらくPython 2.7以降、または特定のパッチレベル)で認証エラーを適切に処理できなくなり、クラッシュする問題(Issue #4024)に対応するために行われました。

問題の根源は、Pythonの標準ライブラリである urllib2HTTPError クラスの内部的な変更にありました。以前のバージョンでは HTTPError オブジェクトの reason 属性にエラーの具体的な理由(例: "BadAuthentication")を直接設定することが可能であったか、あるいはそのように振る舞うことが期待されていました。しかし、新しいPythonバージョンでは、reason 属性が msg 属性の読み取り専用のエイリアス、またはそれに準ずる振る舞いをするようになりました。これにより、ClientLoginError クラスが reason 属性に値を代入しようとすると、エラーが発生するか、意図しない動作を引き起こすようになりました。

この変更は、GoプロジェクトのコードレビューツールがGoogleアカウントの認証(ClientLogin APIを使用していた可能性が高い)を行う際に、認証エラーが発生した場合に適切にエラーの種類を判別できなくなるという直接的な影響を及ぼしました。

前提知識の解説

urllib2.HTTPErrorreason / msg 属性

urllib2 はPython 2.xでHTTPリクエストを扱うためのモジュールです。HTTPError は、HTTPリクエストがサーバーからエラーレスポンス(例: 401 Unauthorized, 404 Not Found)を受け取った際に発生する例外です。

  • msg 属性: HTTPステータスコードに対応する人間が読める形式の理由フレーズ(例: "Unauthorized", "Not Found")を含みます。
  • reason 属性: urllib.error.URLError (HTTPErrorの親クラス) には reason 属性がありますが、urllib2.HTTPError では歴史的に msg 属性がその役割を果たしていました。Python 3.xでは、urllib.error.HTTPErrorreason 属性が追加され、これは msg 属性のエイリアスとして機能するようになりました。

このコミットが行われた2012年頃は、Python 2.xが主流であり、Python 3.xへの移行期でもありました。この時期に urllib2.HTTPError の内部的な振る舞いや属性の扱いが変更されたことが、この問題の直接的な原因となっています。特に、reason 属性が読み取り専用になったことで、カスタム例外クラス ClientLoginError がこの属性に値を設定しようとすると問題が発生しました。

ClientLoginError

このコードベースにおける ClientLoginError は、GoogleのClientLogin認証システム(現在は非推奨)からのエラーを処理するためにカスタムで定義された例外クラスです。これは urllib2.HTTPError を継承しており、認証プロセス中に発生する様々な特定のエラー(例: "BadAuthentication", "CaptchaRequired" など)を捕捉し、それに応じて適切なメッセージをユーザーに表示することを目的としていました。

Pythonのバージョン互換性

Pythonはバージョン間で互換性のない変更が導入されることがあります。特にPython 2.xから3.xへの移行期には、多くの標準ライブラリのAPIが変更されました。このコミットは、そのようなAPIの変更が既存のコードに与える影響の一例を示しています。

技術的詳細

この修正の技術的な核心は、urllib2.HTTPErrorreason 属性が読み取り専用になったという事実に対応することです。

元のコードでは、ClientLoginError のコンストラクタ内で self.reason = args["Error"] という行がありました。これは、Google ClientLogin APIから返されるエラー情報(args ディクショナリの "Error" キーに含まれる文字列)を、ClientLoginError オブジェクトの reason 属性に設定しようとしていました。

しかし、Pythonの新しいバージョンでは、urllib2.HTTPErrorreason 属性は、その親クラスである urllib.error.URLErrorreason 属性と同様に、msg 属性の値を反映する読み取り専用のプロパティとして扱われるようになりました。このため、self.reason = ... のような代入操作は許可されなくなり、エラーが発生するか、代入が無視されるようになりました。

この問題を解決するため、コミットでは以下の変更が行われました。

  1. ClientLoginError.__init__ メソッド内で、self.reason = args["Error"] の行を削除し、代わりに self.msg = args["Error"] としました。これにより、エラーの具体的な理由が msg 属性に設定されるようになります。コメントで「.reason は現在 .msg に基づく読み取り専用プロパティである」と明記されており、msg を設定することで reason も間接的に設定されることを示唆しています。
  2. コードベース全体で、ClientLoginError オブジェクトの reason 属性を参照していた箇所をすべて msg 属性を参照するように変更しました。具体的には、AbstractRpcServer._GetAuthToken メソッド内の except ClientLoginError, e: ブロック内で、e.reason == "..." となっていた条件分岐をすべて e.msg == "..." に変更しました。

この修正により、ClientLoginError はPythonの新しいバージョンでも正しくエラー情報を保持し、その情報に基づいて適切なエラーメッセージをユーザーに表示できるようになりました。

コアとなるコードの変更箇所

--- a/lib/codereview/codereview.py
+++ b/lib/codereview/codereview.py
@@ -2738,7 +2738,9 @@ class ClientLoginError(urllib2.HTTPError):
 	def __init__(self, url, code, msg, headers, args):
 		urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
 		self.args = args
-		self.reason = args["Error"]
+		# .reason is now a read-only property based on .msg
+		# this means we ignore 'msg', but that seems to work fine.
+		self.msg = args["Error"] 
 
 
 class AbstractRpcServer(object):\n@@ -2871,31 +2873,31 @@ class AbstractRpcServer(object):\n \t\t\ttry:\n \t\t\t\tauth_token = self._GetAuthToken(credentials[0], credentials[1])\n \t\t\texcept ClientLoginError, e:\n-\t\t\t\tif e.reason == "BadAuthentication":\n+\t\t\t\tif e.msg == "BadAuthentication":\n \t\t\t\t\tprint >>sys.stderr, "Invalid username or password."\n \t\t\t\t\tcontinue\n-\t\t\t\tif e.reason == "CaptchaRequired":\n+\t\t\t\tif e.msg == "CaptchaRequired":\n \t\t\t\t\tprint >>sys.stderr, (\n \t\t\t\t\t\t"Please go to\\n"\n \t\t\t\t\t\t"https://www.google.com/accounts/DisplayUnlockCaptcha\\n"\n \t\t\t\t\t\t"and verify you are a human.  Then try again.")\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "NotVerified":\n+\t\t\t\tif e.msg == "NotVerified":\n \t\t\t\t\tprint >>sys.stderr, "Account not verified."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "TermsNotAgreed":\n+\t\t\t\tif e.msg == "TermsNotAgreed":\n \t\t\t\t\tprint >>sys.stderr, "User has not agreed to TOS."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "AccountDeleted":\n+\t\t\t\tif e.msg == "AccountDeleted":\n \t\t\t\t\tprint >>sys.stderr, "The user account has been deleted."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "AccountDisabled":\n+\t\t\t\tif e.msg == "AccountDisabled":\n \t\t\t\t\tprint >>sys.stderr, "The user account has been disabled."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "ServiceDisabled":\n+\t\t\t\tif e.msg == "ServiceDisabled":\n \t\t\t\t\tprint >>sys.stderr, "The user's access to the service has been disabled."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "ServiceUnavailable":\n+\t\t\t\tif e.msg == "ServiceUnavailable":\n \t\t\t\t\tprint >>sys.stderr, "The service is not available; try again later."\n \t\t\t\t\tbreak\n \t\t\t\traise\n```

## コアとなるコードの解説

### `ClientLoginError` クラスの変更

*   **変更前**:
    ```python
    self.reason = args["Error"]
    ```
    `ClientLoginError` のインスタンス化時に、`args` ディクショナリから取得したエラー文字列を `self.reason` に直接代入していました。これが、新しいPythonバージョンで `reason` が読み取り専用になったために問題を引き起こしました。

*   **変更後**:
    ```python
    # .reason is now a read-only property based on .msg
    # this means we ignore 'msg', but that seems to work fine.
    self.msg = args["Error"] 
    ```
    `self.reason` への直接代入を削除し、代わりに `self.msg = args["Error"]` としています。これにより、エラーの具体的な理由が `msg` 属性に設定されます。コメントにあるように、`reason` が `msg` に基づく読み取り専用プロパティであるため、`msg` を設定することで `reason` も適切に反映されることが期待されます。

### `AbstractRpcServer` クラスの変更

`AbstractRpcServer` クラス内の `_GetAuthToken` メソッドの `try...except ClientLoginError, e:` ブロック内で、認証エラーの種類を判別するために `e.reason` を使用していた箇所がすべて `e.msg` に変更されています。

*   **変更前**:
    ```python
    if e.reason == "BadAuthentication":
    # ...
    if e.reason == "CaptchaRequired":
    # ... (同様の変更が複数箇所)
    ```
*   **変更後**:
    ```python
    if e.msg == "BadAuthentication":
    # ...
    if e.msg == "CaptchaRequired":
    # ... (同様の変更が複数箇所)
    ```
    これにより、`ClientLoginError` オブジェクトが保持するエラー情報(`msg` 属性に格納されている)に基づいて、正しいエラーメッセージがユーザーに表示されるようになります。

これらの変更は、PythonのバージョンアップによるAPIの振る舞いの変化にコードを適応させ、認証エラーハンドリングの堅牢性を確保することを目的としています。

## 関連リンク

*   Go Gerrit Change-Id: `6545052` - [https://golang.org/cl/6545052](https://golang.org/cl/6545052)
*   Go Issue: `4024` - [https://code.google.com/p/go/issues/detail?id=4024](https://code.google.com/p/go/issues/detail?id=4024) (元のIssueトラッカーはGoogle Codeであり、現在はアーカイブされている可能性があります)

## 参考にした情報源リンク

*   Python `urllib2.HTTPError` の `reason` および `msg` 属性に関する情報:
    *   [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG98GG3RuFv2_pkEQwiGnDAWzLnzIHytkDJe-yl7QeM0XubDLKZ_Pbwgja1cpYUAL8a9mMOUUFosoDhKyEnAO_EEgycODz-916OwT2W1e5OCMj2k-MrNYkBYkp2poHRi-OBLlhCZuLOQQCHSbE6VA==](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG98GG3RuFv2_pkEQwiGnDAWzLnzIHytkDJe-yl7QeM0XubDLKZ_Pbwgja1cpYUAL8a9mMOUUFosoDhKyEnAO_EEgycODz-916OwT2W1e5OCMj2k-MrNYkBYkp2poHRi-OBLlhCZuLOQQCHSbE6VA==)
    *   [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE6mJhb3WyEkNs7f7vf_dcHUKkti3VB_QaL1lwPlVPEoK5GEEl1-hXtvNFvpdiIy8LPTDTHUj0wAbSqUVcoyXOMB67zxLVhwsLj8oUctfmUH5la0Au_lIyZXq0cikY=](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE6mJhb3WyEkNs7f7vf_dcHUKkti3VB_QaL1lwPlVPEoK5GEEl1-hCtvNFvpdiIy8LPTDTHUj0wAbSqUVcoyXOMB67zxLVhwsLj8oUctfmUH5la0Au_lIyZXq0cikY=)