"""
OAuth2 / OpenID Connect authentication related view handler and
supporting utilities.
"""

__author__      = "Graham Klyne (GK@ACM.ORG)"
__copyright__   = "Copyright 2016, G. Klyne"
__license__     = "MIT (http://opensource.org/licenses/MIT)"

import os
import re
import uuid

import logging
log = logging.getLogger(__name__)

import httplib2

from oauth2client.client import OAuth2WebServerFlow
from oauth2client import xsrfutil
from oauth2client.client import flow_from_clientsecrets, FlowExchangeError
from oauth2client.django_orm import Storage

from django.core.urlresolvers import resolve, reverse
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.template import RequestContext, loader
from django.views import generic
from django.views.decorators.csrf import csrf_exempt

from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User

from utils.http_errors import error400values

from login_utils    import HttpResponseRedirectWithQuery, HttpResponseRedirectLogin, object_to_dict
from models         import CredentialsModel
import login_message

FLOW_SECRET_KEY = str(uuid.uuid1())

SCOPE_DEFAULT = "openid profile email"

OAuth2WebServerFlow_strip = (
    "step1_get_authorize_url",
    "step2_exchange"
    )

def oauth2_flow_from_provider_details(provider_details_file, scope=SCOPE_DEFAULT, redirect_uri=None):
    """
    Create an OpenId connect Oauth2 flow object from a supplied provider details file.

    provider_details_file
            name of file containing provoder detaols (including oauth2 client secrets).
    scope   
            OAuth2 scope names for which access is requested (see SCOPE_DEFAULT).
    redirect_uri
            URI to which control is transferred when the OAuth2 authetication dance is completed.
    """
    return flow_from_clientsecrets(
        provider_details_file, scope=scope, redirect_uri=redirect_uri
        )

def oauth2_get_state_token(request_user):
    """
    Get a cross-site request forgery protection token for the current-request user.
    """
    return xsrfutil.generate_token(FLOW_SECRET_KEY, request_user)

def oauth2_flow_to_dict(f):
    """
    Convert an OAuth2 flow object to a dictionary.
    """
    return object_to_dict(f, OAuth2WebServerFlow_strip) 

def oauth2_dict_to_flow(d):
    """
    Constructs a OAuth2WebServerFlow object from a dictionary previously 
    created by oauth2_flow_to_dict.

    Args:
        d:  dict, generated by object_to_dict

    @@TODO: handle multiple flow object types.
    (Might need django.util.module_loading.import_string)
    """
    flow = OAuth2WebServerFlow(
        d['client_id'], d['client_secret'], d['scope'],
        redirect_uri=d['redirect_uri'], 
        user_agent=d['user_agent'],
        auth_uri=d['auth_uri'], 
        token_uri=d['token_uri'],
        revoke_uri=d['revoke_uri'],
        **d['params']
        )
    return flow

class OIDC_AuthDoneView(generic.View):
    """
    View class used to complete login process with authorization grant provided by
    OAuth2 authorization server.
    """

    def get(self, request):
        # Look for authorization grant
        flow             = oauth2_dict_to_flow(request.session['oauth2flow'])
        userid           = flow.params['userid']
        provider_details = request.session['provider_details']
        provider         = provider_details['provider']
        # Get authenticated user details
        try:
            credential = flow.step2_exchange(request.REQUEST) # Raises FlowExchangeError if a problem occurs
            authuser = authenticate(
                username=userid, password=credential, 
                profile_uri=provider_details['profile_uri']
                )
        except FlowExchangeError, e:
            log.error("provider_details %r"%(provider_details,))
            return HttpResponseRedirectLogin(request, str(e))
        # Check authenticated details for user id match any previous values.
        #
        # The user id is entered by the user on the login form, and is used as a key to
        # access authenticated user details in the Django user database.  The user id 
        # itself is not checked by the Oauth2 login flow, other than for checking that
        # it containbs only work characters
        #
        # Instead, we trust that the associated email address has been confirmed by the 
        # OAuth2 provider, and don't allow login where the email adress differs from any 
        # currently saved email address for the user id used..  This aims to  prevent a 
        # new set of OAuth2 credentials being used for a previously created Django user id.
        #
        if not authuser:
            return HttpResponseRedirectLogin(request, 
                login_message.USER_NOT_AUTHENTICATED%(userid, provider)
                )
        if not userid:
            # Get generated username
            userid = authuser.username
        if not re.match(r"\w+$", userid):
            return HttpResponseRedirectLogin(
                request, 
                login_message.USER_ID_SYNTAX%(userid)
                )
        if not authuser.email:
            return HttpResponseRedirectLogin(request, 
                login_message.USER_NO_EMAIL%(userid)
                )
        try:
            olduser = User.objects.get(username=userid)
        except User.DoesNotExist:
            olduser = None
        if olduser:
            if authuser.email != olduser.email:
                return HttpResponseRedirectLogin(request, 
                    login_message.USER_WRONG_EMAIL%(userid, authuser.email, olduser.email)
                    )
        # Complete the login and save details
        authuser.save()
        login(request, authuser)
        request.session['recent_userid'] = userid
        storage = Storage(CredentialsModel, 'id', request.user, 'credential')
        storage.put(credential)
        # Don't normally log the credential/token as they might represent a security leakage:
        # log.debug("OIDC_AuthDoneView: credential:      "+repr(credential.to_json()))
        # log.info("OIDC_AuthDoneView: id_token:        "+repr(credential.id_token))
        log.info("OIDC_AuthDoneView: user.username:   "+authuser.username)
        log.info("OIDC_AuthDoneView: user.first_name: "+authuser.first_name)
        log.info("OIDC_AuthDoneView: user.last_name:  "+authuser.last_name)
        log.info("OIDC_AuthDoneView: user.email:      "+authuser.email)
        return HttpResponseRedirectLogin(request)

# End.
