/
authpam.py
94 lines (82 loc) · 3.07 KB
/
authpam.py
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
# -*- coding: utf-8 -*-
#
# Copyright 2011 Liftoff Software Corporation
#
# Thanks to Alan Schmitz for contributing this module!
# Meta
__version__ = '1.0'
__version_info__ = (1, 0)
__license__ = "AGPLv3 or Proprietary (see LICENSE.txt)"
__doc__ = """
This authentication module is built on top of gopam.py which is included with
Gate One.
It was originally written by Alan Schmitz.
The only non-obvious aspect of this module is that the pam_realm setting is only
used when the user is asked to authenticate and when the user's information is
stored in the 'users' directory. It isn't actually used in any part of the
authentication (PAM doesn't take a "realm" setting).
"""
# Standard library modules
import base64
# Our modules
import gopam
# 3rd party modules
import tornado.httpserver
import tornado.ioloop
import tornado.web
class PAMAuthMixin(tornado.web.RequestHandler):
"""
This is used by PAMAuthHandler in auth.py to authenticate users via PAM.
"""
def initialize(self):
"""
Print out helpful error messages if the requisite settings aren't
configured.
"""
self.require_setting("pam_realm", "PAM Realm")
self.require_setting("pam_service", "PAM Service")
def get_authenticated_user(self, callback):
"""
Processes the client's Authorization header and call self.auth_basic()
"""
auth_header = self.request.headers.get('Authorization')
if auth_header and auth_header.startswith('Basic '):
self.auth_basic(auth_header, callback)
def auth_basic(self, auth_header, callback):
"""
Perform Basic authentication using self.settings['pam_realm'].
"""
auth_decoded = base64.decodestring(auth_header[6:])
username, password = auth_decoded.split(':', 1)
try:
result = gopam.authenticate(
username,
password,
service=self.settings['pam_service'],
tty="console",
PAM_RHOST=self.request.remote_ip) # RHOST so it shows up in logs
if not result:
return self.authenticate_redirect()
except Exception as e: # Basic auth failed
if self.settings['debug']:
logging.debug(e)
return self.authenticate_redirect()
# NOTE: Basic auth just gives us the username without the @REALM part
# so we have to add it:
user = "%s@%s" % (username, self.settings['pam_realm'])
callback(user)
def authenticate_redirect(self):
"""
Informs the browser that this resource requires authentication (status
code 401) which should prompt the browser to reply with credentials.
The browser will be informed that we support Basic auth.
"""
if self._headers_written:
raise Exception('Headers have already been written')
self.set_status(401)
self.add_header(
"WWW-Authenticate",
'Basic realm="%s"' % self.settings['pam_realm']
)
self.finish()
return False