# -*- coding: utf-8 -*-

# Copyright 2014 OpenMarket Ltd
# Copyright 2018 New Vector Ltd
#
# 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 __future__ import absolute_import

import json
import logging

from sydent.hs_federation.verifier import NoAuthenticationError
from signedjson.sign import SignatureVerifyException

from sydent.http.servlets import dict_to_json_bytes
from sydent.db.valsession import ThreePidValSessionStore
from sydent.util.stringutils import is_valid_client_secret
from sydent.validators import (
    IncorrectClientSecretException,
    InvalidSessionIdException,
    SessionNotValidatedException,
)

from twisted.web.resource import Resource
from twisted.web import server
from twisted.internet import defer

logger = logging.getLogger(__name__)


class ThreePidUnbindServlet(Resource):
    def __init__(self, sydent):
        self.sydent = sydent

    def render_POST(self, request):
        self._async_render_POST(request)
        return server.NOT_DONE_YET

    @defer.inlineCallbacks
    def _async_render_POST(self, request):
        try:
            try:
                # json.loads doesn't allow bytes in Python 3.5
                body = json.loads(request.content.read().decode("UTF-8"))
            except ValueError:
                request.setResponseCode(400)
                request.write(dict_to_json_bytes({'errcode': 'M_BAD_JSON', 'error': 'Malformed JSON'}))
                request.finish()
                return

            missing = [k for k in ("threepid", "mxid") if k not in body]
            if len(missing) > 0:
                request.setResponseCode(400)
                msg = "Missing parameters: "+(",".join(missing))
                request.write(dict_to_json_bytes({'errcode': 'M_MISSING_PARAMS', 'error': msg}))
                request.finish()
                return

            threepid = body['threepid']
            mxid = body['mxid']

            if 'medium' not in threepid or 'address' not in threepid:
                request.setResponseCode(400)
                request.write(dict_to_json_bytes({'errcode': 'M_MISSING_PARAMS', 'error': 'Threepid lacks medium / address'}))
                request.finish()
                return

            # We now check for authentication in two different ways, depending
            # on the contents of the request. If the user has supplied "sid"
            # (the Session ID returned by Sydent during the original binding)
            # and "client_secret" fields, they are trying to prove that they
            # were the original author of the bind. We then check that what
            # they supply matches and if it does, allow the unbind.
            # 
            # However if these fields are not supplied, we instead check
            # whether the request originated from a homeserver, and if so the
            # same homeserver that originally created the bind. We do this by
            # checking the signature of the request. If it all matches up, we
            # allow the unbind.
            #
            # Only one method of authentication is required.
            if 'sid' in body and 'client_secret' in body:
                sid = body['sid']
                client_secret = body['client_secret']

                if not is_valid_client_secret(client_secret):
                    request.setResponseCode(400)
                    request.write(dict_to_json_bytes({
                        'errcode': 'M_INVALID_PARAM',
                        'error': 'Invalid client_secret provided'
                    }))
                    request.finish()
                    return

                valSessionStore = ThreePidValSessionStore(self.sydent)

                try:
                    s = valSessionStore.getValidatedSession(sid, client_secret)
                except (IncorrectClientSecretException, InvalidSessionIdException):
                    request.setResponseCode(401)
                    request.write(dict_to_json_bytes({
                        'errcode': 'M_NO_VALID_SESSION',
                        'error': "No valid session was found matching that sid and client secret"
                    }))
                    request.finish()
                    return
                except SessionNotValidatedException:
                    request.setResponseCode(403)
                    request.write(dict_to_json_bytes({
                        'errcode': 'M_SESSION_NOT_VALIDATED',
                        'error': "This validation session has not yet been completed"
                    }))
                    return
                
                if s.medium != threepid['medium'] or s.address != threepid['address']:
                    request.setResponseCode(403)
                    request.write(dict_to_json_bytes({
                        'errcode': 'M_FORBIDDEN',
                        'error': 'Provided session information does not match medium/address combo',
                    }))
                    request.finish()
                    return
            else:
                try:
                    origin_server_name = yield self.sydent.sig_verifier.authenticate_request(request, body)
                except SignatureVerifyException as ex:
                    request.setResponseCode(401)
                    request.write(dict_to_json_bytes({'errcode': 'M_FORBIDDEN', 'error': str(ex)}))
                    request.finish()
                    return
                except NoAuthenticationError as ex:
                    request.setResponseCode(401)
                    request.write(dict_to_json_bytes({'errcode': 'M_FORBIDDEN', 'error': str(ex)}))
                    request.finish()
                    return
                except:
                    logger.exception("Exception whilst authenticating unbind request")
                    request.setResponseCode(500)
                    request.write(dict_to_json_bytes({'errcode': 'M_UNKNOWN', 'error': 'Internal Server Error'}))
                    request.finish()
                    return

                if not mxid.endswith(':' + origin_server_name):
                    request.setResponseCode(403)
                    request.write(dict_to_json_bytes({'errcode': 'M_FORBIDDEN', 'error': 'Origin server name does not match mxid'}))
                    request.finish()
                    return

            self.sydent.threepidBinder.removeBinding(threepid, mxid)

            request.write(dict_to_json_bytes({}))
            request.finish()
        except Exception as ex:
            logger.exception("Exception whilst handling unbind")
            request.setResponseCode(500)
            request.write(dict_to_json_bytes({'errcode': 'M_UNKNOWN', 'error': str(ex)}))
            request.finish()
