#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
#    LinOTP - the open source solution for two factor authentication
#    Copyright (C) 2010 - 2019 KeyIdentity GmbH
#
#    This file is part of LinOTP server.
#
#    This program is free software: you can redistribute it and/or
#    modify it under the terms of the GNU Affero General Public
#    License, version 3, as published by the Free Software Foundation.
#
#    This program 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 Affero General Public License for more details.
#
#    You should have received a copy of the
#               GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
#    E-mail: linotp@keyidentity.com
#    Contact: www.linotp.org
#    Support: www.keyidentity.com
#

"""
Tool to help mass enroll sms tokens
"""

VERSION = '0.2'

import getpass
import json
import sys

try:
    import requests
    from requests.auth import HTTPDigestAuth
except ImportError:
    print """
************************************ Error *************************************
The 'requests' library needs to be installed to run this script.  On
Debian/Ubuntu you can install it with 'apt-get install python-requests'.

If this package is not available on you machine (e.g. because you are running
on a LSE Smart Virtual Appliance version 1.x) then consider downloading this
script to your local machine and running it there. It does not need to run on
the same machine where LinOTP is installed.
*******************************************************************************\
*"""
    sys.exit(1)

from getopt import getopt, GetoptError

import logging
LOG = logging.getLogger(__name__)

class Request(object):
    """
    Request class - to preserve settings for multiple requests to LinOTP
    """

    def __init__(self, url, admin=None, password=None, ssl_verify=True):
        """
        :param url: set up the linOTP URL - required
        :param admin: admin user - required for base authentication
        :param password: password of the admin user
        :param ssl_verify: switch to disable / enable the ssl cert verification
        """
        self.request_options = {}
        self.base_url = url
        self.session = None
        self.session_cookie = None

        if ssl_verify is False:
            self.request_options['verify'] = False

        self.admin = None
        self.password = None

        if admin and password:
            self.admin = admin
            self.password = password

        # trim trailing '/' to prevent error
        if self.base_url[-1] == '/':
            self.base_url = self.base_url[:-1]

    def get_session(self):
        """
        get the admin session context

        - preserver the session context in self.session for further requests

        :return session: return the session value
        """
        url = "/admin/getsession"
        pparams = {}

        if self.session:
            return self.session

        if self.admin:
            auth = HTTPDigestAuth(self.admin, self.password)
            pparams['auth'] = auth
        if self.request_options:
            pparams.update(self.request_options)

        response = self.request(url, pparams=pparams)

        cookies = response.cookies
        for cookie in cookies:
            if cookie.name == 'admin_session':
                self.session = cookie.value
                self.session_cookie = cookie
                break

        return self.session


    def load_json_response(self, response):
        try:
            resp = json.loads(response.text)
        except Exception as exx:
            sys.stderr.write("Exeption: %r\n%r" % (exx, response.text))
            raise exx
        return resp


    def get_users(self, user_filter=None):
        """
        get a list of users according to the filter

        the filter is a dict, which could contain as well the
        realm of the users

        :param filter: dict, to restrict the user search
        :return: array of user description
        """
        url = '/admin/userlist'
        if user_filter == None:
            user_filter = {'username':'*'}
        response = self.request(url, params=user_filter)
        resp = self.load_json_response(response)

        # check for valid return status
        status = resp.get('result', {}).get('status', False)
        if status is False:
            raise Exception('Failed to %s: %r' % (url, response))

        result = resp.get('result', {}).get('value', [])
        return result

    def enroll_token(self, typ='hmac', params=None):
        """
        enroll a token

        :param params: dict with description of token specifica
        :return: serial number on succcess, else raise exception
        """
        url = '/admin/init'

        if params == None:
            params = {}

        params['type'] = typ

        response = self.request(url, params)
        resp = self.load_json_response(response)

        # check for valid return status
        result = resp.get('result', {}).get('value', False)
        if result is False:
            raise Exception('Failed to %s: %r' % (url, response))
        serial = resp.get('detail', {}).get('serial', '')
        return serial


    def request(self, url, params=None, pparams=None):
        """
        base method to run requests

        :param url: the sub url path - will be appended to the base url
        :param params: request parameters for linotp

        :return: the response object
        """

        cookies = None

        if params == None:
            params = {}


        if 'admin/getsession' not in url:
            session = params.get('session', self.get_session())
            params['session'] = session
            cookies = dict(admin_session=session)

        # request command options
        if pparams is None:
            pparams = {}

        if self.admin:
            auth = HTTPDigestAuth(self.admin, self.password)
            pparams['auth'] = auth

        if self.request_options:
            pparams.update(self.request_options)

        try:
            response = requests.post(self.base_url + url, params,
                                 cookies=cookies, **pparams)

        # All exceptions that Requests explicitly raises inherit from requests.exceptions.RequestException.

        # In the event of a network problem (e.g. DNS failure, refused connection, etc),
        # Requests will raise a ConnectionError exception.
        except requests.exceptions.ConnectionError as exx:
            sys.stderr.write("Connection Error: %r" % exx)
            raise exx

        # In the rare event of an invalid HTTP response, Requests will
        # raise an HTTPError exception.
        except requests.exceptions.HTTPError as exx:
            sys.stderr.write("HTTPError %r" % exx)
            raise exx

        # If a request times out, a Timeout exception is raised.
        except requests.exceptions.Timeout as exx:
            sys.stderr.write("Request timed out %r" % exx)
            raise exx

        # If a request exceeds the configured number of maximum redirections,
        # a TooManyRedirects exception is raised.
        except requests.exceptions.TooManyRedirects as exx:
            sys.stderr.write("Too many redirects %r" % exx)
            raise exx


        return response


def usage():
    usage = """
    linotp-enroll-smstoken v.(%s)

    Helper tool to massenroll sms tokens - for all specified users with
    a specified mobile number

    Parameters are:

    --linotp:           the linotp url (required)
    --admin:            the admin user - will prompt for the admin password
    --username:         the username filter (optional)
    --realm:            the realm filter for the user selection (optional)
    --no-ssl-verify     supress the verification of the ssl certificate

    """ % VERSION
    print usage

def main():

    linotp_url = None

    user_filter = {}
    init_pparams = {}

    try:
        opts, args = getopt(sys.argv[1:], "hl:a:u:r:n",
                ["help", "no-ssl-verify", "linotp=", "admin=", "username=", "realm="])

    except GetoptError:
        print "There is an error in your parameter syntax:"
        usage()
        sys.exit(1)


    for opt, arg in opts:
        if opt in ('--help'):
            usage()
            sys.exit(0)

        elif opt in ('--linotp'):
            linotp_url = arg

        elif opt in ('--admin'):
            admin = arg
            adminpw = getpass.getpass(prompt="Please enter password for '%s':"
                                      % admin)
            init_pparams['admin'] = admin
            init_pparams['password'] = adminpw

        elif opt in ['-n', '--no-ssl-verify']:
            init_pparams['ssl_verify'] = False

        elif opt in ('--username'):
            user_filter['username'] = arg

        elif opt in ('--realm'):
            user_filter['realm'] = arg

    if not linotp_url:
        usage()
        sys.stderr.write("No linotp url specified!\n")
        sys.exit(-1)

# main  processing starts here

    r = Request(linotp_url, **init_pparams)

    users = r.get_users(user_filter)
    for user in users:

        enroll_params = {}
        enroll_params['genkey'] = '1'
        enroll_params['user'] = u"%s" % user.get('username')
        enroll_params['resConf'] = user.get('useridresolver').split('.')[-1]
        enroll_params['phone'] = user.get('mobile', None)

        if not enroll_params['phone']:
            sys.stderr.write("SMS Token enrolled for user: %r: failed"
                             " - missing mobile number!\n"
                             % (enroll_params['user']))
            continue

        resp = r.enroll_token(typ='sms', params=enroll_params)
        if resp is True:
            resp = "success"
        print ("SMS Token enrolled for user: %r: %r\n"
               % (enroll_params['user'], resp))

if __name__ == '__main__':
    main()

