56 lines
2.2 KiB
Python
56 lines
2.2 KiB
Python
from base64 import b64decode, b64encode
|
|
import functools
|
|
import socket
|
|
from typing import Union
|
|
|
|
from flask import request, Response, make_response
|
|
import gssapi
|
|
|
|
_server_name = None
|
|
|
|
|
|
def init_spnego(service_name: str, fqdn: Union[str, None] = None):
|
|
"""Set the server principal which will be used for SPNEGO."""
|
|
global _server_name
|
|
if fqdn is None:
|
|
fqdn = socket.getfqdn()
|
|
_server_name = gssapi.Name('ceod/' + fqdn)
|
|
|
|
# make sure that we're actually capable of acquiring credentials
|
|
gssapi.Credentials(usage='accept', name=_server_name)
|
|
|
|
|
|
def requires_authentication(f):
|
|
"""
|
|
Requires that all requests to f have a GSSAPI initiator token.
|
|
The initiator principal will be passed to the first argument of f
|
|
in the form user@REALM.
|
|
"""
|
|
@functools.wraps(f)
|
|
def wrapper(*args, **kwargs):
|
|
if 'authorization' not in request.headers:
|
|
return Response('Unauthorized', 401, {'WWW-Authenticate': 'Negotiate'})
|
|
header = request.headers['authorization']
|
|
client_token = b64decode(header.split()[1])
|
|
creds = gssapi.Credentials(usage='accept', name=_server_name)
|
|
ctx = gssapi.SecurityContext(creds=creds, usage='accept')
|
|
server_token = ctx.step(client_token)
|
|
|
|
# OK so we're going to cheat a bit here by assuming that Kerberos is the
|
|
# mechanism being used (which we know will be true). We know that Kerberos
|
|
# only requires one round-trip for the service handshake, so we don't need
|
|
# to store state between requests. Just to be sure, we assert that this is
|
|
# indeed the case.
|
|
# (This isn't compliant with the GSSAPI spec, but why write more code than
|
|
# necessary?)
|
|
assert ctx.complete, 'only one round trip expected'
|
|
|
|
resp = make_response(f(str(ctx.initiator_name), *args, **kwargs))
|
|
# RFC 2744, section 5.1:
|
|
# "If no token need be sent, gss_accept_sec_context will indicate this
|
|
# by setting the length field of the output_token argument to zero."
|
|
if server_token is not None:
|
|
resp.headers['WWW-Authenticate'] = 'Negotiate ' + b64encode(server_token).decode()
|
|
return resp
|
|
return wrapper
|