# Copyright (c) 2016 EMC Corporation
# 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.


try:
    import cookielib as cookie_lib
except ImportError:
    import http.cookiejar as cookie_lib
import socket

import requests
from requests import exceptions
import six

from cinder.i18n import _
from cinder.volume.drivers.coprhd.helpers import commoncoprhdapi as common


class Authentication(common.CoprHDResource):

    # Commonly used URIs for the 'Authentication' module
    URI_SERVICES_BASE = ''
    URI_AUTHENTICATION = '/login'

    HEADERS = {'Content-Type': 'application/json',
               'ACCEPT': 'application/json', 'X-EMC-REST-CLIENT': 'TRUE'}

    def authenticate_user(self, username, password):
        """Makes REST API call to generate the authentication token.

        Authentication token is generated for the specified user after
        validation

        :param username: Name of the user
        :param password: Password for the user
        :returns: The authtoken
        """

        SEC_REDIRECT = 302
        SEC_AUTHTOKEN_HEADER = 'X-SDS-AUTH-TOKEN'
        LB_API_PORT = 4443
        # Port on which load-balancer/reverse-proxy listens to all incoming
        # requests for CoprHD REST APIs
        APISVC_PORT = 8443  # Port on which apisvc listens to incoming requests

        cookiejar = cookie_lib.LWPCookieJar()

        url = ('https://%(ip)s:%(port)d%(uri)s' %
               {'ip': self.ipaddr, 'port': self.port,
                'uri': self.URI_AUTHENTICATION})

        try:
            if self.port == APISVC_PORT:
                login_response = requests.get(
                    url, headers=self.HEADERS, verify=False,
                    auth=(username, password), cookies=cookiejar,
                    allow_redirects=False, timeout=common.TIMEOUT_SEC)
                if login_response.status_code == SEC_REDIRECT:
                    location = login_response.headers['Location']
                    if not location:
                        raise common.CoprHdError(
                            common.CoprHdError.HTTP_ERR, (_("The redirect"
                                                            " location of the"
                                                            " authentication"
                                                            " service is not"
                                                            " provided")))
                    # Make the second request
                    login_response = requests.get(
                        location, headers=self.HEADERS, verify=False,
                        cookies=cookiejar, allow_redirects=False,
                        timeout=common.TIMEOUT_SEC)
                    if (login_response.status_code !=
                            requests.codes['unauthorized']):
                        raise common.CoprHdError(
                            common.CoprHdError.HTTP_ERR, (_("The"
                                                            " authentication"
                                                            " service failed"
                                                            " to reply with"
                                                            " 401")))

                    # Now provide the credentials
                    login_response = requests.get(
                        location, headers=self.HEADERS,
                        auth=(username, password), verify=False,
                        cookies=cookiejar, allow_redirects=False,
                        timeout=common.TIMEOUT_SEC)
                    if login_response.status_code != SEC_REDIRECT:
                        raise common.CoprHdError(
                            common.CoprHdError.HTTP_ERR,
                            (_("Access forbidden: Authentication required")))
                    location = login_response.headers['Location']
                    if not location:
                        raise common.CoprHdError(
                            common.CoprHdError.HTTP_ERR,
                            (_("The"
                               " authentication service failed to provide the"
                               " location of the service URI when redirecting"
                               " back")))
                    authtoken = login_response.headers[SEC_AUTHTOKEN_HEADER]
                    if not authtoken:
                        details_str = self.extract_error_detail(login_response)
                        raise common.CoprHdError(common.CoprHdError.HTTP_ERR,
                                                 (_("The token is not"
                                                    " generated by"
                                                    " authentication service."
                                                    "%s") %
                                                  details_str))
                    # Make the final call to get the page with the token
                    new_headers = self.HEADERS
                    new_headers[SEC_AUTHTOKEN_HEADER] = authtoken
                    login_response = requests.get(
                        location, headers=new_headers, verify=False,
                        cookies=cookiejar, allow_redirects=False,
                        timeout=common.TIMEOUT_SEC)
                    if login_response.status_code != requests.codes['ok']:
                        raise common.CoprHdError(
                            common.CoprHdError.HTTP_ERR, (_(
                                "Login failure code: "
                                "%(statuscode)s Error: %(responsetext)s") %
                                {'statuscode': six.text_type(
                                    login_response.status_code),
                                 'responsetext': login_response.text}))
            elif self.port == LB_API_PORT:
                login_response = requests.get(
                    url, headers=self.HEADERS, verify=False,
                    cookies=cookiejar, allow_redirects=False)

                if(login_response.status_code ==
                   requests.codes['unauthorized']):
                    # Now provide the credentials
                    login_response = requests.get(
                        url, headers=self.HEADERS, auth=(username, password),
                        verify=False, cookies=cookiejar, allow_redirects=False)
                authtoken = None
                if SEC_AUTHTOKEN_HEADER in login_response.headers:
                    authtoken = login_response.headers[SEC_AUTHTOKEN_HEADER]
            else:
                raise common.CoprHdError(
                    common.CoprHdError.HTTP_ERR,
                    (_("Incorrect port number. Load balanced port is: "
                       "%(lb_api_port)s, api service port is: "
                       "%(apisvc_port)s") %
                     {'lb_api_port': LB_API_PORT,
                        'apisvc_port': APISVC_PORT}))

            if not authtoken:
                details_str = self.extract_error_detail(login_response)
                raise common.CoprHdError(
                    common.CoprHdError.HTTP_ERR,
                    (_("The token is not generated by authentication service."
                       " %s") % details_str))

            if login_response.status_code != requests.codes['ok']:
                error_msg = None
                if login_response.status_code == 401:
                    error_msg = _("Access forbidden: Authentication required")
                elif login_response.status_code == 403:
                    error_msg = _("Access forbidden: You don't have"
                                  " sufficient privileges to perform"
                                  " this operation")
                elif login_response.status_code == 500:
                    error_msg = _("Bourne internal server error")
                elif login_response.status_code == 404:
                    error_msg = _(
                        "Requested resource is currently unavailable")
                elif login_response.status_code == 405:
                    error_msg = (_("GET method is not supported by resource:"
                                   " %s"),
                                 url)
                elif login_response.status_code == 503:
                    error_msg = _("Service temporarily unavailable:"
                                  " The server is temporarily unable"
                                  " to service your request")
                else:
                    error_msg = login_response.text
                raise common.CoprHdError(common.CoprHdError.HTTP_ERR,
                                         (_("HTTP code: %(status_code)s"
                                            ", response: %(reason)s"
                                            " [%(error_msg)s]") % {
                                             'status_code': six.text_type(
                                                 login_response.status_code),
                                             'reason': six.text_type(
                                                 login_response.reason),
                                             'error_msg': six.text_type(
                                                 error_msg)
                                         }))
        except (exceptions.SSLError, socket.error, exceptions.ConnectionError,
                exceptions.Timeout) as e:
            raise common.CoprHdError(
                common.CoprHdError.HTTP_ERR, six.text_type(e))

        return authtoken

    def extract_error_detail(self, login_response):
        details_str = ""
        try:
            if login_response.content:
                json_object = common.json_decode(login_response.content)
                if 'details' in json_object:
                    details_str = json_object['details']

            return details_str
        except common.CoprHdError:
            return details_str
