import StringIO
import getpass
import argparse
import signal
import time
import os
import sys
from wsgiref import validate

import configobj
import toopher
import validate

from common import *


TIMEOUT_PAIRING = 30

DEFAULT_USER_CONFIG_FILE = StringIO.StringIO("""\
# This is a user-specific Toopher configuration file.  See toopher_config (5)
# for more information.  This file provides a way for users to customize the
# behavior of their authentication flows which are configured to use the Toopher
# PAM module.

# Pairings: This section lists known pairing identifiers issued by the Toopher API
# server for hosts to use for pairings identification when making an authentication
# request. The values here may be populated using the 'toopher-pair' utility.
[Pairings]
""")


def sigint_handler(sig, frame):
    sys.exit("Cancelled by user (Ctrl-C)")

signal.signal(signal.SIGINT, sigint_handler)


def main():
    argparser = argparse.ArgumentParser(description="Pair an account with the Toopher app")
    argparser.add_argument('--user', '-u', default='',
                           help="the user to pair (defaults to the current user, specifying a user is usually only useful for the superuser)")
    argparser.add_argument('--shared', '-s', action="store_true",
                           help="create a shared pairing instead of a host-specific pairing")
    argparser.add_argument('--phrase', '-p', metavar="PAIRING_PHRASE",
                           help="a pairing phrase generated by the Toopher app (will prompt if not supplied)")
    args = argparser.parse_args()

    # Retrieve API credentials from system configuration file
    try:
        system_config = get_system_config()
        api = get_api_object(system_config)
    except Exception:
        sys.exit("Could not read the Toopher system config, please request that your administrator configure the system for Toopher.")

    username = args.user
    if not username:
        username = getpass.getuser()

    user_config_filename = get_user_config_filename(username)
    try:
        user_config = get_user_config(username)
    except IOError:  # Does not exist or cannot read
        if os.path.exists(user_config_filename):  # Exists but can't be read
            sys.exit("Could not read user's Toopher config file ('%s')" % user_config_filename)
        else:  # Does not exist, make sure we can write it if we try
            if not os.access(os.path.dirname(user_config_filename), os.W_OK):
                sys.exit("Missing write permissions for the user's Toopher config file ('%s')" % user_config_filename)
    
            user_config = configobj.ConfigObj(DEFAULT_USER_CONFIG_FILE)
            user_config.filename = user_config_filename
    except configobj.ConfigObjError, e:  # Could not parse
        sys.exit("Malformed configuration file ('%s'): %s" % (user_config_filename, e))
    except validate.ValidateError, e:  # Did not validate against spec
        sys.exit("Problem validating user configuration file ('%s'):\n"
                 "%s\n"
                 "Please fix or remove user configuration file and try again."
                 % (user_config_filename, e))
    else:  # Exists, readable, parseable, and valid - make sure we can write it if we try
        if not os.access(user_config_filename, os.W_OK):
            sys.exit("Missing write permissions for the user's Toopher config file ('%s')" % user_config_filename)

    phrase = args.phrase
    while True:
        if not phrase:
            phrase = raw_input("Enter a pairing phrase (generated by the Toopher app): ")
            if not phrase:
                print 'Invalid pairing phrase, please try again.'
            else:
                break

    full_username = get_full_username(username, args.shared)

    try:
        sys.stdout.write('Contacting server to perform pairing... ')
        sys.stdout.flush()
        pairing = api.pair(phrase, full_username)
        print 'done.'
    except Exception as error:
        print 'error.'
        sys.exit('This user could not be paired due to an error: %s' % error)

    print 'Your Toopher app should now ask you to approve this pairing request.  Please respond to continue.'

    sys.stdout.write('Checking status..')
    sys.stdout.flush()
    start_time = time.time()
    while time.time() - start_time < TIMEOUT:
        sys.stdout.write(".")
        sys.stdout.flush()
        pairing_status = api.get_pairing_status(pairing.id)
        if not pairing_status.pending:
            if pairing_status.enabled:
                print ' pairing approved.'
                break
            else:
                print ' pairing denied.'
                sys.exit('This pairing request was denied by the Toopher app user.')
        time.sleep(1)

    pairing_key = USER_CONFIG_PAIRINGS_KEY_SHARED_PAIRING_ID if args.shared else HOSTNAME
    user_config[USER_CONFIG_PAIRINGS_SECTION][pairing_key] = pairing.id
    user_config.write()

    print 'Pairing successful - PAM services configured to use the Toopher module will now use this pairing to authenticate.'


if __name__ == '__main__':
    try:
        main()
    except Exception, e:
        sys.exit("An unexpected error was encountered. Please contact support@toopher.com for resolution: (Error: %s)"
                 % e)
Add toopher-pair utility