1
0
mirror of https://github.com/laurivosandi/certidude synced 2025-10-31 01:19:11 +00:00

Add token based auth for profiles

This commit is contained in:
2017-04-21 21:22:08 +00:00
parent 9a793088c6
commit 0344141faf
7 changed files with 148 additions and 70 deletions

View File

@@ -190,6 +190,7 @@ def certidude_app():
from .tag import TagResource, TagDetailResource
from .attrib import AttributeResource
from .bootstrap import BootstrapResource
from .token import TokenResource
app = falcon.API(middleware=NormalizeMiddleware())
app.req_options.auto_parse_form_urlencoded = True
@@ -202,7 +203,9 @@ def certidude_app():
app.add_route("/api/request/{cn}/", RequestDetailResource())
app.add_route("/api/request/", RequestListResource())
app.add_route("/api/", SessionResource())
app.add_route("/api/bootstrap/", BootstrapResource())
if config.BUNDLE_FORMAT and config.USER_ENROLLMENT_ALLOWED:
app.add_route("/api/token/", TokenResource())
# Extended attributes for scripting etc.
app.add_route("/api/signed/{cn}/attr/", AttributeResource())
@@ -217,8 +220,7 @@ def certidude_app():
# Gateways can submit leases via this API call
app.add_route("/api/lease/", LeaseResource())
# Optional user enrollment API call
if config.USER_ENROLLMENT_ALLOWED:
app.add_route("/api/bundle/", BundleResource())
# Bootstrap resource
app.add_route("/api/bootstrap/", BootstrapResource())
return app

View File

@@ -1,44 +0,0 @@
import logging
import hashlib
from certidude import config, authority
from certidude.auth import login_required
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 BundleResource(object):
@login_required
def on_get(self, req, resp):
common_name = req.context["user"].name
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)

94
certidude/api/token.py Normal file
View File

@@ -0,0 +1,94 @@
import click
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__)
chars = string.ascii_letters + string.digits + '!@#$%^&*()'
SECRET = ''.join(random.choice(chars) for i in range(32))
click.echo("Token secret: %s" % SECRET)
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(SECRET)
csum.update(username)
csum.update(str(timestamp))
if csum.hexdigest() != req.get_param("c", required=True):
raise # TODO
if now < timestamp:
raise # Token not valid yet
if now > timestamp + config.TOKEN_LIFETIME:
raise # token expired
# 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(SECRET)
csum.update(username)
csum.update(str(timestamp))
args = "u=%s&t=%d&c=%s" % (username, timestamp, csum.hexdigest())
token_created = datetime.utcfromtimestamp(timestamp)
token_expires = datetime.utcfromtimestamp(timestamp + config.TOKEN_LIFETIME)
context = globals()
context.update(locals())
mailer.send("token.md", to=user, **context)