|
46 | 46 | import os
|
47 | 47 | from pathlib import Path
|
48 | 48 |
|
49 |
| -from requests.auth import HTTPBasicAuth, HTTPDigestAuth |
| 49 | +from requests.auth import AuthBase, HTTPBasicAuth, HTTPDigestAuth |
50 | 50 | from requests_ntlm import HttpNtlmAuth
|
51 | 51 |
|
52 | 52 | from awsrun.acctload import (
|
@@ -903,31 +903,30 @@ def instantiate(self, args):
|
903 | 903 | if args.loader_password:
|
904 | 904 | auth_options["password"] = args.loader_password
|
905 | 905 |
|
906 |
| - # If they don't exist in user config, then pick defaults |
907 |
| - if "username" not in auth_options: |
908 |
| - auth_options["username"] = _default_username() |
909 |
| - if "password" not in auth_options: |
910 |
| - auth_options["password"] = _default_password(auth_options["username"]) |
| 906 | + # If a username and password has not been provided via CLI flags |
| 907 | + # or via the configuration file, we'll fallback to environment |
| 908 | + # variables if they exist, or lastly we'll prompt the user |
| 909 | + # interactively. BUT, we don't want to do that here because the |
| 910 | + # URLAccountLoader caches data, so we might not need to make an |
| 911 | + # HTTP call, and thus prompting the user would be unneeded (and |
| 912 | + # annoying). So, instead, we use DeferPrompting to wrap the |
| 913 | + # various requests HTTP*Auth classes. This will defer the |
| 914 | + # instantiation of those classes until `requests` invokes the |
| 915 | + # callable `auth` parameter to its various methods. |
911 | 916 |
|
912 | 917 | if args.loader_auth == "oauth2" and "token_url" not in auth_options:
|
913 | 918 | raise TypeError(
|
914 | 919 | "with oauth2 authentication token_url must be set in config: Accounts->options->auth_options->token_url"
|
915 | 920 | )
|
916 | 921 |
|
917 | 922 | auth_types = {
|
918 |
| - "none": _HTTPNone, |
919 |
| - "basic": HTTPBasicAuth, |
920 |
| - "digest": HTTPDigestAuth, |
921 |
| - "ntlm": HttpNtlmAuth, |
922 |
| - "oauth2": HTTPOAuth2, |
| 923 | + "none": _HTTPNone(), |
| 924 | + "basic": _DeferPrompting(HTTPBasicAuth, auth_options), |
| 925 | + "digest": _DeferPrompting(HTTPDigestAuth, auth_options), |
| 926 | + "ntlm": _DeferPrompting(HttpNtlmAuth, auth_options), |
| 927 | + "oauth2": _DeferPrompting(HTTPOAuth2, auth_options), |
923 | 928 | }
|
924 |
| - |
925 |
| - try: |
926 |
| - auth = auth_types[args.loader_auth](**auth_options) |
927 |
| - except TypeError as e: |
928 |
| - raise TypeError( |
929 |
| - f"incompatible auth_options specified in config: Accounts->options->auth_options: {e}" |
930 |
| - ) |
| 929 | + auth = auth_types[args.loader_auth] |
931 | 930 |
|
932 | 931 | parsers = {
|
933 | 932 | "json": JSONParser,
|
@@ -970,6 +969,36 @@ def _default_password(user):
|
970 | 969 | )
|
971 | 970 |
|
972 | 971 |
|
| 972 | +# Helper class to wrap one of `requests` auth classes to defer instantiation |
| 973 | +# of those classes until `requests` actually needs to use the auth data. This |
| 974 | +# is used to avoid interactively prompting a user for their password if one |
| 975 | +# had not been specified via CLI args or their config file. The default will |
| 976 | +# come from the env variable if set, otherwise the user is prompted. But we |
| 977 | +# don't want to prompt when we instantiated the `requests` auth classes |
| 978 | +# because at that time, we do not know if the account loader data has been |
| 979 | +# cached and thus not require making an HTTP call. So, why bother prompting |
| 980 | +# the user in that case? |
| 981 | +class _DeferPrompting(AuthBase): |
| 982 | + def __init__(self, auth_class, auth_options): |
| 983 | + self.auth_class = auth_class |
| 984 | + self.auth_options = auth_options |
| 985 | + |
| 986 | + def __call__(self, req): |
| 987 | + if "username" not in self.auth_options: |
| 988 | + self.auth_options["username"] = _default_username() |
| 989 | + if "password" not in self.auth_options: |
| 990 | + self.auth_options["password"] = _default_password( |
| 991 | + self.auth_options["username"] |
| 992 | + ) |
| 993 | + try: |
| 994 | + auth = self.auth_class(**self.auth_options) |
| 995 | + except TypeError as e: |
| 996 | + raise TypeError( |
| 997 | + f"incompatible auth_options specified in config: Accounts->options->auth_options: {e}" |
| 998 | + ) |
| 999 | + return auth(req) |
| 1000 | + |
| 1001 | + |
973 | 1002 | class _HTTPNone:
|
974 | 1003 | """HTTPNone is a no-op auth type for requests library."""
|
975 | 1004 |
|
|
0 commit comments