@@ -570,19 +570,27 @@ def _test_acquire_token_obo(self, config_pca, config_cca,
570570 # Here we just test regional apps won't adversely break OBO
571571 http_client = None ,
572572 ):
573- # 1. An app obtains a token representing a user, for our mid-tier service
574- pca = msal .PublicClientApplication (
575- config_pca ["client_id" ], authority = config_pca ["authority" ],
576- azure_region = azure_region ,
577- http_client = http_client or MinimalHttpClient ())
578- pca_result = pca .acquire_token_by_username_password (
579- config_pca ["username" ],
580- config_pca ["password" ],
581- scopes = config_pca ["scope" ],
582- )
583- self .assertIsNotNone (
584- pca_result .get ("access_token" ),
585- "PCA failed to get AT because %s" % json .dumps (pca_result , indent = 2 ))
573+ if "client_secret" not in config_pca :
574+ # 1.a An app obtains a token representing a user, for our mid-tier service
575+ result = msal .PublicClientApplication (
576+ config_pca ["client_id" ], authority = config_pca ["authority" ],
577+ azure_region = azure_region ,
578+ http_client = http_client or MinimalHttpClient (),
579+ ).acquire_token_by_username_password (
580+ config_pca ["username" ], config_pca ["password" ],
581+ scopes = config_pca ["scope" ],
582+ )
583+ else : # We repurpose the config_pca to contain client_secret for cca app 1
584+ # 1.b An app obtains a token representing itself, for our mid-tier service
585+ result = msal .ConfidentialClientApplication (
586+ config_pca ["client_id" ], authority = config_pca ["authority" ],
587+ client_credential = config_pca ["client_secret" ],
588+ azure_region = azure_region ,
589+ http_client = http_client or MinimalHttpClient (),
590+ ).acquire_token_for_client (scopes = config_pca ["scope" ])
591+ assertion = result .get ("access_token" )
592+ self .assertIsNotNone (assertion , "First app failed to get AT. {}" .format (
593+ json .dumps (result , indent = 2 )))
586594
587595 # 2. Our mid-tier service uses OBO to obtain a token for downstream service
588596 cca = msal .ConfidentialClientApplication (
@@ -595,9 +603,9 @@ def _test_acquire_token_obo(self, config_pca, config_cca,
595603 # That's fine if OBO app uses short-lived msal instance per session.
596604 # Otherwise, the OBO app need to implement a one-cache-per-user setup.
597605 )
598- cca_result = cca .acquire_token_on_behalf_of (
599- pca_result [ 'access_token' ], config_cca [ "scope" ])
600- self . assertNotEqual ( None , cca_result . get ( "access_token" ), str ( cca_result ))
606+ cca_result = cca .acquire_token_on_behalf_of (assertion , config_cca [ "scope" ])
607+ self . assertIsNotNone ( cca_result . get ( "access_token" ), "OBO call failed: {}" . format (
608+ json . dumps ( cca_result , indent = 2 ) ))
601609
602610 # 3. Now the OBO app can simply store downstream token(s) in same session.
603611 # Alternatively, if you want to persist the downstream AT, and possibly
@@ -606,13 +614,27 @@ def _test_acquire_token_obo(self, config_pca, config_cca,
606614 # Assuming you already did that (which is not shown in this test case),
607615 # the following part shows one of the ways to obtain an AT from cache.
608616 username = cca_result .get ("id_token_claims" , {}).get ("preferred_username" )
609- if username : # It means CCA have requested an IDT w/ "profile" scope
610- self .assertEqual (config_cca ["username" ], username )
611617 accounts = cca .get_accounts (username = username )
612- assert len (accounts ) == 1 , "App is expected to partition token cache per user"
613- account = accounts [0 ]
618+ if username is not None : # It means CCA have requested an IDT w/ "profile" scope
619+ assert config_cca ["username" ] == username , "Incorrect test case configuration"
620+ self .assertEqual (1 , len (accounts ), "App is supposed to partition token cache per user" )
621+ account = accounts [0 ] # Alternatively, cca app could just loop through each account
614622 result = cca .acquire_token_silent (config_cca ["scope" ], account )
615- self .assertEqual (cca_result ["access_token" ], result ["access_token" ])
623+ self .assertTrue (
624+ result and result .get ("access_token" ) == cca_result ["access_token" ],
625+ "CCA should hit an access token from cache: {}" .format (
626+ json .dumps (cca .token_cache ._cache , indent = 2 )))
627+ if "refresh_token" in cca_result :
628+ result = cca .acquire_token_silent (
629+ config_cca ["scope" ], account = account , force_refresh = True )
630+ self .assertTrue (
631+ result and "access_token" in result ,
632+ "CCA should get an AT silently, but we got this instead: {}" .format (result ))
633+ self .assertNotEqual (
634+ result ["access_token" ], cca_result ["access_token" ],
635+ "CCA should get a new AT" )
636+ else :
637+ logger .info ("AAD did not issue a RT for OBO flow" )
616638
617639 def _test_acquire_token_by_client_secret (
618640 self , client_id = None , client_secret = None , authority = None , scope = None ,
@@ -623,6 +645,18 @@ def _test_acquire_token_by_client_secret(
623645 http_client = MinimalHttpClient ())
624646 result = app .acquire_token_for_client (scope )
625647 self .assertIsNotNone (result .get ("access_token" ), "Got %s instead" % result )
648+ result2 = app .acquire_token_silent (scope , account = None )
649+ self .assertEqual (
650+ result2 .get ("access_token" ), result ["access_token" ],
651+ "CCA should hit an access token from cache: {}" .format (
652+ json .dumps (app .token_cache ._cache , indent = 2 ))
653+ )
654+ if "refresh_token" in result : # Empirically, RT is unavailable, but just in case...
655+ result3 = app .acquire_token_silent (scope , account = None , force_refresh = True )
656+ error_message = "CCA should get a new AT via RT in cache: {}" .format (
657+ json .dumps (app .token_cache ._cache , indent = 2 ))
658+ self .assertIsNotNone (result3 , error_message )
659+ self .assertNotEqual (result3 .get ("access_token" ), result ["access_token" ], error_message )
626660
627661
628662class WorldWideTestCase (LabBasedTestCase ):
@@ -732,6 +766,31 @@ def test_acquire_token_obo(self):
732766
733767 self ._test_acquire_token_obo (config_pca , config_cca )
734768
769+ @unittest .skipUnless (
770+ os .path .exists ("tests/sp_obo.pem" ),
771+ "Need a 'tests/sp_obo.pem' private to run OBO for SP test" )
772+ def test_acquire_token_obo_for_sp (self ):
773+ authority = "https://login.windows-ppe.net/f686d426-8d16-42db-81b7-ab578e110ccd"
774+ with open ("tests/sp_obo.pem" ) as pem :
775+ client_secret = {
776+ "private_key" : pem .read (),
777+ "thumbprint" : "378938210C976692D7F523B8C4FFBB645D17CE92" ,
778+ }
779+ midtier_app = {
780+ "authority" : authority ,
781+ "client_id" : "c84e9c32-0bc9-4a73-af05-9efe9982a322" ,
782+ "client_secret" : client_secret ,
783+ "scope" : ["23d08a1e-1249-4f7c-b5a5-cb11f29b6923/.default" ],
784+ #"username": "OBO-Client-PPE", # We do NOT attempt locating initial_app by name
785+ }
786+ initial_app = {
787+ "authority" : authority ,
788+ "client_id" : "9793041b-9078-4942-b1d2-babdc472cc0c" ,
789+ "client_secret" : client_secret ,
790+ "scope" : [midtier_app ["client_id" ] + "/.default" ],
791+ }
792+ self ._test_acquire_token_obo (initial_app , midtier_app )
793+
735794 def test_acquire_token_by_client_secret (self ):
736795 # Vastly different than ArlingtonCloudTestCase.test_acquire_token_by_client_secret()
737796 _app = self .get_lab_app_object (
0 commit comments