wplayer

Check-in [26103392e4]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Logins and cookies
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | master | trunk
Files: files | file ages | folders
SHA3-256:26103392e4a98b806367c85e62bdee00e33109d86395babf9c67fc7fa8a51187
User & Date: ajv-899-334-8894@vsta.org 2016-02-06 19:02:37
Context
2016-02-10
01:35
Make "chore" its own repo check-in: a8a14e254d user: ajv-899-334-8894@vsta.org tags: master, trunk
2016-02-06
19:02
Logins and cookies check-in: 26103392e4 user: ajv-899-334-8894@vsta.org tags: master, trunk
2016-02-03
19:34
Bring up HTTPS check-in: 523deb309d user: ajv-899-334-8894@vsta.org tags: master, trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to .gitignore.

1
2
3

*.pyc
css
certs




>
1
2
3
4
*.pyc
css
certs
var

Changes to chore/config.py.

9
10
11
12
13
14
15
16
17
18
19
20
21






22
23
24
25
26
27
28
    # Set up our fields
    def __init__(self):
	self.fields0 = set()	# Top-level config elems
	self.mults0 = set()	#  ...those which can appear multiple times
	self.subconfig = set()	#  ...those with indented config
	self.fields1 = set()	# Second-level config elems
	self.mults1 = set()	#  ...multiple times
	self.onearg = set()	# Elems which take a single arg
	self.floats = set()	# Elems whose value is a float
	self.ints = set()	#  ...int's
	self.noarg = set()	# Things with no arguments
	self.onearg = set()	#  ...with a single arg







    # Accept input line, return line as lexed
    #  into words
    def lex(self, l):
	words = []
	curword = ""
	inquote = False
	while l:







<





>
>
>
>
>
>







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
    # Set up our fields
    def __init__(self):
	self.fields0 = set()	# Top-level config elems
	self.mults0 = set()	#  ...those which can appear multiple times
	self.subconfig = set()	#  ...those with indented config
	self.fields1 = set()	# Second-level config elems
	self.mults1 = set()	#  ...multiple times

	self.floats = set()	# Elems whose value is a float
	self.ints = set()	#  ...int's
	self.noarg = set()	# Things with no arguments
	self.onearg = set()	#  ...with a single arg

	# Basic config
	#
	# Administrative name of the service we're supplying
	self.fields0.add("service")
	self.onearg.add("service")

    # Accept input line, return line as lexed
    #  into words
    def lex(self, l):
	words = []
	curword = ""
	inquote = False
	while l:

Changes to chore/handlers.py.

1
2
3
4

5
6
7
8



9
10
11
12
13
14
15
..
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
..
64
65
66
67
68
69
70
71
72
73
74
75



76
77
78
79
80
81
82
83
84
85
86
87
...
342
343
344
345
346
347
348




349
350
351
352
353
354
355
356


















































































357
358
359
360
361
362
363
364
365

366
367
368
369
370

















371
372
373
374
375
376
377
378
379
380
381
382
383

384
385
386
387

388
389
390
391
392
393
394
...
401
402
403
404
405
406
407







408
409
410
411
412
413
414
#
# www.py
#	Handling for actual web server bits
#

import base64, socket, sys, json, os, time
from BaseHTTPServer import BaseHTTPRequestHandler
from sendfile import sendfile




# Return value for a digit (hex)
def digval(c):
    if c.isdigit():
	return ord(c) - ord('0')
    c = c.lower()
    if (c < 'a') or (c > 'f'):
	return None
................................................................................
	c1 = c2 = None
    return res

# An instance of this runs to dispatch a given HTTP request
#
# An actual app will inherit this and add app-specific dispatchers.
#
# self.server gets set to our app's root state
class Chore_Handler(BaseHTTPRequestHandler):

    # Default config
    def __init__(self, conn, addr, approot, inits):

	# /js/..., /imgs/..., and such just serve files
	self.lits = ("js", "imgs", "css", "html")

	# Code which, in turn, tries to dispatch ops
	# (Each is (op, fn), e.g., ("GET", self.do_get1))
	self.dispatchers = [ ("GET", self.base_get), ]
................................................................................
	# Add on others (mixins)
	for i in inits:
	    i(self)

	# Default title
	self.title = "web server"

	# Hook here if you want HTTP authen
	self.authentication = None

	# These get dicts if there are options
	self.vals = self.rvals = None




	# This both init's, and runs the web service
	# (BTW, this sucks.  Break out instance creation and
	#  service start--always.)
	BaseHTTPRequestHandler.__init__(self, conn, addr, approot)

    # Hook to set up SSL
    def setup(self):
	self.connection = self.request
	self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)

................................................................................
	    return True,self.send_files(fname)

	# Couldn't help
	return False,None

    # Send a 401 response, indicating authentication issues
    def send401(self, msg):




	self.send_response(401)
	self.send_header('WWW-Authenticate',
	    'Basic realm="Web XMPP Interface"')
	self.send_header('Content-type', 'text/html')
	self.end_headers()
	self.wfile.write(msg)
	return False



















































































    # Intercept after request is parsed; apply HTTP authentication
    def parse_request(self):

	# Make sure it's well-formed
	res = BaseHTTPRequestHandler.parse_request(self)
	if not res:
	    return False

	# TBD, actual authentication

	if self.authentication is None:
	    return True
	approot = self.server

	# Apply digest authentication?

















	auth = self.headers.getheader('Authorization')
	if auth is None:
	    return self.send401("Authentication required\r\n")

	# Do we accept it?
	auth = base64.decodestring(auth[6:])
	tup = auth.split(":")
	if len(tup) != 2:
	    return self.send401("Malformed authentication response\r\n")
	user = self.authenticate(tup[0], tup[1])
	if user is None:
	    return self.send401("Incorrect username or password\r\n")


	# Record User instance of this connection
	self.user = user

	# Ok

	return True

    # Send header
    # Also canonicalize to DOS-style line endings (ew)
    # This code only handles textual responses; binary/large
    #  media is handled inline.
    def send_result(self, buf, mtyp, cacheable=False):
................................................................................
        self.send_header("Content-type", mtyp)
        self.send_header("Content-Length", len(buf))
	if not cacheable:
	    self.send_header("Cache-Control", "NoCache")
	else:
	    self.send_header("Last-Modified",
		time.asctime(time.localtime(self.changed)))







        self.end_headers()

	return buf

    # Generate a meta REFRESH body, send it out
    #  to the client
    def send_redir(self, url, msg=None):




>




>
>
>







 







|



|







 







<
<
<


>
>
>




|







 







>
>
>
>


|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








|
>
|

<

<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>









|
<


>
|
<

<
>







 







>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
..
68
69
70
71
72
73
74



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
...
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458

459

460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486

487
488
489
490

491

492
493
494
495
496
497
498
499
...
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
#
# www.py
#	Handling for actual web server bits
#
import pdb
import base64, socket, sys, json, os, time
from BaseHTTPServer import BaseHTTPRequestHandler
from sendfile import sendfile

# Re-login every 30 days
LoginInterval = 60*60*24*30

# Return value for a digit (hex)
def digval(c):
    if c.isdigit():
	return ord(c) - ord('0')
    c = c.lower()
    if (c < 'a') or (c > 'f'):
	return None
................................................................................
	c1 = c2 = None
    return res

# An instance of this runs to dispatch a given HTTP request
#
# An actual app will inherit this and add app-specific dispatchers.
#
# self.server gets set to our web server's state
class Chore_Handler(BaseHTTPRequestHandler):

    # Default config
    def __init__(self, conn, addr, webserver, inits):

	# /js/..., /imgs/..., and such just serve files
	self.lits = ("js", "imgs", "css", "html")

	# Code which, in turn, tries to dispatch ops
	# (Each is (op, fn), e.g., ("GET", self.do_get1))
	self.dispatchers = [ ("GET", self.base_get), ]
................................................................................
	# Add on others (mixins)
	for i in inits:
	    i(self)

	# Default title
	self.title = "web server"




	# These get dicts if there are options
	self.vals = self.rvals = None

	# Hook for custom headers
	self.extra_headers = []

	# This both init's, and runs the web service
	# (BTW, this sucks.  Break out instance creation and
	#  service start--always.)
	BaseHTTPRequestHandler.__init__(self, conn, addr, webserver)

    # Hook to set up SSL
    def setup(self):
	self.connection = self.request
	self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)

................................................................................
	    return True,self.send_files(fname)

	# Couldn't help
	return False,None

    # Send a 401 response, indicating authentication issues
    def send401(self, msg):
	approot = self.server.approot

	appname = approot.config.get("service",
	    "Web Application Interface")
	self.send_response(401)
	self.send_header('WWW-Authenticate',
	    'Basic realm="%s"' % (appname,))
	self.send_header('Content-type', 'text/html')
	self.end_headers()
	self.wfile.write(msg)
	return False

    # Decode Cookie: header option
    def get_cookie(self, target):
	# Header present?
	pdb.set_trace()
	cookies = self.headers.get("cookie")
	if not cookies:
	    return None

	# Parse into cookies; each is "key=value", with
	#  semicolon separating them (if more than one)
	cookies = [c.strip().split('=') for c in cookies.split(';')]
	for tup in cookies:
	    if len(tup) != 2:
		continue

	    # Here's a match
	    if tup[0] == target:
		return tup[1]

	# Don't have that cookie
	return None

    # Each token is saved in var/cookies as a filename
    def valid_token(self, token):
	if ".." in token:
	    # No effin way
	    return False
	if not os.access("var/cookies/" + token, os.R_OK):
	    return False
	return True

    # Look up the username, see if it's the right password
    # Return True if they are OK, else False
    def authenticate(self, user, pw):
	webserver = self.server
	accts = webserver.config["accounts"]

	f = open(accts, "r")
	for l in f:
	    l = l.strip()
	    if (not l) or l.startswith("#"):
		continue

	    # <user> <pass>
	    tup = l.split()
	    if len(tup) != 2:
		continue

	    # For this user, do you know the password?
	    if tup[0] == user:
		f.close()
		return tup[1] == pw

	f.close()
	return False

    # We need a new cookie
    # Fills in to server.cookies[]
    def new_cookie(self):
	webserver = self.server

	# Generate a random cookie, store as an active cookie
	cookie = base64.b64encode(os.urandom(36))
	webserver.cookies.add(cookie)

	# Expires some day
	expires = time.time() + LoginInterval
	tm = time.strftime("%a, %d-%b-%Y %T GMT", time.gmtime(expires))

	# Set it on the user's browser
	self.extra_headers.append(
	 ("Set-Cookie", "loginToken=%s; Expires=%s" % (cookie, tm)) )

	# And save it across server restarts
	try:
	    # Just touch it
	    f = open("var/cookies/" + cookie, "w")
	    f.close()
	except:
	    # Oh, well.  It's good for the session.
	    pass

    # Intercept after request is parsed; apply HTTP authentication
    def parse_request(self):

	# Make sure it's well-formed
	res = BaseHTTPRequestHandler.parse_request(self)
	if not res:
	    return False

	# No configured authentication, let them proceed
	webserver = self.server
	if webserver.authentication is None:
	    return True



	# See if they can satisfy us with an authentication cookie
	cookie = self.get_cookie("loginToken")
	if cookie is not None:
	    # Already verified
	    if cookie in webserver.cookies:
		return True

	    # Look up in filesystem
	    if self.valid_token(cookie):
		self.cookie = cookie
		return True

	#
	# It's either absent, or invalid, or expired.
	#

	# We're going to need a user/pass
	auth = self.headers.getheader('Authorization')
	if auth is None:
	    return self.send401("Authentication required\r\n")

	# Do we accept it?
	auth = base64.decodestring(auth[6:])
	tup = auth.split(":")
	if len(tup) != 2:
	    return self.send401("Malformed authentication response\r\n")
	if not self.authenticate(tup[0], tup[1]):

	    return self.send401("Incorrect username or password\r\n")

	# Generate new cookie, record
	self.new_cookie()



	# Welcome on
	return True

    # Send header
    # Also canonicalize to DOS-style line endings (ew)
    # This code only handles textual responses; binary/large
    #  media is handled inline.
    def send_result(self, buf, mtyp, cacheable=False):
................................................................................
        self.send_header("Content-type", mtyp)
        self.send_header("Content-Length", len(buf))
	if not cacheable:
	    self.send_header("Cache-Control", "NoCache")
	else:
	    self.send_header("Last-Modified",
		time.asctime(time.localtime(self.changed)))

	# Add on any extras we've calculated
	if self.extra_headers:
	    for tag,val in self.extra_headers:
		self.send_header(tag, val)
	    del self.extra_headers[:]

        self.end_headers()

	return buf

    # Generate a meta REFRESH body, send it out
    #  to the client
    def send_redir(self, url, msg=None):

Changes to chore/www.py.

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
..
54
55
56
57
58
59
60







61
62
63
64
65
66
67
68
69
70
71
72
73
    #  and interfaces
    c.fields0.add("serve")
    c.subconfig.add("serve")
    c.onearg.add("serve")
    c.mults0.add("serve")

    # Config port, which network interface, and SSL stuff
    c.fields1.update( ("port", "iface", "publicCert", "privateKey") )


    # Port is numeric
    c.ints.add("port")

    # These take only one argument
    c.onearg.update( ("serve", "iface", "publicCert", "privateKey") )


# HTTP server, listens and spins up threads for each request
class HTTP(object):

    # Our connection back to the root WebXMPP state
    #
    # @proto indicates http[s]
    # The rest of the config params are in the @config dict
    #
    # @apphandler is an HTTP request handler, rooted
    #  from chore.handlers.Chore_Handler
    # @approot is rooted from chore.server.Server
................................................................................
	self.config = config

	self.apphandler = apphandler
	self.approot = approot

	# The socket we'll open
	self.socket = None








    # We're on our own thread, handle request
    def launch(self, conn, tup):
	try:
	    try:
		handler = self.apphandler(conn, tup, self.approot)
	    except:
		# Probably a closed socket on us
		pass
	    # The __init__ runs the service; when it returns
	    #  we're already done.
	finally:
	    try:







|
>





|
>




|







 







>
>
>
>
>
>
>





|







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
..
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
    #  and interfaces
    c.fields0.add("serve")
    c.subconfig.add("serve")
    c.onearg.add("serve")
    c.mults0.add("serve")

    # Config port, which network interface, and SSL stuff
    c.fields1.update( ("port", "iface", "publicCert", "privateKey",
	"accounts") )

    # Port is numeric
    c.ints.add("port")

    # These take only one argument
    c.onearg.update( ("serve", "iface", "publicCert", "privateKey",
	"accounts") )

# HTTP server, listens and spins up threads for each request
class HTTP(object):

    # Our connection back to the root app state
    #
    # @proto indicates http[s]
    # The rest of the config params are in the @config dict
    #
    # @apphandler is an HTTP request handler, rooted
    #  from chore.handlers.Chore_Handler
    # @approot is rooted from chore.server.Server
................................................................................
	self.config = config

	self.apphandler = apphandler
	self.approot = approot

	# The socket we'll open
	self.socket = None

	# Authentication?  Or None.
	self.authentication = config.get("accounts")
	if self.authentication:
	    self.cookies = set()
	else:
	    self.cookies = None

    # We're on our own thread, handle request
    def launch(self, conn, tup):
	try:
	    try:
		handler = self.apphandler(conn, tup, self)
	    except:
		# Probably a closed socket on us
		pass
	    # The __init__ runs the service; when it returns
	    #  we're already done.
	finally:
	    try: