|
| 1 | +import getpass, logging, pprint, sys, msal |
| 2 | + |
| 3 | + |
| 4 | +def _input_boolean(message): |
| 5 | + return input( |
| 6 | + "{} (N/n/F/f or empty means False, otherwise it is True): ".format(message) |
| 7 | + ) not in ('N', 'n', 'F', 'f', '') |
| 8 | + |
| 9 | +def _input(message, default=None): |
| 10 | + return input(message.format(default=default)).strip() or default |
| 11 | + |
| 12 | +def _select_options( |
| 13 | + options, header="Your options:", footer=" Your choice? ", option_renderer=str, |
| 14 | + accept_nonempty_string=False, |
| 15 | + ): |
| 16 | + assert options, "options must not be empty" |
| 17 | + if header: |
| 18 | + print(header) |
| 19 | + for i, o in enumerate(options, start=1): |
| 20 | + print(" {}: {}".format(i, option_renderer(o))) |
| 21 | + if accept_nonempty_string: |
| 22 | + print(" Or you can just type in your input.") |
| 23 | + while True: |
| 24 | + raw_data = input(footer) |
| 25 | + try: |
| 26 | + choice = int(raw_data) |
| 27 | + if 1 <= choice <= len(options): |
| 28 | + return options[choice - 1] |
| 29 | + except ValueError: |
| 30 | + if raw_data and accept_nonempty_string: |
| 31 | + return raw_data |
| 32 | + |
| 33 | +def _input_scopes(): |
| 34 | + return _select_options([ |
| 35 | + "https://graph.microsoft.com/.default", |
| 36 | + "https://management.azure.com/.default", |
| 37 | + "User.Read", |
| 38 | + "User.ReadBasic.All", |
| 39 | + ], |
| 40 | + header="Select a scope (multiple scopes can only be input by manually typing them):", |
| 41 | + accept_nonempty_string=True, |
| 42 | + ).split() |
| 43 | + |
| 44 | +def _select_account(app): |
| 45 | + accounts = app.get_accounts() |
| 46 | + if accounts: |
| 47 | + return _select_options( |
| 48 | + accounts, |
| 49 | + option_renderer=lambda a: a["username"], |
| 50 | + header="Account(s) already signed in inside MSAL Python:", |
| 51 | + ) |
| 52 | + else: |
| 53 | + print("No account available inside MSAL Python. Use other methods to acquire token first.") |
| 54 | + |
| 55 | +def acquire_token_silent(app): |
| 56 | + """acquire_token_silent() - with an account already signed into MSAL Python.""" |
| 57 | + account = _select_account(app) |
| 58 | + if account: |
| 59 | + pprint.pprint(app.acquire_token_silent( |
| 60 | + _input_scopes(), |
| 61 | + account=account, |
| 62 | + force_refresh=_input_boolean("Bypass MSAL Python's token cache?"), |
| 63 | + )) |
| 64 | + |
| 65 | +def acquire_token_interactive(app): |
| 66 | + """acquire_token_interactive() - User will be prompted if app opts to do select_account.""" |
| 67 | + pprint.pprint(app.acquire_token_interactive( |
| 68 | + _input_scopes(), |
| 69 | + prompt="select_account" if _input_boolean("Select Account?") else None, |
| 70 | + login_hint=_input("login_hint: ") or None, |
| 71 | + )) |
| 72 | + |
| 73 | +def acquire_token_by_username_password(app): |
| 74 | + """acquire_token_by_username_password() - See constraints here: https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#constraints-for-ropc""" |
| 75 | + pprint.pprint(app.acquire_token_by_username_password( |
| 76 | + _input("username: "), getpass.getpass("password: "), scopes=_input_scopes())) |
| 77 | + |
| 78 | +_JWK1 = """{"kty":"RSA", "n":"2tNr73xwcj6lH7bqRZrFzgSLj7OeLfbn8216uOMDHuaZ6TEUBDN8Uz0ve8jAlKsP9CQFCSVoSNovdE-fs7c15MxEGHjDcNKLWonznximj8pDGZQjVdfK-7mG6P6z-lgVcLuYu5JcWU_PeEqIKg5llOaz-qeQ4LEDS4T1D2qWRGpAra4rJX1-kmrWmX_XIamq30C9EIO0gGuT4rc2hJBWQ-4-FnE1NXmy125wfT3NdotAJGq5lMIfhjfglDbJCwhc8Oe17ORjO3FsB5CLuBRpYmP7Nzn66lRY3Fe11Xz8AEBl3anKFSJcTvlMnFtu3EpD-eiaHfTgRBU7CztGQqVbiQ", "e":"AQAB"}""" |
| 79 | +SSH_CERT_DATA = {"token_type": "ssh-cert", "key_id": "key1", "req_cnf": _JWK1} |
| 80 | +SSH_CERT_SCOPE = ["https://pas.windows.net/CheckMyAccess/Linux/.default"] |
| 81 | + |
| 82 | +def acquire_ssh_cert_silently(app): |
| 83 | + """Acquire an SSH Cert silently- This typically only works with Azure CLI""" |
| 84 | + account = _select_account(app) |
| 85 | + if account: |
| 86 | + result = app.acquire_token_silent( |
| 87 | + SSH_CERT_SCOPE, |
| 88 | + account, |
| 89 | + data=SSH_CERT_DATA, |
| 90 | + force_refresh=_input_boolean("Bypass MSAL Python's token cache?"), |
| 91 | + ) |
| 92 | + pprint.pprint(result) |
| 93 | + if result and result.get("token_type") != "ssh-cert": |
| 94 | + logging.error("Unable to acquire an ssh-cert.") |
| 95 | + |
| 96 | +def acquire_ssh_cert_interactive(app): |
| 97 | + """Acquire an SSH Cert interactively - This typically only works with Azure CLI""" |
| 98 | + result = app.acquire_token_interactive( |
| 99 | + SSH_CERT_SCOPE, |
| 100 | + prompt="select_account" if _input_boolean("Select Account?") else None, |
| 101 | + login_hint=_input("login_hint: ") or None, |
| 102 | + data=SSH_CERT_DATA, |
| 103 | + ) |
| 104 | + pprint.pprint(result) |
| 105 | + if result.get("token_type") != "ssh-cert": |
| 106 | + logging.error("Unable to acquire an ssh-cert") |
| 107 | + |
| 108 | +def remove_account(app): |
| 109 | + """remove_account() - Invalidate account and/or token(s) from cache, so that acquire_token_silent() would be reset""" |
| 110 | + account = _select_account(app) |
| 111 | + if account: |
| 112 | + app.remove_account(account) |
| 113 | + print('Account "{}" and/or its token(s) are signed out from MSAL Python'.format(account["username"])) |
| 114 | + |
| 115 | +def exit(_): |
| 116 | + """Exit""" |
| 117 | + print("Bye") |
| 118 | + sys.exit() |
| 119 | + |
| 120 | +def main(): |
| 121 | + print("Welcome to the Msal Python Console Test App") |
| 122 | + chosen_app = _select_options([ |
| 123 | + {"client_id": "04b07795-8ddb-461a-bbee-02f9e1bf7b46", "name": "Azure CLI"}, |
| 124 | + {"client_id": "04f0c124-f2bc-4f59-8241-bf6df9866bbd", "name": "Visual Studio (Correctly configured for MSA-PT)"}, |
| 125 | + ], |
| 126 | + option_renderer=lambda a: a["name"], |
| 127 | + header="Impersonate this app (or you can type in the client_id of your own app)", |
| 128 | + accept_nonempty_string=True) |
| 129 | + app = msal.PublicClientApplication( |
| 130 | + chosen_app["client_id"] if isinstance(chosen_app, dict) else chosen_app, |
| 131 | + authority=_select_options([ |
| 132 | + "https://login.microsoftonline.com/common", |
| 133 | + "https://login.microsoftonline.com/organizations", |
| 134 | + "https://login.microsoftonline.com/microsoft.onmicrosoft.com", |
| 135 | + "https://login.microsoftonline.com/msidlab4.onmicrosoft.com", |
| 136 | + "https://login.microsoftonline.com/consumers", |
| 137 | + ], header="Input authority", accept_nonempty_string=True), |
| 138 | + ) |
| 139 | + if _input_boolean("Enable MSAL Python's DEBUG log?"): |
| 140 | + logging.basicConfig(level=logging.DEBUG) |
| 141 | + while True: |
| 142 | + func = _select_options([ |
| 143 | + acquire_token_silent, |
| 144 | + acquire_token_interactive, |
| 145 | + acquire_token_by_username_password, |
| 146 | + acquire_ssh_cert_silently, |
| 147 | + acquire_ssh_cert_interactive, |
| 148 | + remove_account, |
| 149 | + exit, |
| 150 | + ], option_renderer=lambda f: f.__doc__, header="MSAL Python APIs:") |
| 151 | + try: |
| 152 | + func(app) |
| 153 | + except KeyboardInterrupt: # Useful for bailing out a stuck interactive flow |
| 154 | + print("Aborted") |
| 155 | + |
| 156 | +if __name__ == "__main__": |
| 157 | + main() |
| 158 | + |
0 commit comments