#!/usr/bin/python3
"""
This script verifies a user connecting to the VPN based on their username, and password and
target node.

It handles node selection using the "user:node" username format. The script will resolve the
target node, and return a failure if it can not validate the target node for any reason.

The script is called by OpenVPN (see auth-user-pass-verify in server.conf) with the following
params passed in via ENV:
- username
- password

Note: no longer support the old certificate format with node ID specified in the common name
(we don't want these certs to work anymore as they don't fit the new auth model).

The script exits 0 on success, 1 on failure.
"""

import os
from sys import exit
import central_sdi
from central_sdi import (
    ipaccess_cfg,
    ipaccess_node_cfg,
    nodes,
    util,
)

if __name__ == '__main__':
    central_sdi.init()
    env = central_sdi.env
    logger = env.logger

    username_string = os.environ.get('username')
    if not username_string:
        logger.warning('No "username" found in environment')
        exit(1)

    password = os.environ.get('password')

    username, node_id = nodes.parse_username_string(username_string)
    if not username:
        logger.error(f'No "username" was provided: {username_string}')
        exit(1)

    if not node_id:
        logger.error('No "node_id" was provided, interactive node resolution is not supported')
        exit(1)

    sdi_header = util.log_header(node_id, username)

    # First create a Lighthouse session with the provided credentials.
    # TODO can't think of a better way of doing this, but it would be nice in future
    # if there was a way to make an API request as a user without having to create a
    # session.
    token = None
    try:
        token = util.get_lh_session_token(username, password)
    except Exception as e:  # pylint: disable=W0703
        logger.exception(f'{sdi_header} VPN client connection failed - user authentication failed')
        exit(1)

    # read the user's groups from the "secctxt" file-based cache
    # NOTE: we could replace this with something with a well-defined contract (with a contract, even)
    user_groups = None
    try:
        user_groups = list(util.get_lh_user_groups(username))
    except:  # pylint: disable=W0702
        # it does handle this as a generic error, if it was needed, on connect (this is still nasty)
        pass

    # Verify IP Access is enabled
    cfg = ipaccess_cfg.default(ipaccess_cfg.load())
    if cfg['disable']:
        logger.warning(
            f'{sdi_header} VPN client connection failed - IP Access is disabled')
        exit(1)

    # Verify node is enabled for IP Access
    nodes_cfg = ipaccess_node_cfg.nodes_cfg_load()
    node_cfg = nodes_cfg.get_node_cfg_by_id(node_id)
    if not node_cfg:
        node_cfg = ipaccess_node_cfg.NodeConfig(node_id)

    if node_cfg.disabled:
        logger.warning(f'{sdi_header} VPN client connection failed - {node_id} is not enabled for IP Access')
        exit(1)

    # Check user's authorization for this node
    authorized = False
    node = None
    try:
        authorized, node = nodes.node_authorized(node_id, token)
    except Exception as e:  # pylint: disable=W0703
        logger.exception(f'{sdi_header} VPN client connection failed - error while authenticating user')
        exit(1)
    if not authorized:
        logger.warning(f'{sdi_header} VPN client connection failed - user authentication failed')
        exit(1)

    # Verify node connection status
    if node.connection_status != 'connected':
        logger.warning(f'{sdi_header} VPN client connection failed - could not fetch node information')
        exit(1)

    # The result of this script is a verified username and node ID.
    # We want to make this accessible by subsequent OpenVPN scripts (e.g. client-connect),
    # and the easiest way is by writing to a temporary file.
    # NOTE: the file is not randomized or locked, so this is ONLY safe if OpenVPN doesn't
    # handle connections concurrently.
    env.last_auth = username, node_id, token, user_groups

    logger.info(f'{sdi_header} VPN client authenticated')
