def auto_authentication(self, cfg, log):
        """
        Attempts to authenticate automatically by first searching the Batch Apps
        configuration for an unattended session, then a cached session.
        
        :Args:
            - cfg (:class:`batchapps.Configuration`): An instance of the Batch
              Apps Configuration class, read from the file set in the addon
              User Preferences.
            - log (:class:`batchapps.log.PickleLog`): A logger object as set in
              BatchAppsSettings.

        :Returns:
            - ``True`` if the addon was successfully authenticated,
              else ``False``
        """
        try:
            log.info("Checking for unattended session...")
            self.props.credentials = AzureOAuth.get_unattended_session(
                config=cfg)
            log.info("Found!")
            return True

        except (AuthenticationException, InvalidConfigException) as exp:
            log.info("Could not get unattended session: {0}".format(exp))

        try:
            log.info("Checking for cached session...")
            self.props.credentials = AzureOAuth.get_session(config=cfg)
            log.info("Found!")
            return True

        except (AuthenticationException, InvalidConfigException) as exp:
            log.info("Could not get cached session: {0}".format(exp))
            return False
    def auto_authentication(self, cfg, log):
        """
        Attempts to authenticate automatically by first searching the Batch Apps
        configuration for an unattended session, then a cached session.
        
        :Args:
            - cfg (:class:`batchapps.Configuration`): An instance of the Batch
              Apps Configuration class, read from the file set in the addon
              User Preferences.
            - log (:class:`batchapps.log.PickleLog`): A logger object as set in
              BatchAppsSettings.

        :Returns:
            - ``True`` if the addon was successfully authenticated,
              else ``False``
        """
        try:
            log.info("Checking for unattended session...")
            self.props.credentials = AzureOAuth.get_unattended_session(config=cfg)
            log.info("Found!")
            return True

        except (AuthenticationException, InvalidConfigException) as exp:
            log.info("Could not get unattended session: {0}".format(exp))

        try:
            log.info("Checking for cached session...")
            self.props.credentials = AzureOAuth.get_session(config=cfg)
            log.info("Found!")
            return True

        except (AuthenticationException, InvalidConfigException) as exp:
            log.info("Could not get cached session: {0}".format(exp))
            return False
    def test_azureoauth_get_session(self, mock_config, mock_credentials):
        """Test get_session"""

        mock_config.return_value = mock.create_autospec(Configuration)
        mock_config.return_value.aad_config.return_value = {'client_id':'abc'}
        AzureOAuth.get_session()
        self.assertTrue(mock_config.called)
        mock_credentials.assert_called_with(mock_config.return_value, 'abc')
        mock_config.reset_mock()

        mock_cfg = mock.create_autospec(Configuration)
        mock_cfg.aad_config.return_value = {'client_id':'abc'}
        AzureOAuth.get_session(config=mock_cfg)
        self.assertFalse(mock_config.called)
        mock_credentials.assert_called_with(mock_cfg, 'abc')
    def open_websession(self):
        """
        Open a web browser session to prompt the user to authenticate via their
        AAD credentials.
        This method of authentication is the 'last resort' after
        auto-authentication and unattended authentication have failed.

        :Raises:
            - :class:`RuntimeError` if authentication fails, which will fail
              the loading of the addon as all auth routes have failed. This
              could be due to either an 
              :class:`batchapps.exceptions.AuthenticationException` of a
              :class:`batchapps.exceptions.InvalidConfigException`.
        """
        session = bpy.context.scene.batchapps_session

        try:
            url, state = AzureOAuth.get_authorization_url(config=session.cfg)
            webbrowser.open(url)

            session.log.info("Opened web browser for authentication "
                             "and waiting for response.")

            self.wait_for_request()

        except (AuthenticationException, InvalidConfigException) as exp:
            session.log.error("Unable to open Web UI auth session: "
                              "{0}".format(exp))

            raise RuntimeError("Failed to authorize addon")
    def open_websession(self):
        """
        Open a web browser session to prompt the user to authenticate via their
        AAD credentials.
        This method of authentication is the 'last resort' after
        auto-authentication and unattended authentication have failed.

        :Raises:
            - :class:`RuntimeError` if authentication fails, which will fail
              the loading of the addon as all auth routes have failed. This
              could be due to either an 
              :class:`batchapps.exceptions.AuthenticationException` of a
              :class:`batchapps.exceptions.InvalidConfigException`.
        """
        session = bpy.context.scene.batchapps_session

        try:
            url, state = AzureOAuth.get_authorization_url(config=session.cfg)
            webbrowser.open(url)

            session.log.info("Opened web browser for authentication "
                             "and waiting for response.")

            self.wait_for_request()

        except (AuthenticationException, InvalidConfigException) as exp:
            session.log.error("Unable to open Web UI auth session: "
                              "{0}".format(exp))

            raise RuntimeError("Failed to authorize addon")
    def test_azureoauth_setup_session(self, mock_requests):
        """Test _setup_session"""

        AzureOAuth.state = None
        AzureOAuth._setup_session({})
        mock_requests.OAuth2Session.assert_called_with(
            None, redirect_uri="http://None", state=None)

        session = AzureOAuth._setup_session({'redirect_uri':'1', 'client_id':'3'})
        mock_requests.OAuth2Session.assert_called_with("3",
                                                       redirect_uri="http://1",
                                                       state=None)
        AzureOAuth.state = "test"
        session = AzureOAuth._setup_session({'redirect_uri':'1', 'client_id':'3'})
        self.assertTrue(session.verify)
        mock_requests.OAuth2Session.assert_called_with("3",
                                                       redirect_uri="http://1",
                                                       state='test')
    def test_azureoauth_get_authorization_token(self,
                                     mock_creds, mock_state,
                                     mock_setup, mock_config):
        """Test get_authorization_token"""

        mock_state.return_value = True
        mock_setup.return_value = mock.create_autospec(
            requests_oauthlib.OAuth2Session)
        mock_setup.return_value.fetch_token.return_value = {}

        mock_config.return_value.aad_config.return_value = {'root':'1/',
                                               'unattended_key':'3',
                                               'token_uri':'/auth',
                                               'resource':'test',
                                               'tenant':'common',
                                               'client_id':'abc'}

        with self.assertRaises(InvalidConfigException):
            AzureOAuth.get_authorization_token("test", config="test")

        mock_setup.return_value.fetch_token.return_value = {}
        authed = AzureOAuth.get_authorization_token("test")
        mock_setup.assert_called_with(mock.ANY)
        mock_creds.assert_called_with(mock.ANY, mock.ANY, token={})
        self.assertIsNotNone(authed)

        authed = AzureOAuth.get_authorization_token("test", state="test")
        mock_setup.assert_called_with(mock.ANY)
        mock_creds.assert_called_with(mock.ANY, 'abc', token={})

        mock_state.return_value = False
        with self.assertRaises(AuthenticationException):
            authed = AzureOAuth.get_authorization_token("test")

        mock_state.return_value = True
        credentials.CA_CERT = "cacert.pem"
        AzureOAuth.get_authorization_token("test", state="test")
        mock_setup.return_value.fetch_token.assert_called_with(
            "https://1/common/auth",
            authorization_response='https://Nonetest',
            verify="cacert.pem")
        
        credentials.VERIFY = False
        AzureOAuth.get_authorization_token("test")
        mock_setup.return_value.fetch_token.assert_called_with(
            "https://1/common/auth",
            authorization_response='https://Nonetest',
            verify=False)
    def test_azureoauth_get_authorization_url(self, mock_gen, mock_setup, mock_config):
        """Test get_authorization_url"""

        mock_gen.return_value = "gen_state_123"
        mock_setup.return_value = mock.create_autospec(
            requests_oauthlib.OAuth2Session)

        mock_config.return_value.aad_config.return_value = {'auth_uri':'1', 'resource':'2', 'root':'3',
                                                            'tenant':'4'}
        with self.assertRaises(AuthenticationException):
            AzureOAuth.get_authorization_url()


        mock_setup.return_value.authorization_url.return_value = ("a", "b")
        url, state = AzureOAuth.get_authorization_url()
        self.assertTrue(mock_config.called)
        self.assertIsNotNone(url)
        self.assertIsNotNone(state)
        mock_setup.return_value.authorization_url.assert_called_with('https://341',
                                                                     resource='https://2')
        self.assertEqual(AzureOAuth.state, "gen_state_123")
        mock_config.reset_mock()
        with self.assertRaises(InvalidConfigException):
            AzureOAuth.get_authorization_url(1)

        self.assertFalse(mock_config.called)
        url, state = AzureOAuth.get_authorization_url(msa=True, prompt=True, state="test")
        self.assertEqual(AzureOAuth.state, "test")
        mock_setup.return_value.authorization_url.assert_called_with('https://341',
                                                                     resource='https://2',
                                                                     prompt='login',
                                                                     domain_hint='live.com')
 def test_azureoauth_check_state(self):
     """Test _check_state"""
     self.assertFalse(AzureOAuth._check_state("test", "abc"))
     self.assertFalse(AzureOAuth._check_state("test&abc", "abc"))
     self.assertFalse(AzureOAuth._check_state("test&state=xyx", "abc"))
     self.assertFalse(AzureOAuth._check_state("test&state=xyx&", "abc"))
     self.assertFalse(AzureOAuth._check_state("test&state=abcd&", "abc"))
     self.assertTrue(AzureOAuth._check_state("test&state=abc&", "abc"))
    def web_authentication(self):
        """
        Prompts user to authenticate via a web browser session, after
        auto-authentication and unattended authentication have failed.

        If web authentication is successful, the session (i.e. refresh token)
        will be cached to enable auto-authentication next time the addon is
        used. Session page will be set to the HOME page.

        If unsuccessful, the addon will failed to load and the ERROR page will
        be display. Error details will be logged to the console.

        """
        session = bpy.context.scene.batchapps_session

        if self.auto_authentication(session.cfg, session.log):
            session.start(self.props.credentials)
            session.page = "HOME"
            session.redraw()
            return

        self.open_websession()

        if not self.props.code:
            session.log.warning("Log in timed out - please try again.")
            session.page = "LOGIN"

        elif '/?error=' in self.props.code:
            error = self.decode_error('/?error=')
            details = self.decode_error(
                '&error_description=').replace('+', ' ')

            session.log.error("Authentication failed: {0}".format(error))
            session.log.error(details)
            session.page = "ERROR"

        else:
            session.log.info(
                "Received valid authentication response from web browser.")
            session.log.info("Now retrieving new authentication token...")

            self.props.credentials = AzureOAuth.get_authorization_token(
                self.props.code, config=session.cfg)

            session.start(self.props.credentials)
            session.log.info("Successful! Login complete.")
        
        session.redraw()
    def web_authentication(self):
        """
        Prompts user to authenticate via a web browser session, after
        auto-authentication and unattended authentication have failed.

        If web authentication is successful, the session (i.e. refresh token)
        will be cached to enable auto-authentication next time the addon is
        used. Session page will be set to the HOME page.

        If unsuccessful, the addon will failed to load and the ERROR page will
        be display. Error details will be logged to the console.

        """
        session = bpy.context.scene.batchapps_session

        if self.auto_authentication(session.cfg, session.log):
            session.start(self.props.credentials)
            session.page = "HOME"
            session.redraw()
            return

        self.open_websession()

        if not self.props.code:
            session.log.warning("Log in timed out - please try again.")
            session.page = "LOGIN"

        elif '/?error=' in self.props.code:
            error = self.decode_error('/?error=')
            details = self.decode_error('&error_description=').replace(
                '+', ' ')

            session.log.error("Authentication failed: {0}".format(error))
            session.log.error(details)
            session.page = "ERROR"

        else:
            session.log.info(
                "Received valid authentication response from web browser.")
            session.log.info("Now retrieving new authentication token...")

            self.props.credentials = AzureOAuth.get_authorization_token(
                self.props.code, config=session.cfg)

            session.start(self.props.credentials)
            session.log.info("Successful! Login complete.")

        session.redraw()
    def test_azureoauth_get_principal_token(self, mock_token):
        """Test deprecated method get_principal_token"""

        AzureOAuth.get_principal_token()
        mock_token.assert_called_with(config=None)

        AzureOAuth.get_principal_token("hello")
        mock_token.assert_called_with(config="hello")

        AzureOAuth.get_principal_token(config="world")
        mock_token.assert_called_with(config="world")
    def test_azureoauth_get_unattended_session(self,
                                 mock_creds,
                                 mock_config,
                                 mock_client,
                                 mock_req):
        """Test get_unattended_session"""

        mock_session = mock.create_autospec(
            requests_oauthlib.OAuth2Session)

        mock_req.OAuth2Session.return_value = mock_session
        mock_config.aad_config.return_value = {'root':'1/',
                                               'unattended_key':'3',
                                               'token_uri':'/auth',
                                               'resource':'test',
                                               'unattended_account':'abc'}

        with self.assertRaises(InvalidConfigException):
            AzureOAuth.get_unattended_session(mock_config)

        mock_config.aad_config.return_value['unattended_account'] = 'ClientID=abc;TenantID=common'
        AzureOAuth.get_unattended_session(mock_config)
        mock_client.assert_called_with("abc")
        mock_req.OAuth2Session.assert_called_with("abc", client=mock.ANY)
        mock_session.fetch_token.assert_called_with(
            "https://1/common/auth",
            client_id='abc',
            resource='https://test',
            client_secret='3',
            response_type='client_credentials',
            verify=True)

        mock_config.aad_config.return_value = {'root':'http://1/',
                                               'unattended_key':'3',
                                               'token_uri':'/auth',
                                               'resource':'https://test',
                                               'unattended_account':'ClientID=abc;TenantID=common'}

        AzureOAuth.get_unattended_session(mock_config)
        mock_client.assert_called_with("abc")
        mock_req.OAuth2Session.assert_called_with("abc", client=mock.ANY)
        mock_session.fetch_token.assert_called_with(
            "https://1/common/auth",
            client_id='abc',
            resource='https://test',
            client_secret='3',
            response_type='client_credentials',
            verify=True)

        credentials.CA_CERT = "cacert.pem"
        AzureOAuth.get_unattended_session(mock_config)
        mock_client.assert_called_with("abc")
        mock_req.OAuth2Session.assert_called_with("abc", client=mock.ANY)
        mock_session.fetch_token.assert_called_with(
            "https://1/common/auth",
            client_id='abc',
            resource='https://test',
            client_secret='3',
            response_type='client_credentials',
            verify="cacert.pem")
        
        credentials.VERIFY = False
        AzureOAuth.get_unattended_session(mock_config)
        mock_client.assert_called_with("abc")
        mock_req.OAuth2Session.assert_called_with("abc", client=mock.ANY)
        mock_session.fetch_token.assert_called_with(
            "https://1/common/auth",
            client_id='abc',
            resource='https://test',
            client_secret='3',
            response_type='client_credentials',
            verify=False)