def whoami_command(linked_identities): """ Executor for `globus whoami` """ client = get_auth_client() # get userinfo from auth. # if we get back an error the user likely needs to log in again try: res = client.oauth2_userinfo() except AuthAPIError: safeprint( "Unable to get user information. Please try " "logging in again.", write_to_stderr=True, ) click.get_current_context().exit(1) print_command_hint( "For information on which identities are in session see\n" " globus session show\n" ) # --linked-identities either displays all usernames or a table if verbose if linked_identities: try: formatted_print( res["identity_set"], fields=[ ("Username", "username"), ("Name", "name"), ("ID", "sub"), ("Email", "email"), ], simple_text=( None if is_verbose() else "\n".join([x["username"] for x in res["identity_set"]]) ), ) except KeyError: safeprint( "Your current login does not have the consents required " "to view your full identity set. Please log in again " "to agree to the required consents.", write_to_stderr=True, ) # Default output is the top level data else: formatted_print( res, text_format=FORMAT_TEXT_RECORD, fields=[ ("Username", "preferred_username"), ("Name", "name"), ("ID", "sub"), ("Email", "email"), ], simple_text=(None if is_verbose() else res["preferred_username"]), )
def role_list(endpoint_id): """ List the assigned roles on an endpoint. You must have sufficient privileges to see the roles on the endpoint. """ client = get_client() roles = client.endpoint_role_list(endpoint_id) resolved_ids = IdentityMap( get_auth_client(), (x["principal"] for x in roles if x["principal_type"] == "identity"), ) def principal_str(role): principal = role["principal"] if role["principal_type"] == "identity": try: return resolved_ids[principal]["username"] except KeyError: return principal if role["principal_type"] == "group": return (u"https://app.globus.org/groups/{}").format(principal) return principal formatted_print( roles, fields=[ ("Principal Type", "principal_type"), ("Role ID", "id"), ("Principal", principal_str), ("Role", "role"), ], )
def list_command(endpoint_id): """List all rules in an endpoint's access control list.""" client = get_client() rules = client.endpoint_acl_list(endpoint_id) resolved_ids = IdentityMap( get_auth_client(), (x["principal"] for x in rules if x["principal_type"] == "identity"), ) def principal_str(rule): principal = rule["principal"] if rule["principal_type"] == "identity": try: return resolved_ids[principal]["username"] except KeyError: return principal if rule["principal_type"] == "group": return (u"https://app.globus.org/groups/{}").format(principal) return rule["principal_type"] formatted_print( rules, fields=[ ("Rule ID", "id"), ("Permissions", "permissions"), ("Shared With", principal_str), ("Path", "path"), ], )
def get_identities_command(values): """ Executor for `globus get-identities` """ client = get_auth_client() resolved_values = [_try_b32_decode(v) or v for v in values] # since API doesn't accept mixed ids and usernames, # split input values into separate lists ids = [] usernames = [] for val in resolved_values: try: uuid.UUID(val) ids.append(val) except ValueError: usernames.append(val) # make two calls to get_identities with ids and usernames # then combine the calls into one response results = [] if len(ids): results += client.get_identities(ids=ids)["identities"] if len(usernames): results += client.get_identities(usernames=usernames)["identities"] res = GlobusResponse({"identities": results}) def _custom_text_format(identities): """ Non-verbose text output is customized """ def resolve_identity(value): """ helper to deal with variable inputs and uncertain response order """ for identity in identities: if identity["id"] == value: return identity["username"] if identity["username"] == value: return identity["id"] return "NO_SUCH_IDENTITY" # standard output is one resolved identity per line in the same order # as the inputs. A resolved identity is either a username if given a # UUID vice versa, or "NO_SUCH_IDENTITY" if the identity could not be # found for val in resolved_values: safeprint(resolve_identity(val)) formatted_print( res, response_key='identities', fields=[('ID', 'id'), ('Username', 'username'), ('Full Name', 'name'), ('Organization', 'organization'), ('Email Address', 'email')], # verbose output is a table. Order not guaranteed, may contain # duplicates text_format=(FORMAT_TEXT_TABLE if is_verbose() else _custom_text_format))
def setUpClass(self): """ Gets a TransferClient and AuthClient for direct sdk calls Cleans any old sharing data created by previous test runs """ self.tc = get_client() self.ac = get_auth_client() clean_sharing()
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 whoami_command(linked_identities): """ Executor for `globus whoami` """ client = get_auth_client() # get userinfo from auth. # if we get back an error the user likely needs to log in again try: res = client.oauth2_userinfo() except AuthAPIError: click.echo( "Unable to get user information. Please try logging in again.", err=True) click.get_current_context().exit(1) print_command_hint( "For information on which identities are in session see\n" " globus session show\n") # --linked-identities either displays all usernames or a table if verbose if linked_identities: try: formatted_print( res["identity_set"], fields=[ ("Username", "username"), ("Name", "name"), ("ID", "sub"), ("Email", "email"), ], simple_text=(None if is_verbose() else "\n".join( [x["username"] for x in res["identity_set"]])), ) except KeyError: click.echo( "Your current login does not have the consents required " "to view your full identity set. Please log in again " "to agree to the required consents.", err=True, ) # Default output is the top level data else: formatted_print( res, text_format=FORMAT_TEXT_RECORD, fields=[ ("Username", "preferred_username"), ("Name", "name"), ("ID", "sub"), ("Email", "email"), ], simple_text=(None if is_verbose() else res["preferred_username"]), )
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 session_update(identities, no_local_server, all): if (not (identities or all)) or (identities and all): raise click.UsageError("Either give one or more IDENTITIES or use --all") auth_client = get_auth_client() # if --all use every identity id in the user's identity set if all: res = auth_client.oauth2_userinfo() try: identity_ids = [user["sub"] for user in res["identity_set"]] except KeyError: click.echo( "Your current login does not have the consents required " "to view your full identity set. Please log in again " "to agree to the required consents.", err=True, ) click.get_current_context().exit(1) # otherwise try to resolve any non uuid values to identity ids else: identity_ids = [] identity_names = [] for val in identities: try: uuid.UUID(val) identity_ids.append(val) except ValueError: identity_names.append(val) if identity_names: res = auth_client.get_identities(usernames=identity_names)["identities"] for name in identity_names: for identity in res: if identity["username"] == name: identity_ids.append(identity["id"]) break else: click.echo("No such identity {}".format(val), err=True) click.get_current_context().exit(1) # create session params once we have all identity ids session_params = { "session_required_identities": ",".join(identity_ids), "session_message": "Authenticate to update your CLI session.", } # use a link login if remote session or user requested if no_local_server or is_remote_session(): do_link_auth_flow(session_params=session_params) # otherwise default to a local server login flow else: click.echo( "You are running 'globus session update', " "which should automatically open a browser window for you to " "authenticate with specific identities.\n" "If this fails or you experience difficulty, try " "'globus session update --no-local-server'" "\n---" ) do_local_server_auth_flow(session_params=session_params) click.echo( "\nYou have successfully updated your CLI session.\n" "Use 'globus session show' to see the updated session." )
def ac(): with patch_config(): return get_auth_client()
def get_identities_command(values): """ Executor for `globus get-identities` """ client = get_auth_client() resolved_values = [_try_b32_decode(v) or v for v in values] # since API doesn't accept mixed ids and usernames, # split input values into separate lists ids = [] usernames = [] for val in resolved_values: try: uuid.UUID(val) ids.append(val) except ValueError: usernames.append(val) # make two calls to get_identities with ids and usernames # then combine the calls into one response results = [] if len(ids): results += client.get_identities(ids=ids)["identities"] if len(usernames): results += client.get_identities(usernames=usernames)["identities"] res = GlobusResponse({"identities": results}) def _custom_text_format(identities): """ Non-verbose text output is customized """ def resolve_identity(value): """ helper to deal with variable inputs and uncertain response order """ for identity in identities: if identity["id"] == value: return identity["username"] if identity["username"] == value: return identity["id"] return "NO_SUCH_IDENTITY" # standard output is one resolved identity per line in the same order # as the inputs. A resolved identity is either a username if given a # UUID vice versa, or "NO_SUCH_IDENTITY" if the identity could not be # found for val in resolved_values: safeprint(resolve_identity(val)) formatted_print( res, response_key="identities", fields=[ ("ID", "id"), ("Username", "username"), ("Full Name", "name"), ("Organization", "organization"), ("Email Address", "email"), ], # verbose output is a table. Order not guaranteed, may contain # duplicates text_format=(FORMAT_TEXT_TABLE if is_verbose() else _custom_text_format), )
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)
def get_identities_command(values, provision): """ Lookup Globus Auth Identities given one or more uuids and/or usernames. Default output resolves each UUID to a username and each username to a UUID, with one output per line in the same order as the inputs. If a particular input had no corresponding identity in Globus Auth, "NO_SUCH_IDENTITY" is printed instead. If more fields are desired, --verbose will give tabular output, but does not guarantee order and ignores inputs with no corresponding Globus Auth identity. """ client = get_auth_client() resolved_values = [_try_b32_decode(v) or v for v in values] # since API doesn't accept mixed ids and usernames, # split input values into separate lists ids = [] usernames = [] for val in resolved_values: try: uuid.UUID(val) ids.append(val) except ValueError: usernames.append(val) # make two calls to get_identities with ids and usernames # then combine the calls into one response results = [] if len(ids): results += client.get_identities(ids=ids, provision=provision)["identities"] if len(usernames): results += client.get_identities(usernames=usernames, provision=provision)["identities"] res = GlobusResponse({"identities": results}) def _custom_text_format(identities): """ Non-verbose text output is customized """ def resolve_identity(value): """ helper to deal with variable inputs and uncertain response order """ for identity in identities: if identity["id"] == value: return identity["username"] if identity["username"] == value: return identity["id"] return "NO_SUCH_IDENTITY" # standard output is one resolved identity per line in the same order # as the inputs. A resolved identity is either a username if given a # UUID vice versa, or "NO_SUCH_IDENTITY" if the identity could not be # found for val in resolved_values: click.echo(resolve_identity(val)) formatted_print( res, response_key="identities", fields=[ ("ID", "id"), ("Username", "username"), ("Full Name", "name"), ("Organization", "organization"), ("Email Address", "email"), ], # verbose output is a table. Order not guaranteed, may contain # duplicates text_format=(FORMAT_TEXT_TABLE if is_verbose() else _custom_text_format), )