/*!
 * authorize.cpp
 *
 * Copyright (c) 2015, NADAL Jean-Baptiste. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA
 * 
 * @Author:  NADAL Jean-Baptiste
 * @Date: 04/03/2015
 *
 */

/*-------------------------------- INCLUDES ---------------------------------*/

#include <string.h>
#include <crypt.h>

#include <cstdio>
#include <iostream>
#include <fstream>

#include <jsoncpp/json.h>

#include "restd.h"

#include "authorize.h"
#include "session.h"
#include "message.h"


/*-------------------------------- GLOBALE ---------------------------------*/

static const char *gAuthorize_Url = "/authorize";
static const char *gLogin_Url = "/login.html";

/*--------------------------- LOCAL FUNCTIONS -------------------------------*/

static void redirect_to_ssl (struct mg_connection *aConn,
                            const struct mg_request_info *aRequest_info);
static void redirect_to_login (struct mg_connection *aConn,
                               const struct mg_request_info *aRequest_info);
static int is_authorized (const struct mg_connection *aConn,
                          const struct mg_request_info *aRequest_info);
static void authorize (struct mg_connection *aConn,
                       const struct mg_request_info *aRequest_info);
int check_password (const char *aUser, const char *aPassword);


/*!  ----------------------------------------------------------------------------
 *  @fn my_strlcpy
 *
 *  @brief  TODO
 */
static void my_strlcpy (char *dst, const char *src, size_t len)
{
    strncpy(dst, src, len);
    dst[len - 1] = '\0';
}


/*!  ----------------------------------------------------------------------------
 *  @fn begin_request_handler
 *
 *  @brief TODO.
 */
int begin_request_handler (struct mg_connection *aConn)
{
    const struct mg_request_info *theRequest_Info = mg_get_request_info (aConn);
    int theProcessed = 1;

    printf ("begin_request_handler: request_info->uri%s\n", theRequest_Info->uri);

    if (!theRequest_Info->is_ssl) {

        printf ("clear request => redirect to ssl.\n");
        redirect_to_ssl (aConn, theRequest_Info);
    }
    else if (!is_authorized (aConn, theRequest_Info)) {

        printf ("Redirect to the login page.\n");
        redirect_to_login (aConn, theRequest_Info);
    }
    else if (strcmp (theRequest_Info->uri, gAuthorize_Url) == 0) {

        printf ("Redirect to authorize.\n");
        authorize (aConn, theRequest_Info);
    }
    else {
        // No suitable handler found, mark as not processed. Civetweb will
        // try to serve the request.
        theProcessed = 0;
    }
    
    return theProcessed;
}


/*!  ----------------------------------------------------------------------------
 *  @fn redirect_to_ssl
 *
 *  @brief This method force the reconnection to the https port, if we try to
           access with an http request.
 */
static void redirect_to_ssl (struct mg_connection *aConn,
                            const struct mg_request_info *aRequest_info)
{
  const char *aP, *anHost = mg_get_header (aConn, "Host");

  if (anHost != NULL && (aP = strchr (anHost, ':')) != NULL) {
      
    mg_printf (aConn, "HTTP/1.1 302 Found\r\n"
                      "Location: https://%.*s:8082/%s:8082\r\n\r\n",
                      (int) (aP - anHost), anHost, aRequest_info->uri);
  } 
  else {

    mg_printf (aConn, "%s", "HTTP/1.1 500 Error\r\n\r\nHost: header is not set");
  }
}


/*!  ----------------------------------------------------------------------------
 *  @fn redirect_to_login
 *
 *  @brief Redirect user to the login form. In the cookie, store the original URL
 * we came from, so that after the authorization we could redirect back.
 */
static void redirect_to_login (struct mg_connection *aConn,
                               const struct mg_request_info *aRequest_info)
{
  mg_printf (aConn, "HTTP/1.1 302 Found\r\n"
                    "Set-Cookie: original_url=%s\r\n"
                    "Location: %s\r\n\r\n",
                    aRequest_info->uri, gLogin_Url);
}


/*!  ----------------------------------------------------------------------------
 *  @fn is_authorized
 *
 *  @brief Return 1 if request is authorized, 0 otherwise.
 */
static int is_authorized (const struct mg_connection *aConn,
                          const struct mg_request_info *aRequest_info)
{ 
    struct session *session;
    char valid_id[33];
    int authorized = 0;

    // Always authorize accesses to login page and to authorize URI
    if (!strcmp (aRequest_info->uri, gLogin_Url) ||
        !strcmp (aRequest_info->uri, gAuthorize_Url)) {
        return 1;
    }

    pthread_rwlock_rdlock (&gRwlock);
    if ((session = get_session (aConn)) != NULL) {
        generate_session_id (valid_id, session->random, session->user);
        if (strcmp (valid_id, session->session_id) == 0) {
            session->expire = time(0) + SESSION_TTL;
            authorized = 1;
        }
    }
    pthread_rwlock_unlock (&gRwlock);

    return authorized;
}


/*!  ----------------------------------------------------------------------------
 *  @fn authorize
 *
 *  @brief A handler for the /authorize endpoint.
 *         Login page form sends user name and password to this endpoint.
 */
static void authorize (struct mg_connection *aConn,
                       const struct mg_request_info *aRequest_info)
{
  char user[MAX_USER_LEN], password[MAX_USER_LEN];
  struct session *session;

  // Fetch user name and password.
  get_qsvar (aRequest_info, "user", user, sizeof (user));
  get_qsvar (aRequest_info, "password", password, sizeof (password));

  if (check_password (user, password) && (session = new_session()) != NULL) {
    
    /* Authentication success:
     *   1. create new session
     *   2. set session ID token in the cookie
     *   3. remove original_url from the cookie - not needed anymore
     *   4. redirect client back to the original URL
     *
     * The most secure way is to stay HTTPS all the time. However, just to
     * show the technique, we redirect to HTTP after the successful
     * authentication. The danger of doing this is that session cookie can
     * be stolen and an attacker may impersonate the user.
     * Secure application must use HTTPS all the time.
     */
    my_strlcpy (session->user, user, sizeof (session->user));
    snprintf (session->random, sizeof (session->random), "%d", rand());
    generate_session_id (session->session_id, session->random, session->user);
    send_server_message ("<%s> joined", session->user);
    mg_printf (aConn, "HTTP/1.1 302 Found\r\n"
                      "Set-Cookie: session=%s; max-age=3600; http-only\r\n"  // Session ID
                      "Set-Cookie: user=%s\r\n"  // Set user, needed by Javascript code
                      "Set-Cookie: original_url=/; max-age=0\r\n"  // Delete original_url
                      "Location: /\r\n\r\n",
                      session->session_id, session->user);
  } else {
    // Authentication failure, redirect to login.
    redirect_to_login (aConn, aRequest_info);
  }
}


/*!  ----------------------------------------------------------------------------
 *  @fn check_password
 *
 *  @brief TODO.
 */
int check_password (const char *aUser, const char *aPassword)
{
  int theResult;
  Json::Value theRoot;
  Json::Value theUsers;
  Json::Reader theReader;
  std::ifstream thePassFile ("./rsc/users.json");

  if (!theReader.parse (thePassFile, theRoot)) {

    fprintf (stderr, "Failed to parse the User File.\n");
    return 0;
  }

  for (const Json::Value& theUser : theRoot["users"]) {

    if (theUser["login"].asString() == aUser) {

      char *theCrypted;
      theCrypted = crypt (aPassword, theUser["password"].asString().c_str());

      printf ("compare: <%s> AND <%s> \n", theCrypted, theUser["password"].asString().c_str());

      theResult = strcmp (theCrypted, theUser["password"].asString().c_str()) == 0;
      printf ("resultat : %d\n", theResult);
    }
  }

  printf ("check_password - done\n");

  return theResult;
}
