def do_link_auth_flow(session_params={}, force_new_client=False): """ Prompts the user with a link to authenticate with globus auth and authorize the CLI to act on their behalf. """ # get the ConfidentialApp client object auth_client = internal_auth_client( requires_instance=True, force_new_client=force_new_client) # start the Confidential App Grant flow auth_client.oauth2_start_flow( redirect_uri=auth_client.base_url + 'v2/web/auth-code', refresh_tokens=True, requested_scopes=SCOPES) # prompt additional_params = {"prompt": "login"} additional_params.update(session_params) linkprompt = 'Please authenticate with Globus here' safeprint('{0}:\n{1}\n{2}\n{1}\n' .format(linkprompt, '-' * len(linkprompt), auth_client.oauth2_get_authorize_url( additional_params=additional_params))) # come back with auth code auth_code = click.prompt( 'Enter the resulting Authorization Code here').strip() # finish auth flow exchange_code_and_store_config(auth_client, auth_code) return True
def check_logged_in(): # first, try to get the refresh tokens from config # we can skip the access tokens and their expiration times as those are not # strictly necessary transfer_rt = lookup_option(TRANSFER_RT_OPTNAME) auth_rt = lookup_option(AUTH_RT_OPTNAME) # if either of the refresh tokens are null return False if (transfer_rt is None or auth_rt is None): return False # get or create the instance client auth_client = internal_auth_client(requires_instance=True) # check that tokens and client are valid try: for tok in (transfer_rt, auth_rt): res = auth_client.oauth2_validate_token(tok) if not res['active']: return False # if the instance client is invalid, an AuthAPIError will be raised # we then force a new client to be created before continuing except AuthAPIError: return False return True
def do_link_login_flow(): """ Prompts the user with a link to authorize the CLI to act on their behalf. """ # get the NativeApp client object native_client = internal_auth_client() # start the Native App Grant flow, prefilling the # named grant label on the consent page if we can get a # hostname for the local system label = platform.node() or None native_client.oauth2_start_flow(refresh_tokens=True, prefill_named_grant=label, requested_scopes=SCOPES) # prompt linkprompt = 'Please log into Globus here' safeprint('{0}:\n{1}\n{2}\n{1}\n'.format( linkprompt, '-' * len(linkprompt), native_client.oauth2_get_authorize_url())) # come back with auth code auth_code = click.prompt( 'Enter the resulting Authorization Code here').strip() # finish login flow exchange_code_and_store_config(native_client, auth_code)
def check_logged_in(): # first, try to get the refresh tokens from config # we can skip the access tokens and their expiration times as those are not # strictly necessary transfer_rt = lookup_option(TRANSFER_RT_OPTNAME) auth_rt = lookup_option(AUTH_RT_OPTNAME) # if either of the refresh tokens are null return False if transfer_rt is None or auth_rt is None: return False # get or create the instance client auth_client = internal_auth_client(requires_instance=True) # check that tokens and client are valid try: for tok in (transfer_rt, auth_rt): res = auth_client.oauth2_validate_token(tok) if not res["active"]: return False # if the instance client is invalid, an AuthAPIError will be raised # we then force a new client to be created before continuing except AuthAPIError: return False return True
def logout_command(): # try to get the user's preferred username from userinfo # if an API error is raised, they probably are not logged in try: username = get_auth_client().oauth2_userinfo()["preferred_username"] except AuthAPIError: safeprint(("Unable to lookup username. You may not be logged in. " "Attempting logout anyway...\n")) username = None safeprint( u'Logging out of Globus{}\n'.format(u' as ' + username if username else '')) # build the NativeApp client object native_client = internal_auth_client() # remove tokens from config and revoke them # also, track whether or not we should print the rescind help print_rescind_help = False for token_opt in (TRANSFER_RT_OPTNAME, TRANSFER_AT_OPTNAME, AUTH_RT_OPTNAME, AUTH_AT_OPTNAME): # first lookup the token -- if not found we'll continue token = lookup_option(token_opt) if not token: safeprint(('Warning: Found no token named "{}"! ' 'Recommend rescinding consent').format(token_opt)) print_rescind_help = True continue # token was found, so try to revoke it try: native_client.oauth2_revoke_token(token) # if we network error, revocation failed -- print message and abort so # that we can revoke later when the network is working except globus_sdk.NetworkError: safeprint(('Failed to reach Globus to revoke tokens. ' 'Because we cannot revoke these tokens, cancelling ' 'logout')) click.get_current_context().exit(1) # finally, we revoked, so it's safe to remove the token remove_option(token_opt) # remove expiration times, just for cleanliness for expires_opt in (TRANSFER_AT_EXPIRES_OPTNAME, AUTH_AT_EXPIRES_OPTNAME): remove_option(expires_opt) # if print_rescind_help is true, we printed warnings above # so, jam out an extra newline as a separator safeprint(("\n" if print_rescind_help else "") + _LOGOUT_EPILOG) # if some token wasn't found in the config, it means its possible that the # config file was removed without logout # in that case, the user should rescind the CLI consent to invalidate any # potentially leaked refresh tokens, so print the help on that if print_rescind_help: safeprint(_RESCIND_HELP)
def session_show(): """List all identities in your current CLI auth session. Lists identities that are in the session tied to the CLI's access tokens along with the time the user authenticated with that identity. """ # get a token to introspect, refreshing if neccecary auth_client = internal_auth_client() try: auth_client.authorizer._check_expiration_time() except AttributeError: # if we have no RefreshTokenAuthorizor pass access_token = lookup_option(AUTH_AT_OPTNAME) # only instance clients can introspect tokens if isinstance(auth_client, globus_sdk.ConfidentialAppAuthClient): res = auth_client.oauth2_token_introspect(access_token, include="session_info") session_info = res.get("session_info", {}) authentications = session_info.get("authentications") or {} # empty session if still using Native App Client else: session_info = {} authentications = {} # resolve ids to human readable usernames resolved_ids = globus_sdk.IdentityMap(get_auth_client(), list(authentications)) # put the nested dicts in a format table output can work with # while also converting vals into human readable formats list_data = [{ "id": key, "username": resolved_ids.get(key, {}).get("username"), "auth_time": time.strftime("%Y-%m-%d %H:%M %Z", time.localtime(vals["auth_time"])), } for key, vals in authentications.items()] print_command_hint( "For information on your primary identity or full identity set see\n" " globus whoami\n") formatted_print( list_data, json_converter=lambda x: session_info, fields=[("Username", "username"), ("ID", "id"), ("Auth Time", "auth_time")], )
def get_auth_client(): tokens = get_auth_tokens() authorizer = None # if there's a refresh token, use it to build the authorizer if tokens['refresh_token'] is not None: authorizer = RefreshTokenAuthorizer(tokens['refresh_token'], internal_auth_client(), tokens['access_token'], tokens['access_token_expires'], on_refresh=_update_access_tokens) client = AuthClient(authorizer=authorizer, app_name=version.app_name) return client
def get_client(): tokens = get_transfer_tokens() authorizer = None # if there's a refresh token, use it to build the authorizer if tokens['refresh_token'] is not None: authorizer = RefreshTokenAuthorizer(tokens['refresh_token'], internal_auth_client(), tokens['access_token'], tokens['access_token_expires'], on_refresh=_update_access_tokens) return RetryingTransferClient(tries=10, authorizer=authorizer, app_name=version.app_name)
def get_auth_client(): tokens = get_auth_tokens() authorizer = None # if there's a refresh token, use it to build the authorizer if tokens["refresh_token"] is not None: authorizer = RefreshTokenAuthorizer( tokens["refresh_token"], internal_auth_client(), tokens["access_token"], tokens["access_token_expires"], on_refresh=_update_access_tokens, ) client = AuthClient(authorizer=authorizer, app_name=version.app_name) return client
def session_show(): # get a token to introspect, refreshing if neccecary auth_client = internal_auth_client() try: auth_client.authorizer._check_expiration_time() except AttributeError: # if we have no RefreshTokenAuthorizor pass access_token = lookup_option(AUTH_AT_OPTNAME) # only instance clients can introspect tokens if isinstance(auth_client, globus_sdk.ConfidentialAppAuthClient): res = auth_client.oauth2_token_introspect(access_token, include="session_info") session_info = res.get("session_info", {}) authentications = session_info.get("authentications") or {} # empty session if still using Native App Client else: session_info = {} authentications = {} # resolve ids to human readable usernames resolved_ids = LazyIdentityMap(list(authentications)) # put the nested dicts in a format table output can work with # while also converting vals into human readable formats list_data = [ { "id": key, "username": resolved_ids.get(key), "auth_time": time.strftime( "%Y-%m-%d %H:%M %Z", time.localtime(vals["auth_time"]) ), } for key, vals in authentications.items() ] print_command_hint( "For information on your primary identity or full identity set see\n" " globus whoami\n" ) formatted_print( list_data, json_converter=lambda x: session_info, fields=[("Username", "username"), ("ID", "id"), ("Auth Time", "auth_time")], )
def get_client(): tokens = get_transfer_tokens() authorizer = None # if there's a refresh token, use it to build the authorizer if tokens["refresh_token"] is not None: authorizer = RefreshTokenAuthorizer( tokens["refresh_token"], internal_auth_client(), tokens["access_token"], tokens["access_token_expires"], on_refresh=_update_access_tokens, ) return RetryingTransferClient( tries=10, authorizer=authorizer, app_name=version.app_name )
def check_logged_in(): # first, try to get the refresh tokens from config # we can skip the access tokens and their expiration times as those are not # strictly necessary transfer_rt = lookup_option(TRANSFER_RT_OPTNAME) auth_rt = lookup_option(AUTH_RT_OPTNAME) # if either of the refresh tokens are null return False if (transfer_rt is None or auth_rt is None): return False # check that tokens are valid native_client = internal_auth_client() for tok in (transfer_rt, auth_rt): res = native_client.oauth2_validate_token(tok) if not res['active']: return False return True
def do_local_server_auth_flow(session_params=None, force_new_client=False): """ Starts a local http server, opens a browser to have the user authenticate, and gets the code redirected to the server (no copy and pasting required) """ session_params = session_params or {} # start local server and create matching redirect_uri with start_local_server(listen=("127.0.0.1", 0)) as server: _, port = server.socket.getsockname() redirect_uri = "http://localhost:{}".format(port) # get the ConfidentialApp client object and start a flow auth_client = internal_auth_client(requires_instance=True, force_new_client=force_new_client) auth_client.oauth2_start_flow(refresh_tokens=True, redirect_uri=redirect_uri, requested_scopes=SCOPES) additional_params = {"prompt": "login"} additional_params.update(session_params) url = auth_client.oauth2_get_authorize_url( additional_params=additional_params) # open web-browser for user to log in, get auth code webbrowser.open(url, new=1) auth_code = server.wait_for_code() if isinstance(auth_code, LocalServerError): click.echo("Authorization failed: {}".format(auth_code), err=True) click.get_current_context().exit(1) elif isinstance(auth_code, Exception): click.echo( "Authorization failed with unexpected error:\n{}".format( auth_code), err=True, ) click.get_current_context().exit(1) # finish auth flow and return true exchange_code_and_store_config(auth_client, auth_code) return True
def do_local_server_login_flow(): """ Starts a local http server, opens a browser to have the user login, and gets the code redirected to the server (no copy and pasting required) """ safeprint( "You are running 'globus login', which should automatically open " "a browser window for you to login.\n" "If this fails or you experience difficulty, try " "'globus login --no-local-server'" "\n---") # start local server and create matching redirect_uri with start_local_server(listen=('127.0.0.1', 0)) as server: _, port = server.socket.getsockname() redirect_uri = 'http://localhost:{}'.format(port) # get the NativeApp client object and start a flow # if available, use the system-name to prefill the grant label = platform.node() or None native_client = internal_auth_client() native_client.oauth2_start_flow(refresh_tokens=True, prefill_named_grant=label, redirect_uri=redirect_uri, requested_scopes=SCOPES) url = native_client.oauth2_get_authorize_url() # open web-browser for user to log in, get auth code webbrowser.open(url, new=1) auth_code = server.wait_for_code() if isinstance(auth_code, LocalServerError): safeprint('Login failed: {}'.format(auth_code), write_to_stderr=True) click.get_current_context().exit(1) elif isinstance(auth_code, Exception): safeprint('Login failed with unexpected error:\n{}'.format(auth_code), write_to_stderr=True) click.get_current_context().exit(1) # finish login flow exchange_code_and_store_config(native_client, auth_code)
def do_local_server_auth_flow(session_params=None, force_new_client=False): """ Starts a local http server, opens a browser to have the user authenticate, and gets the code redirected to the server (no copy and pasting required) """ session_params = session_params or {} # start local server and create matching redirect_uri with start_local_server(listen=("127.0.0.1", 0)) as server: _, port = server.socket.getsockname() redirect_uri = "http://localhost:{}".format(port) # get the ConfidentialApp client object and start a flow auth_client = internal_auth_client( requires_instance=True, force_new_client=force_new_client ) auth_client.oauth2_start_flow( refresh_tokens=True, redirect_uri=redirect_uri, requested_scopes=SCOPES ) additional_params = {"prompt": "login"} additional_params.update(session_params) url = auth_client.oauth2_get_authorize_url(additional_params=additional_params) # open web-browser for user to log in, get auth code webbrowser.open(url, new=1) auth_code = server.wait_for_code() if isinstance(auth_code, LocalServerError): safeprint("Authorization failed: {}".format(auth_code), write_to_stderr=True) click.get_current_context().exit(1) elif isinstance(auth_code, Exception): safeprint( "Authorization failed with unexpected error:\n{}".format(auth_code), write_to_stderr=True, ) click.get_current_context().exit(1) # finish auth flow and return true exchange_code_and_store_config(auth_client, auth_code) return True
def do_link_auth_flow(session_params=None, force_new_client=False): """ Prompts the user with a link to authenticate with globus auth and authorize the CLI to act on their behalf. """ session_params = session_params or {} # get the ConfidentialApp client object auth_client = internal_auth_client( requires_instance=True, force_new_client=force_new_client ) # start the Confidential App Grant flow auth_client.oauth2_start_flow( redirect_uri=auth_client.base_url + "v2/web/auth-code", refresh_tokens=True, requested_scopes=SCOPES, ) # prompt additional_params = {"prompt": "login"} additional_params.update(session_params) linkprompt = "Please authenticate with Globus here" safeprint( "{0}:\n{1}\n{2}\n{1}\n".format( linkprompt, "-" * len(linkprompt), auth_client.oauth2_get_authorize_url(additional_params=additional_params), ) ) # come back with auth code auth_code = click.prompt("Enter the resulting Authorization Code here").strip() # finish auth flow exchange_code_and_store_config(auth_client, auth_code) return True
def logout_command(): # try to get the user's preferred username from userinfo # if an API error is raised, they probably are not logged in try: username = get_auth_client().oauth2_userinfo()["preferred_username"] except AuthAPIError: click.echo(("Unable to lookup username. You may not be logged in. " "Attempting logout anyway...\n")) username = None click.echo( u"Logging out of Globus{}\n".format(u" as " + username if username else "")) # we use a Native Client to prevent an invalid instance client # from preventing token revocation native_client = internal_native_client() # remove tokens from config and revoke them # also, track whether or not we should print the rescind help print_rescind_help = False for token_opt in ( TRANSFER_RT_OPTNAME, TRANSFER_AT_OPTNAME, AUTH_RT_OPTNAME, AUTH_AT_OPTNAME, ): # first lookup the token -- if not found we'll continue token = lookup_option(token_opt) if not token: click.echo(('Warning: Found no token named "{}"! ' "Recommend rescinding consent").format(token_opt)) print_rescind_help = True continue # token was found, so try to revoke it try: native_client.oauth2_revoke_token(token) # if we network error, revocation failed -- print message and abort so # that we can revoke later when the network is working except globus_sdk.NetworkError: click.echo(("Failed to reach Globus to revoke tokens. " "Because we cannot revoke these tokens, cancelling " "logout")) click.get_current_context().exit(1) # finally, we revoked, so it's safe to remove the token remove_option(token_opt) # delete the instance client if one exists client_id = lookup_option(CLIENT_ID_OPTNAME) if client_id: instance_client = internal_auth_client() try: instance_client.delete("/v2/api/clients/{}".format(client_id)) # if the client secret has been invalidated or the client has # already been removed, we continue on except AuthAPIError: pass # remove deleted client values and expiration times for opt in ( CLIENT_ID_OPTNAME, CLIENT_SECRET_OPTNAME, TRANSFER_AT_EXPIRES_OPTNAME, AUTH_AT_EXPIRES_OPTNAME, ): remove_option(opt) # if print_rescind_help is true, we printed warnings above # so, jam out an extra newline as a separator click.echo(("\n" if print_rescind_help else "") + _LOGOUT_EPILOG) # if some token wasn't found in the config, it means its possible that the # config file was removed without logout # in that case, the user should rescind the CLI consent to invalidate any # potentially leaked refresh tokens, so print the help on that if print_rescind_help: click.echo(_RESCIND_HELP)