
import click
import falcon
import ipaddress
import kerberos
import logging
import os
import re
import socket

logger = logging.getLogger("api")

# Vanilla Kerberos provides only username.
# AD also embeds PAC (Privilege Attribute Certificate), which
# is supposed to be sent via HTTP headers and it contains
# the groups user is part of.
# Even then we would have to manually look up the e-mail
# address eg via LDAP, hence to keep things simple
# we simply use Kerberos to authenticate.

FQDN = socket.getaddrinfo(socket.gethostname(), 0, flags=socket.AI_CANONNAME)[0][3]

if not os.getenv("KRB5_KTNAME"):
    click.echo("Kerberos keytab not specified, set environment variable 'KRB5_KTNAME'", err=True)
    exit(250)

try:
    principal = kerberos.getServerPrincipalDetails("HTTP", FQDN)
except kerberos.KrbError as exc:
    click.echo("Failed to initialize Kerberos, reason: %s" % exc, err=True)
    exit(249)
else:
    click.echo("Kerberos enabled, service principal is HTTP/%s" % FQDN)

def login_required(func):
    def authenticate(resource, req, resp, *args, **kwargs):
        authorization = req.get_header("Authorization")

        if not authorization:
            resp.append_header("WWW-Authenticate", "Negotiate")
            logger.debug("No Kerberos ticket offered while attempting to access %s from %s", req.env["PATH_INFO"], req.env["REMOTE_ADDR"])
            raise falcon.HTTPUnauthorized("Unauthorized", "No Kerberos ticket offered, are you sure you've logged in with domain user account?")

        token = ''.join(authorization.split()[1:])

        try:
            result, context = kerberos.authGSSServerInit("HTTP@" + FQDN)
        except kerberos.GSSError as ex:
            # TODO: logger.error
            raise falcon.HTTPForbidden("Forbidden", "Authentication System Failure: %s(%s)" % (ex.args[0][0], ex.args[1][0],))

        try:
            result = kerberos.authGSSServerStep(context, token)
        except kerberos.GSSError as ex:
            s = str(dir(ex))
            kerberos.authGSSServerClean(context)
            # TODO: logger.error
            raise falcon.HTTPForbidden("Forbidden", "Bad credentials: %s (%s)" % (ex.args[0][0], ex.args[1][0]))
        except kerberos.KrbError as ex:
            kerberos.authGSSServerClean(context)
            # TODO: logger.error
            raise falcon.HTTPForbidden("Forbidden", "Bad credentials: %s" % (ex.args[0],))

        user = kerberos.authGSSServerUserName(context)
        req.context["user"], req.context["user_realm"] = user.split("@")

        try:
            # BUGBUG: https://github.com/02strich/pykerberos/issues/6
            #kerberos.authGSSServerClean(context)
            pass
        except kerberos.GSSError as ex:
            # TODO: logger.error
            raise error.LoginFailed('Authentication System Failure %s(%s)' % (ex.args[0][0], ex.args[1][0],))

        if result == kerberos.AUTH_GSS_COMPLETE:
            logger.debug("Succesfully authenticated user %s for %s from %s", user, req.env["PATH_INFO"], req.env["REMOTE_ADDR"])
            return func(resource, req, resp, *args, **kwargs)
        elif result == kerberos.AUTH_GSS_CONTINUE:
            # TODO: logger.error
            raise falcon.HTTPUnauthorized("Unauthorized", "Tried GSSAPI")
        else:
            # TODO: logger.error
            raise falcon.HTTPForbidden("Forbidden", "Tried GSSAPI")

    return authenticate


def authorize_admin(func):
    def wrapped(self, req, resp, *args, **kwargs):
        from certidude import config
        # Parse remote IPv4/IPv6 address
        remote_addr = ipaddress.ip_network(req.env["REMOTE_ADDR"])

        # Check for administration subnet whitelist
        print("Comparing:", config.ADMIN_SUBNETS, "To:", remote_addr)
        for subnet in config.ADMIN_SUBNETS:
            if subnet.overlaps(remote_addr):
                break
        else:
            logger.info("Rejected access to administrative call %s by %s from %s, source address not whitelisted", req.env["PATH_INFO"], user, req.env["REMOTE_ADDR"])
            raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % remote_addr)

        # Check for username whitelist
        if req.context.get("user") not in config.ADMIN_USERS:
            logger.info("Rejected access to administrative call %s by %s from %s, user not whitelisted", req.env["PATH_INFO"], user, req.env["REMOTE_ADDR"])
            raise falcon.HTTPForbidden("Forbidden", "User %s not whitelisted" % req.context.get("user"))

        # Retain username, TODO: Better abstraction with username, e-mail, sn, gn?

        return func(self, req, resp, *args, **kwargs)
    return wrapped

@@ -2991,20 +2991,35 @@
 om %25s%22, 
+req.context%5B%22
 user
+%22%5D
 , req.en
@@ -4020,36 +4020,51 @@
 v%5B%22PATH_INFO%22%5D, 
+req.context%5B%22
 user
+%22%5D
 , req.env%5B%22REMOT
@@ -4405,20 +4405,35 @@
 INFO%22%5D, 
+req.context%5B%22
 user
+%22%5D
 , req.en

Fix lookup of user context variable