def _test_device_flow(
            self, client_id=None, authority=None, scope=None, **ignored):
        assert client_id and authority and scope
        self.app = msal.PublicClientApplication(
            client_id, authority=authority, http_client=MinimalHttpClient())
        flow = self.app.initiate_device_flow(scopes=scope)
        assert "user_code" in flow, "DF does not seem to be provisioned: %s".format(
            json.dumps(flow, indent=4))
        logger.info(flow["message"])

        duration = 60
        logger.info("We will wait up to %d seconds for you to sign in" % duration)
        flow["expires_at"] = min(  # Shorten the time for quick test
            flow["expires_at"], time.time() + duration)
        result = self.app.acquire_token_by_device_flow(flow)
        self.assertLoosely(  # It will skip this test if there is no user interaction
            result,
            assertion=lambda: self.assertIn('access_token', result),
            skippable_errors=self.app.client.DEVICE_FLOW_RETRIABLE_ERRORS)
        if "access_token" not in result:
            self.skip("End user did not complete Device Flow in time")
        self.assertCacheWorksForUser(result, scope, username=None)
        result["access_token"] = result["refresh_token"] = "************"
        logger.info(
            "%s obtained tokens: %s", self.id(), json.dumps(result, indent=4))
    def test_memorize(self):
        # We use a real authority so the constructor can finish tenant discovery
        authority = "https://login.microsoftonline.com/common"
        self.assertNotIn(authority,
                         Authority._domains_without_user_realm_discovery)
        a = Authority(authority, MinimalHttpClient(), validate_authority=False)

        try:
            # We now pretend this authority supports no User Realm Discovery
            class MockResponse(object):
                status_code = 404

            a.user_realm_discovery("*****@*****.**",
                                   response=MockResponse())
            self.assertIn(
                "login.microsoftonline.com",
                Authority._domains_without_user_realm_discovery,
                "user_realm_discovery() should memorize domains not supporting URD"
            )
            a.user_realm_discovery(
                "*****@*****.**",
                response=
                "This would cause exception if memorization did not work")
        finally:  # MUST NOT let the previous test changes affect other test cases
            Authority._domains_without_user_realm_discovery = set([])
def get_lab_app(
        env_client_id="LAB_APP_CLIENT_ID",
        env_client_secret="LAB_APP_CLIENT_SECRET",
        ):
    """Returns the lab app as an MSAL confidential client.

    Get it from environment variables if defined, otherwise fall back to use MSI.
    """
    if os.getenv(env_client_id) and os.getenv(env_client_secret):
        # A shortcut mainly for running tests on developer's local development machine
        # or it could be setup on Travis CI
        #   https://docs.travis-ci.com/user/environment-variables/#defining-variables-in-repository-settings
        # Data came from here
        # https://docs.msidlab.com/accounts/confidentialclient.html
        logger.info("Using lab app defined by ENV variables %s and %s",
                env_client_id, env_client_secret)
        client_id = os.getenv(env_client_id)
        client_secret = os.getenv(env_client_secret)
    else:
        logger.info("ENV variables %s and/or %s are not defined. Fall back to MSI.",
                env_client_id, env_client_secret)
        # See also https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/Programmatically-accessing-LAB-API's.aspx
        raise unittest.SkipTest("MSI-based mechanism has not been implemented yet")
    return msal.ConfidentialClientApplication(client_id, client_secret,
            authority="https://login.microsoftonline.com/"
                "72f988bf-86f1-41af-91ab-2d7cd011db47",  # Microsoft tenant ID
            http_client=MinimalHttpClient())
 def setUpClass(cls):
     http_client = MinimalHttpClient()
     if "client_certificate" in CONFIG:
         private_key_path = CONFIG["client_certificate"]["private_key_path"]
         with open(os.path.join(THIS_FOLDER, private_key_path)) as f:
             private_key = f.read()  # Expecting PEM format
         cls.client = Client(
             CONFIG["openid_configuration"],
             CONFIG['client_id'],
             http_client=http_client,
             client_assertion=JwtSigner(
                 private_key,
                 algorithm="RS256",
                 sha1_thumbprint=CONFIG["client_certificate"]["thumbprint"]
             ).sign_assertion(
                 audience=CONFIG["openid_configuration"]["token_endpoint"],
                 issuer=CONFIG["client_id"],
             ),
             client_assertion_type=Client.CLIENT_ASSERTION_TYPE_JWT,
         )
     else:
         cls.client = Client(CONFIG["openid_configuration"],
                             CONFIG['client_id'],
                             http_client=http_client,
                             client_secret=CONFIG.get('client_secret'))
예제 #5
0
 def test_subject_name_issuer_authentication(self):
     self.skipUnlessWithConfig(["client_id", "client_certificate"])
     client_cert = self.config["client_certificate"]
     assert "private_key_path" in client_cert and "thumbprint" in client_cert
     if not "public_certificate" in client_cert:
         self.skipTest(
             "Skipping SNI test due to lack of public_certificate")
     with open(os.path.join(THIS_FOLDER,
                            client_cert['private_key_path'])) as f:
         private_key = f.read()  # Should be in PEM format
     with open(os.path.join(THIS_FOLDER,
                            client_cert['public_certificate'])) as f:
         public_certificate = f.read()
     self.app = msal.ConfidentialClientApplication(
         self.config['client_id'],
         authority=self.config["authority"],
         client_credential={
             "private_key": private_key,
             "thumbprint": self.config["thumbprint"],
             "public_certificate": public_certificate,
         },
         http_client=MinimalHttpClient())
     scope = self.config.get("scope", [])
     result = self.app.acquire_token_for_client(scope)
     self.assertIn('access_token', result)
     self.assertCacheWorksForApp(result, scope)
 def test_acquire_token_silent_with_an_empty_cache_should_return_none(self):
     config = self.get_lab_user(
         usertype="cloud", azureenvironment=self.environment, publicClient="no")
     app = msal.ConfidentialClientApplication(
         config['client_id'], authority=config['authority'],
         http_client=MinimalHttpClient())
     result = app.acquire_token_silent(scopes=config['scope'], account=None)
     self.assertEqual(result, None)
 def _test_acquire_token_by_client_secret(
         self, client_id=None, client_secret=None, authority=None, scope=None,
         **ignored):
     assert client_id and client_secret and authority and scope
     app = msal.ConfidentialClientApplication(
         client_id, client_credential=client_secret, authority=authority,
         http_client=MinimalHttpClient())
     result = app.acquire_token_for_client(scope)
     self.assertIsNotNone(result.get("access_token"), "Got %s instead" % result)
 def test_wellknown_host_and_tenant(self):
     # Assert all well known authority hosts are using their own "common" tenant
     for host in WELL_KNOWN_AUTHORITY_HOSTS:
         a = Authority('https://{}/common'.format(host),
                       MinimalHttpClient())
         self.assertEqual(a.authorization_endpoint,
                          'https://%s/common/oauth2/v2.0/authorize' % host)
         self.assertEqual(a.token_endpoint,
                          'https://%s/common/oauth2/v2.0/token' % host)
 def test_lessknown_host_will_return_a_set_of_v1_endpoints(self):
     # This is an observation for current (2016-10) server-side behavior.
     # It is probably not a strict API contract. I simply mention it here.
     less_known = 'login.windows.net'  # less.known.host/
     v1_token_endpoint = 'https://{}/common/oauth2/token'.format(less_known)
     a = Authority('https://{}/common'.format(less_known),
                   MinimalHttpClient())
     self.assertEqual(a.token_endpoint, v1_token_endpoint)
     self.assertNotIn('v2.0', a.token_endpoint)
예제 #10
0
    def _test_acquire_token_obo(self, config_pca, config_cca):
        # 1. An app obtains a token representing a user, for our mid-tier service
        pca = msal.PublicClientApplication(config_pca["client_id"],
                                           authority=config_pca["authority"],
                                           http_client=MinimalHttpClient())
        pca_result = pca.acquire_token_by_username_password(
            config_pca["username"],
            config_pca["password"],
            scopes=config_pca["scope"],
        )
        self.assertIsNotNone(
            pca_result.get("access_token"), "PCA failed to get AT because %s" %
            json.dumps(pca_result, indent=2))

        # 2. Our mid-tier service uses OBO to obtain a token for downstream service
        cca = msal.ConfidentialClientApplication(
            config_cca["client_id"],
            client_credential=config_cca["client_secret"],
            authority=config_cca["authority"],
            http_client=MinimalHttpClient(),
            # token_cache= ...,  # Default token cache is all-tokens-store-in-memory.
            # That's fine if OBO app uses short-lived msal instance per session.
            # Otherwise, the OBO app need to implement a one-cache-per-user setup.
        )
        cca_result = cca.acquire_token_on_behalf_of(pca_result['access_token'],
                                                    config_cca["scope"])
        self.assertNotEqual(None, cca_result.get("access_token"),
                            str(cca_result))

        # 3. Now the OBO app can simply store downstream token(s) in same session.
        #    Alternatively, if you want to persist the downstream AT, and possibly
        #    the RT (if any) for prolonged access even after your own AT expires,
        #    now it is the time to persist current cache state for current user.
        #    Assuming you already did that (which is not shown in this test case),
        #    the following part shows one of the ways to obtain an AT from cache.
        username = cca_result.get("id_token_claims",
                                  {}).get("preferred_username")
        self.assertEqual(config_cca["username"], username)
        if username:  # A precaution so that we won't use other user's token
            account = cca.get_accounts(username=username)[0]
            result = cca.acquire_token_silent(config_cca["scope"], account)
            self.assertEqual(cca_result["access_token"],
                             result["access_token"])
    def test_authority_honors_a_patched_requests(self):
        # First, we test that the original, unmodified authority is working
        a = msal.authority.Authority(
            "https://login.microsoftonline.com/common", MinimalHttpClient())
        self.assertEqual(
            a.authorization_endpoint,
            'https://login.microsoftonline.com/common/oauth2/v2.0/authorize')

        original = msal.authority.requests
        try:
            # Now we mimic a (discouraged) practice of patching authority.requests
            msal.authority.requests = DummyHttpClient()
            # msal.authority is expected to honor that patch.
            with self.assertRaises(RuntimeError):
                a = msal.authority.Authority(
                    "https://login.microsoftonline.com/common", MinimalHttpClient())
        finally:  # Tricky:
            # Unpatch is necessary otherwise other test cases would be affected
            msal.authority.requests = original
 def test_rt_being_added(self):
     client = Client(
         {"token_endpoint": "http://example.com/token"},
         "client_id",
         http_client=MinimalHttpClient(),
         on_obtaining_tokens=lambda event: self.assertEqual(
             "new", event["response"].get("refresh_token")),
         on_updating_rt=lambda rt_item, new_rt: self.fail(
             "This should not be called here"),
     )
     client.obtain_token_by_authorization_code("code", post=self._dummy)
 def test_invalid_host_skipping_validation_can_be_turned_off(self):
     try:
         Authority('https://example.com/invalid',
                   MinimalHttpClient(),
                   validate_authority=False)
     except ValueError as e:
         if "invalid_instance" in str(e):  # Imprecise but good enough
             self.fail(
                 "validate_authority=False should turn off validation")
     except:  # Could be requests...RequestException, json...JSONDecodeError, etc.
         pass  # Those are expected for this unittest case
 def test_client_secret(self):
     self.skipUnlessWithConfig(["client_id", "client_secret"])
     self.app = msal.ConfidentialClientApplication(
         self.config["client_id"],
         client_credential=self.config.get("client_secret"),
         authority=self.config.get("authority"),
         http_client=MinimalHttpClient())
     scope = self.config.get("scope", [])
     result = self.app.acquire_token_for_client(scope)
     self.assertIn('access_token', result)
     self.assertCacheWorksForApp(result, scope)
 def test_rt_being_updated(self):
     old_rt = {"refresh_token": "old"}
     client = Client(
         {"token_endpoint": "http://example.com/token"},
         "client_id",
         http_client=MinimalHttpClient(),
         on_obtaining_tokens=lambda event: self.assertNotIn(
             "refresh_token", event["response"]),
         on_updating_rt=lambda old, new:  # TODO: ensure it being called
         (self.assertEqual(old_rt, old), self.assertEqual("new", new)),
     )
     client.obtain_token_by_refresh_token({"refresh_token": "old"},
                                          post=self._dummy)
 def _test_username_password(self,
         authority=None, client_id=None, username=None, password=None, scope=None,
         **ignored):
     assert authority and client_id and username and password and scope
     self.app = msal.PublicClientApplication(
         client_id, authority=authority, http_client=MinimalHttpClient())
     result = self.app.acquire_token_by_username_password(
         username, password, scopes=scope)
     self.assertLoosely(result)
     # self.assertEqual(None, result.get("error"), str(result))
     self.assertCacheWorksForUser(
         result, scope,
         username=username if ".b2clogin.com" not in authority else None,
         )
 def test_rt_being_migrated(self):
     old_rt = {"refresh_token": "old"}
     client = Client(
         {"token_endpoint": "http://example.com/token"},
         "client_id",
         http_client=MinimalHttpClient(),
         on_obtaining_tokens=lambda event: self.assertEqual(
             "new", event["response"].get("refresh_token")),
         on_updating_rt=lambda rt_item, new_rt: self.fail(
             "This should not be called here"),
     )
     client.obtain_token_by_refresh_token({"refresh_token": "old"},
                                          on_updating_rt=False,
                                          post=self._dummy)
 def test_client_certificate(self):
     self.skipUnlessWithConfig(["client_id", "client_certificate"])
     client_cert = self.config["client_certificate"]
     assert "private_key_path" in client_cert and "thumbprint" in client_cert
     with open(os.path.join(THIS_FOLDER, client_cert['private_key_path'])) as f:
         private_key = f.read()  # Should be in PEM format
     self.app = msal.ConfidentialClientApplication(
         self.config['client_id'],
         {"private_key": private_key, "thumbprint": client_cert["thumbprint"]},
         http_client=MinimalHttpClient())
     scope = self.config.get("scope", [])
     result = self.app.acquire_token_for_client(scope)
     self.assertIn('access_token', result)
     self.assertCacheWorksForApp(result, scope)
def _get_app_and_auth_code(
        client_id,
        client_secret=None,
        authority="https://login.microsoftonline.com/common",
        port=44331,
        scopes=["https://graph.microsoft.com/.default"],  # Microsoft Graph
        **kwargs):
    from msal.oauth2cli.authcode import obtain_auth_code
    app = msal.ClientApplication(
        client_id, client_secret, authority=authority, http_client=MinimalHttpClient())
    redirect_uri = "http://localhost:%d" % port
    ac = obtain_auth_code(port, auth_uri=app.get_authorization_request_url(
        scopes, redirect_uri=redirect_uri, **kwargs))
    assert ac is not None
    return (app, ac, redirect_uri)
 def setUp(self):
     self.authority_url = "https://login.microsoftonline.com/common"
     self.authority = msal.authority.Authority(self.authority_url,
                                               MinimalHttpClient())
     self.scopes = ["s1", "s2"]
     self.uid = "my_uid"
     self.utid = "my_utid"
     self.account = {"home_account_id": "{}.{}".format(self.uid, self.utid)}
     self.frt = "what the frt"
     self.cache = msal.SerializableTokenCache()
     self.preexisting_family_app_id = "preexisting_family_app"
     self.cache.add({  # Pre-populate a FRT
         "client_id": self.preexisting_family_app_id,
         "scope": self.scopes,
         "token_endpoint": "{}/oauth2/v2.0/token".format(self.authority_url),
         "response": TokenCacheTestCase.build_response(
             access_token="Siblings won't share AT. test_remove_account() will.",
             id_token=TokenCacheTestCase.build_id_token(aud=self.preexisting_family_app_id),
             uid=self.uid, utid=self.utid, refresh_token=self.frt, foci="1"),
         })  # The add(...) helper populates correct home_account_id for future searching
 def setUp(self):
     self.authority_url = "https://login.microsoftonline.com/common"
     self.authority = msal.authority.Authority(
         self.authority_url, MinimalHttpClient())
     self.scopes = ["s1", "s2"]
     self.uid = "my_uid"
     self.utid = "my_utid"
     self.account = {"home_account_id": "{}.{}".format(self.uid, self.utid)}
     self.rt = "this is a rt"
     self.cache = msal.SerializableTokenCache()
     self.client_id = "my_app"
     self.cache.add({  # Pre-populate the cache
         "client_id": self.client_id,
         "scope": self.scopes,
         "token_endpoint": "{}/oauth2/v2.0/token".format(self.authority_url),
         "response": TokenCacheTestCase.build_response(
             access_token="an expired AT to trigger refresh", expires_in=-99,
             uid=self.uid, utid=self.utid, refresh_token=self.rt),
         })  # The add(...) helper populates correct home_account_id for future searching
     self.app = ClientApplication(
         self.client_id, authority=self.authority_url, token_cache=self.cache)
 def test_unknown_host_wont_pass_instance_discovery(self):
     _assert = getattr(self, "assertRaisesRegex",
                       self.assertRaisesRegexp)  # Hack
     with _assert(ValueError, "invalid_instance"):
         Authority('https://example.com/tenant_doesnt_matter_in_this_case',
                   MinimalHttpClient())