#! /usr/bin/python2
#
# Copyright 2010-2017 University of Chicago
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import base64
import getpass
import getopt
import httplib
import json
import os
import random
import socket
import urllib
from subprocess import Popen, PIPE
import sys

def public_name():
    """
    Try to guess the public host name of this machine. If this is
    on a machine which is able to access ec2 metadata, it will use
    that; otherwise socket.getfqdn()
    """
    url = 'http://169.254.169.254/latest/meta-data/public-hostname'
    value = None
    try:
        socket.setdefaulttimeout(3.0)
        value = urllib.urlopen(url).read()
    except IOError:
        pass

    if value is not None and "404 - Not Found" in value:
        value = None

    if value is None:
        value = socket.getfqdn()
    return value

"""
Register Globus as an OAuth client with this MyProxy OAuth Delegation
service. The registration consists of two steps:
1. Get an access token from Globus.
2. Trigger Globus client registration with this service.
"""
def configure(username=None, pwstdin=False, oauth_server=None, myproxy_server=None, nexus_server=None):

    # Delay this until after seteuid() so that the database file is owned by 
    # the specified user
    from myproxyoauth.database import db_session, Admin, Client
    if username is None:
        default_user = getpass.getuser()
        print "Globus Username [%s]: " % (default_user),
        username = sys.stdin.readline().strip()
        if username == '':
            username = default_user
    if pwstdin:
        print "Globus Password: ",
        password = sys.stdin.readline().strip()
    else:
        password = getpass.getpass("Globus Password: ")

    default_hostname = public_name()

    if oauth_server is None:
        print "OAuth Server [%s]: " % (default_hostname),
        oauth_server = sys.stdin.readline().strip()
        if oauth_server == '':
            oauth_server = default_hostname

    if myproxy_server is None:
        print "MyProxy Server [%s]: " % (default_hostname),
        myproxy_server = sys.stdin.readline().strip()
        if myproxy_server == '':
            myproxy_server = default_hostname

    if nexus_server is None:
        nexus_server = "nexus.api.globusonline.org"

    admins = db_session.get_admin()
    admin = None
    if len(admins) > 0:
        admin = admins[0]

    if admin is not None:
        if admin.username != username:
            message = 'You are not an admin of the MyProxy Delegation Service'
            print >>sys.stderr, message
            sys.exit(1)

    try:
        access_token = get_access_token(username, password, nexus_server)
    except Exception, e:
        message='Could not get access token. %s' % str(e)
        print >>sys.stderr, message
        sys.exit(1)

    client_id = 'myproxy:oa4mp,2012:/client/' \
        + ''.join([random.choice('0123456789abcdef') for i in range(32)])
    try:
        (home_url, gateway_name, oauth_consumer_id, public_key) = register_go(
                nexus_server, access_token, client_id, oauth_server)
    except Exception, e:
        message = str(e)
        print >>sys.stderr, message
        sys.exit(1)

    print "Registered: gateway_name: %s, home_url: %s," \
          " oauth_consumer_id: %s" \
          % (gateway_name, home_url, oauth_consumer_id)

    if admin is None:
        db_session.add_admin(Admin(username))

    db_session.delete_clients(
        [Client(oauth_consumer_key=oauth_consumer_id)])
    client = Client()
    client.oauth_consumer_key=oauth_consumer_id
    client.oauth_client_pubkey=public_key
    client.name=gateway_name
    client.home_url=home_url
    client.myproxy_server=myproxy_server
    db_session.add_client(client)

    db_session.commit()
    selinux_permissions()

def get_access_token(username, password, server):
    """
    Get an access token from Globus Nexus.
    Returns: an access token
    """

    basic_auth = base64.b64encode('%s:%s' % (username, password))
    headers = { 'Content-type': 'app/json; charset=UTF-8',
            'Hostname': server,
            'Accept': 'app/json; charset=UTF-8',
            'Authorization': 'Basic %s' % basic_auth }
    c = httplib.HTTPSConnection(server, 443)
    c.request('GET', '/goauth/token?grant_type=client_credentials',
            headers=headers)
    response = c.getresponse()
    json_reader = None
    if hasattr(json, 'loads'):
        json_reader = json.loads
    elif hasattr(json, 'JsonReader'):
        json_reader_obj = json.JsonReader()
        json_reader = json_reader_obj.read

    if response.status == 403:
        try :
            message = json_reader(response.read()).get('message')
        except Exception, e:
            message = str(e)
        raise Exception('403 Error: %s' % message)
    elif response.status > 299 or response.status < 200:
        raise Exception('%d Error: %s' % (response.status, response.reason))
    data = json_reader(response.read())
    token = data.get('access_token')
    if token is None:
        raise Exception('No access token in response')
    return token


def register_go(server, access_token, client_id, myproxy_server):
    """
    Trigger the nexus_server to register with the oauth_server.
    Returns: home_url, gateway_name, public_key, oauth_consumer_id
    """

    headers = { 'Content-type': 'app/json',
            'X-Globus-Goauthtoken': access_token}
    body = '{"oauth_consumer_id": "%s", "oauth_server": "%s"}' \
            % (client_id, myproxy_server)
    c = httplib.HTTPSConnection(server, 443)
    c.request('POST', '/identity_providers/oauth_registration',
            body=body, headers=headers)
    response = c.getresponse()
    json_reader = None
    if hasattr(json, 'loads'):
        json_reader = json.loads
    elif hasattr(json, 'JsonReader'):
        json_reader_obj = json.JsonReader()
        json_reader = json_reader_obj.read
    if response.status == 403:
        try:
            message = json_reader(response.read()).get('message')
        except Exception, e:
            message = str(e)
        raise Exception('403 Error: %s' % message)
    elif response.status > 299 or response.status < 200:
        raise Exception('%d Error: %s' % (response.status, response.reason))
    data = json_reader(response.read())
    home_url = data.get('home_url')
    gateway_name = data.get('gateway_name')
    oauth_consumer_id = data.get('oauth_consumer_id')
    public_key = data.get('public_key')
    return (home_url, gateway_name, oauth_consumer_id, public_key)


def usage(outstream=sys.stdout):
    print >>outstream, """myproxy-oauth-setup [-h]
myproxy-oauth-setup {-s | --stdin} {-u USER | --user=USER}
                    {-o SERVER| --oauth-server=SERVER}
                    {-m SERVER| --myproxy-server=SERVER}
                    {-n SERVER| --nexus-api-server=SERVER}"""

def selinux_permissions():
    save_uid = os.geteuid()
    if os.getuid() == 0:
        os.seteuid(0)

    semanage = which("semanage")
    chcon = which("chcon")
    restorecon = which("restorecon")
    setsebool = which("setsebool")
    args = None

    # Allow httpd to write to the myproxy-oauth sqlite3 database
    if semanage is not None:
        args = [semanage, 'fcontext', '-a', '-t', 'httpd_sys_rw_content_t',
                '/var/lib/myproxy-oauth/myproxy-oauth.db']
    elif chcon is not None:
        args = [chcon, '-t', 'httpd_sys_rw_content_t',
                '/var/lib/myproxy-oauth/myproxy-oauth.db']

    if args is not None:
        context_proc = Popen(args, stdin=None, stdout=PIPE, stderr=PIPE)
        (out, err) = context_proc.communicate()
        if out is not None:
            print out
        if err is not None:
            print >>sys.stderr, err

    args = None

    # Allow httpd to connect to myproxy service
    if semanage is not None:
        args = [semanage, 'boolean', '-m', '-1', 'httpd_can_network_connect']
    elif setsebool is not None:
        args = [setsebool, '-P', 'httpd_can_network_connect', 'on']

    if args is not None:
        setbool_proc = Popen(args, stdin=None, stdout=PIPE, stderr=PIPE)
        (out, err) = setbool_proc.communicate()
        if out is not None:
            print out
        if err is not None:
            print >>sys.stderr, err

    args = None
    # Allow httpd to run wsgi
    if semanage is not None:
        args = [semanage, 'boolean', '-m', '-1', 'httpd_execmem']
    elif setsebool is not None:
        args = [setsebool, '-P', 'httpd_execmem', 'on']
    if args is not None:
        setbool_proc = Popen(args, stdin=None, stdout=PIPE, stderr=PIPE)
        (out, err) = setbool_proc.communicate()
        if out is not None:
            print out
        if err is not None:
            print >>sys.stderr, err

    if semanage is not None and restorecon is not None:
        args = [restorecon, '/var/lib/myproxy-oauth/myproxy-oauth.db']
        restorecon_proc = Popen(args, stdin=None, stdout=PIPE, stderr=PIPE)
        (out, err) = restorecon_proc.communicate()
        if out is not None:
            print out
        if err is not None:
            print >>sys.stderr, err

    if os.geteuid() == 0 and save_uid != 0:
        os.seteuid(save_uid)
def which(name):
    exe_path = None
    path_to_check = os.environ['PATH'].split(os.pathsep)
    for sbin in  ["/usr/sbin", "/sbin"]:
        if sbin not in path_to_check:
            path_to_check.append(sbin)

    for path in path_to_check:
        exe_path = os.path.join(path, name)
        if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
            return exe_path
    return None

if __name__ == '__main__':
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hsu:o:m:n:i:",
                ["help", "stdin", "user=", "oauth-server=", "myproxy-server=",
                 "nexus-api-server=", "id="])
    except getopt.GetoptError, e:
        print >>sys.stderr, "Invalid option " + e.opt
        usage(outstream=sys.stderr)
        sys.exit(1)

    pwstdin = False
    user = None
    oauth_server = None
    myproxy_server = None
    nexus_server = None
    userid = None

    for (o, val) in opts:
        if o in ['-h', '--help']:
            usage()
            sys.exit(0)
        elif o in ['-s', '--stdin']:
            pwstdin = True
        elif o in ['-u',  '--user']:
            user = val
        elif o in ['-o', '--oauth-server']:
            oauth_server = val
        elif o in ['-m', '--myproxy-server']:
            myproxy_server = val
        elif o in ['-n', '--nexus-api-server']:
            nexus_server = val
        elif o in ['-i', '--id']:
            userid = int(val)
        else:
            print >>sys.stderr, "Unknown option %s" %(o)
            sys.exit(1)

    if userid is not None:
        os.seteuid(userid)

    configure(username=user, pwstdin=pwstdin, oauth_server=oauth_server,
                myproxy_server=myproxy_server, nexus_server=nexus_server)
# vim: syntax=python: nospell:
