2015-12-12 22:34:08 +00:00
|
|
|
import falcon
|
|
|
|
import ipaddress
|
|
|
|
import json
|
2016-03-21 21:42:39 +00:00
|
|
|
import logging
|
2015-12-12 22:34:08 +00:00
|
|
|
import re
|
|
|
|
import types
|
|
|
|
from datetime import date, time, datetime
|
|
|
|
from OpenSSL import crypto
|
2016-03-27 20:38:14 +00:00
|
|
|
from certidude.auth import User
|
2015-12-12 22:34:08 +00:00
|
|
|
from certidude.wrappers import Request, Certificate
|
2016-09-17 21:00:14 +00:00
|
|
|
from urlparse import urlparse
|
2016-03-21 21:42:39 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger("api")
|
|
|
|
|
|
|
|
def csrf_protection(func):
|
|
|
|
"""
|
|
|
|
Protect resource from common CSRF attacks by checking user agent and referrer
|
|
|
|
"""
|
|
|
|
def wrapped(self, req, resp, *args, **kwargs):
|
|
|
|
# Assume curl and python-requests are used intentionally
|
|
|
|
if req.user_agent.startswith("curl/") or req.user_agent.startswith("python-requests/"):
|
|
|
|
return func(self, req, resp, *args, **kwargs)
|
|
|
|
|
|
|
|
# For everything else assert referrer
|
|
|
|
referrer = req.headers.get("REFERER")
|
2016-09-17 21:00:14 +00:00
|
|
|
|
|
|
|
|
2016-03-21 21:42:39 +00:00
|
|
|
if referrer:
|
|
|
|
scheme, netloc, path, params, query, fragment = urlparse(referrer)
|
2016-09-17 21:00:14 +00:00
|
|
|
if ":" in netloc:
|
|
|
|
host, port = netloc.split(":", 1)
|
|
|
|
else:
|
|
|
|
host, port = netloc, None
|
|
|
|
if host == req.host:
|
2016-03-21 21:42:39 +00:00
|
|
|
return func(self, req, resp, *args, **kwargs)
|
|
|
|
|
|
|
|
# Kaboom!
|
2016-03-29 05:54:55 +00:00
|
|
|
logger.warning(u"Prevented clickbait from '%s' with user agent '%s'",
|
2016-03-21 21:42:39 +00:00
|
|
|
referrer or "-", req.user_agent)
|
2016-09-17 21:00:14 +00:00
|
|
|
raise falcon.HTTPForbidden("Forbidden",
|
2016-03-21 21:42:39 +00:00
|
|
|
"No suitable UA or referrer provided, cross-site scripting disabled")
|
|
|
|
return wrapped
|
|
|
|
|
2015-12-12 22:34:08 +00:00
|
|
|
|
|
|
|
def event_source(func):
|
|
|
|
def wrapped(self, req, resp, *args, **kwargs):
|
|
|
|
if req.get_header("Accept") == "text/event-stream":
|
|
|
|
resp.status = falcon.HTTP_SEE_OTHER
|
|
|
|
resp.location = req.context.get("ca").push_server + "/ev/" + req.context.get("ca").uuid
|
|
|
|
resp.body = "Redirecting to:" + resp.location
|
|
|
|
return func(self, req, resp, *args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
|
|
|
|
class MyEncoder(json.JSONEncoder):
|
|
|
|
REQUEST_ATTRIBUTES = "signable", "identity", "changed", "common_name", \
|
|
|
|
"organizational_unit", "given_name", "surname", "fqdn", "email_address", \
|
|
|
|
"key_type", "key_length", "md5sum", "sha1sum", "sha256sum", "key_usage"
|
|
|
|
|
2016-03-21 21:42:39 +00:00
|
|
|
CERTIFICATE_ATTRIBUTES = "revokable", "identity", "common_name", \
|
2015-12-12 22:34:08 +00:00
|
|
|
"organizational_unit", "given_name", "surname", "fqdn", "email_address", \
|
2016-03-21 21:42:39 +00:00
|
|
|
"key_type", "key_length", "sha256sum", "serial_number", "key_usage", \
|
|
|
|
"signed", "expires"
|
2015-12-12 22:34:08 +00:00
|
|
|
|
|
|
|
def default(self, obj):
|
|
|
|
if isinstance(obj, crypto.X509Name):
|
|
|
|
try:
|
|
|
|
return ", ".join(["%s=%s" % (k.decode("ascii"),v.decode("utf-8")) for k, v in obj.get_components()])
|
|
|
|
except UnicodeDecodeError: # Work around old buggy pyopenssl
|
|
|
|
return ", ".join(["%s=%s" % (k.decode("ascii"),v.decode("iso8859")) for k, v in obj.get_components()])
|
|
|
|
if isinstance(obj, ipaddress._IPAddressBase):
|
|
|
|
return str(obj)
|
|
|
|
if isinstance(obj, set):
|
|
|
|
return tuple(obj)
|
|
|
|
if isinstance(obj, datetime):
|
|
|
|
return obj.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
|
|
|
if isinstance(obj, date):
|
|
|
|
return obj.strftime("%Y-%m-%d")
|
|
|
|
if isinstance(obj, types.GeneratorType):
|
|
|
|
return tuple(obj)
|
|
|
|
if isinstance(obj, Request):
|
|
|
|
return dict([(key, getattr(obj, key)) for key in self.REQUEST_ATTRIBUTES \
|
|
|
|
if hasattr(obj, key) and getattr(obj, key)])
|
|
|
|
if isinstance(obj, Certificate):
|
|
|
|
return dict([(key, getattr(obj, key)) for key in self.CERTIFICATE_ATTRIBUTES \
|
|
|
|
if hasattr(obj, key) and getattr(obj, key)])
|
2016-03-27 20:38:14 +00:00
|
|
|
if isinstance(obj, User):
|
|
|
|
return dict(name=obj.name, given_name=obj.given_name,
|
|
|
|
surname=obj.surname, mail=obj.mail)
|
2015-12-12 22:34:08 +00:00
|
|
|
if hasattr(obj, "serialize"):
|
|
|
|
return obj.serialize()
|
|
|
|
return json.JSONEncoder.default(self, obj)
|
|
|
|
|
|
|
|
|
|
|
|
def serialize(func):
|
|
|
|
"""
|
|
|
|
Falcon response serialization
|
|
|
|
"""
|
|
|
|
def wrapped(instance, req, resp, **kwargs):
|
2016-03-21 21:42:39 +00:00
|
|
|
resp.set_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
|
|
resp.set_header("Pragma", "no-cache")
|
|
|
|
resp.set_header("Expires", "0")
|
2015-12-12 22:34:08 +00:00
|
|
|
r = func(instance, req, resp, **kwargs)
|
|
|
|
if resp.body is None:
|
2016-03-21 21:42:39 +00:00
|
|
|
if req.accept.startswith("application/json"):
|
2015-12-12 22:34:08 +00:00
|
|
|
resp.set_header("Content-Type", "application/json")
|
|
|
|
resp.set_header("Content-Disposition", "inline")
|
|
|
|
resp.body = json.dumps(r, cls=MyEncoder)
|
2016-03-21 21:42:39 +00:00
|
|
|
elif hasattr(r, "content_type") and req.client_accepts(r.content_type):
|
|
|
|
resp.set_header("Content-Type", r.content_type)
|
|
|
|
resp.set_header("Content-Disposition",
|
|
|
|
("attachment; filename=%s" % r.suggested_filename).encode("ascii"))
|
|
|
|
resp.body = r.dump()
|
2016-03-27 20:38:14 +00:00
|
|
|
elif hasattr(r, "content_type"):
|
2016-03-29 05:54:55 +00:00
|
|
|
logger.debug(u"Client did not accept application/json or %s, "
|
2016-03-27 20:38:14 +00:00
|
|
|
"client expected %s", r.content_type, req.accept)
|
2016-03-21 21:42:39 +00:00
|
|
|
raise falcon.HTTPUnsupportedMediaType(
|
|
|
|
"Client did not accept application/json or %s" % r.content_type)
|
2016-03-27 20:38:14 +00:00
|
|
|
else:
|
2016-03-29 05:54:55 +00:00
|
|
|
logger.debug(u"Client did not accept application/json, client expected %s", req.accept)
|
2016-03-27 20:38:14 +00:00
|
|
|
raise falcon.HTTPUnsupportedMediaType(
|
|
|
|
"Client did not accept application/json")
|
2015-12-12 22:34:08 +00:00
|
|
|
return r
|
|
|
|
return wrapped
|
|
|
|
|