~malcscott/+junk/nebula

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import Cookie
from datetime import datetime

import raven


class RavenAuth(object):

	validity_past = 86400
	validity_future = 300

	authenticated = False

	def __init__(self, url, desc):
		self.url = url
		self.desc = desc

	def authorise(self, raven_response):
		"""Check that the (valid, authenticated) user is authorized
		(override this in a subclass)"""
		return True

	def _check_raven_response(self, r, validity_past, validity_future):
		issue_delta = (datetime.utcnow() - r.issue).total_seconds()
		if not r.success:
			return (False, "Unsuccessful authentication")
		elif not r.url[:min(len(self.url),len(r.url))] == self.url:
			return (False, "Authenticated for a different URL (%s)" % r.url)
		elif issue_delta > validity_past:
			return (False, "Authentication ticket expired (age: %d)" % issue_delta)
		elif (-issue_delta) > validity_future:
			return (False, "Authentication ticket travelled through a wormhole (age: %d)" % issue_delta)
		# If we ever set iact, aauth:
		#elif (r.check_iact_aauth(None, None)):
		#	return (False, "Invalid authentication parameters (auth=%s; sso=%s)" % (r.auth, r.sso))
		elif not self.authorise(r):
			return (False, "User is unauthorised")
		else:
			return (True, r.principal)

	def _auth_init(self, request, env, interaction_required_class):
		"""If authenticated: return (True, username)
		If authentication failed: return (False, error_message)"""

		cookies = Cookie.SimpleCookie()
		if 'HTTP_COOKIE' in env:
			cookies.load(env['HTTP_COOKIE'])
		if 'WLS-Response' in request:
			response_str = request.getfirst('WLS-Response')
			r = raven.Response(response_str)
			success, data = self._check_raven_response(r, 60, 15)
			if success:
				print >>env['wsgi.errors'], "Authenticated via Raven: %s" % (r.principal)
				cookies['NebulAuthRaven'] = response_str
				cookies['NebulAuthRaven']['httponly'] = True
				if self.url[:6] == 'https:':
					cookies['NebulAuthRaven']['secure'] = True
				cookies['NebulAuthRaven']['max-age'] = self.validity_past
				cookie_list = [('Set-Cookie', cookies['NebulAuthRaven'].OutputString())]
				# use the exception to make a (spurious) extra redirect which
				# sets the cookies only, for convenience
				raise interaction_required_class(self.url, headers=cookie_list)
			else:
				print >>env['wsgi.errors'], "Rejected WLS response for %s: %s" % (r.principal, data)
				if 'NebulAuthRaven' in cookies:
					cookies['NebulAuthRaven'] = ''
				return (False, data)
		if 'NebulAuthRaven' in cookies and cookies['NebulAuthRaven'] != '':
			r = raven.Response(cookies['NebulAuthRaven'].value)
			success, data = self._check_raven_response(r, self.validity_past, self.validity_future)
			if success:
				return (True, data)
			else:
				print >>env['wsgi.errors'], "Rejected existing ticket for %s: %s" % (r.principal, data)
		raise interaction_required_class(self.url, lambda url: str(raven.Request(url=url, desc=self.desc)))

	def auth_init(self, request, env, interaction_required_class):
		self.authenticated, authdata = self._auth_init(request, env, interaction_required_class)
		if self.authenticated:
			self.user = authdata
		else:
			self.user = None
		return self.authenticated, authdata

	def vm_allowed(self, vm):
		if vm["is_control_domain"]:
			return False
		if vm["is_a_template"]:
			return False

		if ("xvp: %s" % self.user) in vm["tags"]:
			return True
		if vm["name_label"] == self.user:
			return True
		if vm["name_label"][:len(self.user)+1] == ("%s " % self.user):
			return True

		return False

	def op_allowed(self, op):
		# TODO
		return False

	def sanitise_vm_name(self, name):
		"Assumes input name is sane and just needs the username adding"
		return "%s %s" % (self.user, name)

	def set_vm_properties(self, xenapi, vm):
		xenapi.VM.set_tags(vm, ["xvp: %s" % self.user])


class SRCFRavenAuth(RavenAuth):
	"""TODO: intention is to check for SRCF membership here... for now, just allow ~anyone"""

	def authorise(self, raven_response):
		return ("current" in raven_response.ptags)