certidude/certidude/api/token.py

100 lines
3.8 KiB
Python
Raw Normal View History

2017-04-21 21:22:08 +00:00
import click
import falcon
2017-04-21 21:22:08 +00:00
import logging
import hashlib
import random
import string
from datetime import datetime
from time import time
from certidude import mailer
from certidude.user import User
from certidude import config, authority
from certidude.auth import login_required, authorize_admin
logger = logging.getLogger(__name__)
KEYWORDS = (
(u"Android", u"android"),
(u"iPhone", u"iphone"),
(u"iPad", u"ipad"),
(u"Ubuntu", u"ubuntu"),
(u"Fedora", u"fedora"),
(u"Linux", u"linux"),
(u"Macintosh", u"mac"),
)
class TokenResource(object):
def on_get(self, req, resp):
# Consume token
now = time()
timestamp = req.get_param_as_int("t", required=True)
username = req.get_param("u", required=True)
user = User.objects.get(username)
csum = hashlib.sha256()
csum.update(config.TOKEN_SECRET)
2017-04-21 21:22:08 +00:00
csum.update(username)
csum.update(str(timestamp))
2017-04-24 17:33:55 +00:00
margin = 300 # Tolerate 5 minute clock skew as Kerberos does
2017-04-21 21:22:08 +00:00
if csum.hexdigest() != req.get_param("c", required=True):
2017-04-26 06:13:41 +00:00
raise falcon.HTTPForbidden("Forbidden", "Invalid token supplied, did you copy-paste link correctly?")
2017-04-24 17:33:55 +00:00
if now < timestamp - margin:
2017-04-26 06:13:41 +00:00
raise falcon.HTTPForbidden("Forbidden", "Token not valid yet, are you sure server clock is correct?")
2017-04-24 17:33:55 +00:00
if now > timestamp + margin + config.TOKEN_LIFETIME:
2017-04-26 06:13:41 +00:00
raise falcon.HTTPForbidden("Forbidden", "Token expired")
2017-04-21 21:22:08 +00:00
# At this point consider token to be legitimate
common_name = username
if config.USER_MULTIPLE_CERTIFICATES:
for key, value in KEYWORDS:
if key in req.user_agent:
device_identifier = value
break
else:
device_identifier = u"unknown-device"
common_name = u"%s@%s-%s" % (common_name, device_identifier, \
hashlib.sha256(req.user_agent).hexdigest()[:8])
logger.info(u"Signing bundle %s for %s", common_name, req.context.get("user"))
if config.BUNDLE_FORMAT == "p12":
resp.set_header("Content-Type", "application/x-pkcs12")
resp.set_header("Content-Disposition", "attachment; filename=%s.p12" % common_name.encode("ascii"))
resp.body, cert = authority.generate_pkcs12_bundle(common_name,
owner=req.context.get("user"))
elif config.BUNDLE_FORMAT == "ovpn":
resp.set_header("Content-Type", "application/x-openvpn")
resp.set_header("Content-Disposition", "attachment; filename=%s.ovpn" % common_name.encode("ascii"))
resp.body, cert = authority.generate_ovpn_bundle(common_name,
owner=req.context.get("user"))
else:
raise ValueError("Unknown bundle format %s" % config.BUNDLE_FORMAT)
@login_required
@authorize_admin
def on_post(self, req, resp):
# Generate token
issuer = req.context.get("user")
username = req.get_param("user", required=True)
user = User.objects.get(username)
timestamp = int(time())
csum = hashlib.sha256()
csum.update(config.TOKEN_SECRET)
2017-04-21 21:22:08 +00:00
csum.update(username)
csum.update(str(timestamp))
args = "u=%s&t=%d&c=%s" % (username, timestamp, csum.hexdigest())
# Token lifetime in local time, to select timezone: dpkg-reconfigure tzdata
token_created = datetime.fromtimestamp(timestamp)
token_expires = datetime.fromtimestamp(timestamp + config.TOKEN_LIFETIME)
2017-04-26 06:13:41 +00:00
try:
with open("/etc/timezone") as fh:
token_timezone = fh.read().strip()
except EnvironmentError:
token_timezone = None
2017-04-21 21:22:08 +00:00
context = globals()
context.update(locals())
mailer.send("token.md", to=user, **context)
2017-04-26 06:13:41 +00:00
resp.body = args