# Copyright 2012 NetApp
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from unittest import mock
from urllib import parse

import ddt
import webob

from manila.api.v1 import security_service
from manila.common import constants
from manila import db
from manila import exception
from manila import policy
from manila import test
from manila.tests.api import fakes


@ddt.ddt
class ShareApiTest(test.TestCase):
    """Share Api Test."""
    def setUp(self):
        super(ShareApiTest, self).setUp()
        self.controller = security_service.SecurityServiceController()
        self.maxDiff = None
        self.ss_active_directory = {
            "created_at": "fake-time",
            "updated_at": "fake-time-2",
            "id": 1,
            "name": "fake-name",
            "description": "Fake Security Service Desc",
            "type": constants.SECURITY_SERVICES_ALLOWED_TYPES[0],
            "dns_ip": "1.1.1.1",
            "server": "fake-server",
            "domain": "fake-domain",
            "user": "fake-user",
            "password": "fake-password",
            "status": constants.STATUS_NEW,
            "project_id": "fake",
        }
        self.ss_ldap = {
            "created_at": "fake-time",
            "updated_at": "fake-time-2",
            "id": 2,
            "name": "ss-ldap",
            "description": "Fake Security Service Desc",
            "type": constants.SECURITY_SERVICES_ALLOWED_TYPES[1],
            "dns_ip": "2.2.2.2",
            "server": "test-server",
            "domain": "test-domain",
            "user": "test-user",
            "password": "test-password",
            "status": "active",
            "project_id": "fake",
        }
        self.valid_search_opts = {
            'user': 'fake-user',
            'server': 'fake-server',
            'dns_ip': '1.1.1.1',
            'domain': 'fake-domain',
            'type': constants.SECURITY_SERVICES_ALLOWED_TYPES[0],
        }
        self.check_policy_patcher = mock.patch(
            'manila.api.v1.security_service.policy.check_policy')
        self.check_policy_patcher.start()
        self.addCleanup(self._stop_started_patcher, self.check_policy_patcher)
        self.security_service_list_expected_resp = {
            'security_services': [{
                'id': self.ss_active_directory['id'],
                'name': self.ss_active_directory['name'],
                'type': self.ss_active_directory['type'],
                'status': self.ss_active_directory['status']
            }, ]
        }

        self.fake_share_network_list_with_share_servers = [{
            'id': 'fake_sn_id',
            'share_network_subnets': [{
                'id': 'fake_sns_id',
                'share_servers': [{'id': 'fake_ss_id'}]
            }]
        }]
        self.fake_share_network_list_without_share_servers = [{
            'id': 'fake_sn_id',
            'share_network_subnets': [{
                'id': 'fake_sns_id',
                'share_servers': []
            }]
        }]

    def _stop_started_patcher(self, patcher):
        if hasattr(patcher, 'is_local'):
            patcher.stop()

    def test_security_service_show(self):
        db.security_service_get = mock.Mock(
            return_value=self.ss_active_directory)
        req = fakes.HTTPRequest.blank('/security-services/1')
        res_dict = self.controller.show(req, '1')
        expected = self.ss_active_directory.copy()
        expected.update()
        self.assertEqual({'security_service': self.ss_active_directory},
                         res_dict)

    def test_security_service_show_not_found(self):
        db.security_service_get = mock.Mock(side_effect=exception.NotFound)
        req = fakes.HTTPRequest.blank('/shares/1')
        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.show,
                          req, '1')

    def test_security_service_create(self):
        sec_service = self.ss_active_directory.copy()
        create_stub = mock.Mock(
            return_value=sec_service)
        self.mock_object(db, 'security_service_create', create_stub)

        req = fakes.HTTPRequest.blank('/security-services')
        res_dict = self.controller.create(
            req, {"security_service": sec_service})
        expected = self.ss_active_directory.copy()
        self.assertEqual({'security_service': expected}, res_dict)

    def test_security_service_create_invalid_types(self):
        sec_service = self.ss_active_directory.copy()
        sec_service['type'] = 'invalid'
        req = fakes.HTTPRequest.blank('/security-services')
        self.assertRaises(exception.InvalidInput, self.controller.create, req,
                          {"security_service": sec_service})

    @ddt.data('2.76')
    def test_security_service_create_invalid_active_directory(self, version):
        sec_service = self.ss_active_directory.copy()
        sec_service['default_ad_site'] = 'fake_default_ad_site'
        req = fakes.HTTPRequest.blank('/security-services', version=version)
        self.assertRaises(exception.InvalidInput, self.controller.create, req,
                          {"security_service": sec_service})

    def test_create_security_service_no_body(self):
        body = {}
        req = fakes.HTTPRequest.blank('/security-services')
        self.assertRaises(webob.exc.HTTPUnprocessableEntity,
                          self.controller.create,
                          req,
                          body)

    def test_security_service_delete(self):
        db.security_service_delete = mock.Mock()
        db.security_service_get = mock.Mock()
        db.share_network_get_all_by_security_service = mock.Mock(
            return_value=[])
        req = fakes.HTTPRequest.blank('/security_services/1')
        resp = self.controller.delete(req, 1)
        db.security_service_delete.assert_called_once_with(
            req.environ['manila.context'], 1)
        self.assertEqual(202, resp.status_int)

    def test_security_service_delete_not_found(self):
        db.security_service_get = mock.Mock(side_effect=exception.NotFound)
        req = fakes.HTTPRequest.blank('/security_services/1')
        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.delete,
                          req,
                          1)

    def test_security_service_delete_has_share_networks(self):
        db.security_service_get = mock.Mock()
        db.share_network_get_all_by_security_service = mock.Mock(
            return_value=[{'share_network': 'fake_share_network'}])
        req = fakes.HTTPRequest.blank('/security_services/1')
        self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
                          req, 1)

    def test_security_service_update_name(self):
        new = self.ss_active_directory.copy()
        updated = self.ss_active_directory.copy()
        updated['name'] = 'new'
        self.mock_object(security_service.policy, 'check_policy')
        db.security_service_get = mock.Mock(return_value=new)
        db.security_service_update = mock.Mock(return_value=updated)
        fake_sns = {'id': 'fake_sns_id', 'share_servers': ['fake_ss']}
        db.share_network_get_all_by_security_service = mock.Mock(
            return_value=[{
                'id': 'fake_id',
                'share_network_subnets': [fake_sns]
            }])
        body = {"security_service": {"name": "new"}}
        req = fakes.HTTPRequest.blank('/security_service/1')
        res_dict = self.controller.update(req, 1, body)['security_service']
        self.assertEqual(updated['name'], res_dict['name'])
        db.share_network_get_all_by_security_service.assert_called_once_with(
            req.environ['manila.context'], 1)
        self.assertEqual(2, security_service.policy.check_policy.call_count)
        security_service.policy.check_policy.assert_has_calls([
            mock.call(req.environ['manila.context'],
                      security_service.RESOURCE_NAME, 'update', new)
        ])

    def test_security_service_update_description(self):
        new = self.ss_active_directory.copy()
        updated = self.ss_active_directory.copy()
        updated['description'] = 'new'
        self.mock_object(security_service.policy, 'check_policy')
        db.security_service_get = mock.Mock(return_value=new)
        db.security_service_update = mock.Mock(return_value=updated)
        fake_sns = {'id': 'fake_sns_id', 'share_servers': ['fake_ss']}
        db.share_network_get_all_by_security_service = mock.Mock(
            return_value=[{
                'id': 'fake_id',
                'share_network_subnets': [fake_sns]
            }])
        body = {"security_service": {"description": "new"}}
        req = fakes.HTTPRequest.blank('/security_service/1')
        res_dict = self.controller.update(req, 1, body)['security_service']
        self.assertEqual(updated['description'], res_dict['description'])
        db.share_network_get_all_by_security_service.assert_called_once_with(
            req.environ['manila.context'], 1)
        self.assertEqual(2, security_service.policy.check_policy.call_count)
        security_service.policy.check_policy.assert_has_calls([
            mock.call(req.environ['manila.context'],
                      security_service.RESOURCE_NAME, 'update', new)
        ])

    @mock.patch.object(db, 'security_service_get', mock.Mock())
    @mock.patch.object(db, 'share_network_get_all_by_security_service',
                       mock.Mock())
    def test_security_service_update_invalid_keys_sh_server_exists(self):
        self.mock_object(security_service.policy, 'check_policy')
        fake_sns = {'id': 'fake_sns_id', 'share_servers': ['fake_ss']}
        db.share_network_get_all_by_security_service.return_value = [
            {'id': 'fake_id', 'share_network_subnets': [fake_sns]},
        ]
        db.security_service_get.return_value = self.ss_active_directory.copy()
        body = {'security_service': {'user_id': 'new_user'}}
        req = fakes.HTTPRequest.blank('/security_services/1')
        self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
                          req, 1, body)
        db.security_service_get.assert_called_once_with(
            req.environ['manila.context'], 1)
        db.share_network_get_all_by_security_service.assert_called_once_with(
            req.environ['manila.context'], 1)
        self.assertEqual(1, security_service.policy.check_policy.call_count)
        security_service.policy.check_policy.assert_has_calls([
            mock.call(req.environ['manila.context'],
                      security_service.RESOURCE_NAME, 'update',
                      db.security_service_get.return_value)
        ])

    @mock.patch.object(db, 'security_service_get', mock.Mock())
    @mock.patch.object(db, 'security_service_update', mock.Mock())
    @mock.patch.object(db, 'share_network_get_all_by_security_service',
                       mock.Mock())
    def test_security_service_update_valid_keys_sh_server_exists(self):
        self.mock_object(security_service.policy, 'check_policy')
        fake_sns = {'id': 'fake_sns_id', 'share_servers': ['fake_ss']}
        db.share_network_get_all_by_security_service.return_value = [
            {'id': 'fake_id', 'share_network_subnets': [fake_sns]},
        ]
        old = self.ss_active_directory.copy()
        updated = self.ss_active_directory.copy()
        updated['name'] = 'new name'
        updated['description'] = 'new description'
        db.security_service_get.return_value = old
        db.security_service_update.return_value = updated
        body = {
            'security_service': {
                'description': 'new description',
                'name': 'new name',
            },
        }
        req = fakes.HTTPRequest.blank('/security_services/1')
        res_dict = self.controller.update(req, 1, body)['security_service']
        self.assertEqual(updated['description'], res_dict['description'])
        self.assertEqual(updated['name'], res_dict['name'])
        db.security_service_get.assert_called_once_with(
            req.environ['manila.context'], 1)
        db.share_network_get_all_by_security_service.assert_called_once_with(
            req.environ['manila.context'], 1)
        db.security_service_update.assert_called_once_with(
            req.environ['manila.context'], 1, body['security_service'])
        self.assertEqual(2, security_service.policy.check_policy.call_count)
        security_service.policy.check_policy.assert_has_calls([
            mock.call(req.environ['manila.context'],
                      security_service.RESOURCE_NAME, 'update', old)
        ])

    @mock.patch.object(db, 'security_service_get', mock.Mock())
    def test_security_service_update_has_share_servers(self):
        db.security_service_get = mock.Mock()
        self.mock_object(
            self.controller, '_share_servers_dependent_on_sn_exist',
            mock.Mock(return_value=True))
        body = {"security_service": {"type": "ldap"}}

        req = fakes.HTTPRequest.blank('/security_services/1')
        self.assertRaises(webob.exc.HTTPForbidden,
                          self.controller.update,
                          req,
                          1,
                          body)

    @ddt.data(True, False)
    def test_security_service_update_share_server_dependent_exists(self,
                                                                   expected):
        req = fakes.HTTPRequest.blank('/security_services/1')
        context = req.environ['manila.context']
        db.security_service_get = mock.Mock()
        network = (self.fake_share_network_list_with_share_servers if expected
                   else self.fake_share_network_list_without_share_servers)
        db.share_network_get_all_by_security_service = mock.Mock(
            return_value=network)

        result = self.controller._share_servers_dependent_on_sn_exist(
            context, 'fake_id')
        self.assertEqual(expected, result)

    def test_security_service_list(self):
        db.security_service_get_all_by_project = mock.Mock(
            return_value=[self.ss_active_directory.copy()])
        req = fakes.HTTPRequest.blank('/security_services')
        res_dict = self.controller.index(req)
        self.assertEqual(self.security_service_list_expected_resp, res_dict)

    @mock.patch.object(db, 'share_network_get', mock.Mock())
    def test_security_service_list_filter_by_sn(self):
        sn = {
            'id': 'fake_sn_id',
            'security_services': [self.ss_active_directory, ],
        }
        db.share_network_get.return_value = sn
        req = fakes.HTTPRequest.blank(
            '/security-services?share_network_id=fake_sn_id')
        res_dict = self.controller.index(req)
        self.assertEqual(self.security_service_list_expected_resp, res_dict)
        db.share_network_get.assert_called_once_with(
            req.environ['manila.context'],
            sn['id'])

    @mock.patch.object(db, 'security_service_get_all_by_project', mock.Mock())
    @mock.patch.object(db, 'security_service_get_all', mock.Mock())
    def test_security_services_list_all_tenants_policy_authorized(self):
        self.check_policy_patcher.stop()
        db.security_service_get_all.return_value = [
            self.ss_active_directory,
            self.ss_ldap,
        ]
        req = fakes.HTTPRequest.blank(
            '/security-services?all_tenants=1')
        self.mock_object(policy, "check_policy", mock.Mock(return_value=True))
        fake_context = req.environ['manila.context']
        self.controller.index(req)
        db.security_service_get_all_by_project.assert_not_called()
        db.security_service_get_all.assert_called_once_with(fake_context)

    @mock.patch.object(db, 'security_service_get_all_by_project', mock.Mock())
    @mock.patch.object(db, 'security_service_get_all', mock.Mock())
    def test_security_services_list_all_tenants_policy_not_authorized(self):
        self.check_policy_patcher.stop()
        db.security_service_get_all.return_value = [
            self.ss_active_directory,
            self.ss_ldap,
        ]
        req = fakes.HTTPRequest.blank(
            '/security-services?all_tenants=1')
        self.mock_object(policy,
                         "check_policy",
                         mock.Mock(side_effect=exception.NotAuthorized()))
        self.assertRaises(exception.NotAuthorized, self.controller.index, req)
        db.security_service_get_all_by_project.assert_not_called()
        db.security_service_get_all.assert_not_called()

    @mock.patch.object(db, 'security_service_get_all', mock.Mock())
    def test_security_services_list_all_tenants_with_invalid_value(self):
        req = fakes.HTTPRequest.blank(
            '/security-services?all_tenants=nerd',
            use_admin_context=True)
        self.assertRaises(exception.InvalidInput, self.controller.index, req)

    @mock.patch.object(db, 'security_service_get_all_by_project', mock.Mock())
    def test_security_services_list_all_tenants_with_value_zero(self):
        db.security_service_get_all_by_project.return_value = []
        req = fakes.HTTPRequest.blank(
            '/security-services?all_tenants=0',
            use_admin_context=True)
        res_dict = self.controller.index(req)
        self.assertEqual({'security_services': []}, res_dict)
        db.security_service_get_all_by_project.assert_called_once_with(
            req.environ['manila.context'],
            req.environ['manila.context'].project_id)

    @mock.patch.object(db, 'security_service_get_all_by_project', mock.Mock())
    def test_security_services_list_admin_context_invalid_opts(self):
        db.security_service_get_all_by_project.return_value = [
            self.ss_active_directory,
            self.ss_ldap,
        ]
        req = fakes.HTTPRequest.blank(
            '/security-services?fake_opt=fake_value',
            use_admin_context=True)
        res_dict = self.controller.index(req)
        self.assertEqual({'security_services': []}, res_dict)
        db.security_service_get_all_by_project.assert_called_once_with(
            req.environ['manila.context'],
            req.environ['manila.context'].project_id)

    @mock.patch.object(db, 'security_service_get_all_by_project', mock.Mock())
    def test_security_service_list_all_filter_opts_separately(self):
        db.security_service_get_all_by_project.return_value = [
            self.ss_active_directory,
            self.ss_ldap,
        ]
        for opt, val in self.valid_search_opts.items():
            for use_admin_context in [True, False]:
                req = fakes.HTTPRequest.blank(
                    '/security-services?' + opt + '=' + val,
                    use_admin_context=use_admin_context)
                res_dict = self.controller.index(req)
                self.assertEqual(self.security_service_list_expected_resp,
                                 res_dict)
                db.security_service_get_all_by_project.assert_called_with(
                    req.environ['manila.context'],
                    req.environ['manila.context'].project_id)

    @mock.patch.object(db, 'security_service_get_all_by_project', mock.Mock())
    def test_security_service_list_all_filter_opts(self):
        db.security_service_get_all_by_project.return_value = [
            self.ss_active_directory,
            self.ss_ldap,
        ]
        query_string = '/security-services?' + parse.urlencode(sorted(
            [(k, v) for (k, v) in list(self.valid_search_opts.items())]))
        for use_admin_context in [True, False]:
            req = fakes.HTTPRequest.blank(query_string,
                                          use_admin_context=use_admin_context)
            res_dict = self.controller.index(req)
            self.assertEqual(self.security_service_list_expected_resp,
                             res_dict)
            db.security_service_get_all_by_project.assert_called_with(
                req.environ['manila.context'],
                req.environ['manila.context'].project_id)
