Browse Source

Retro fitted the class with security: Username/Password protection.

Joe Hager 18 years ago
parent
commit
80c18644be
1 changed files with 154 additions and 27 deletions
  1. 154 27
      direct/src/http/webNotifyDebug.py

+ 154 - 27
direct/src/http/webNotifyDebug.py

@@ -1,18 +1,63 @@
 from direct.task import Task
 from direct.task import Task
 from direct.http import WebRequest
 from direct.http import WebRequest
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
+import random, string
 
 
 class webNotifyDebug:
 class webNotifyDebug:
-    def __init__(self, portNumber = 8888):
+    def __init__(self, portNumber = 8888, username = None, password = None):
 
 
         self.portNumber = portNumber
         self.portNumber = portNumber
+        self.username = username
+        self.password = password
+        self.passwordProtect = False
+        self.pageToHit = 'debug'
+        self.authTokens = []
         self.web = WebRequest.WebRequestDispatcher()
         self.web = WebRequest.WebRequestDispatcher()
         self.web.listenOnPort(int(self.portNumber))
         self.web.listenOnPort(int(self.portNumber))
         # 'debug' will be the name of the page we have to hit
         # 'debug' will be the name of the page we have to hit
-        self.web.registerGETHandler('debug', self.debug)
+        # If both a username and password should be specified, then
+        # we will need to present a username and password prompt to the user
+        if self.username and self.password:
+            # set self.passwordProtect to True
+            self.passwordProtect = True
+            # Register 'debug' with the password prompt
+            self.web.registerGETHandler('debug', self.passwordPrompt)
+            self.web.registerGETHandler('authDebug', self.authDebug)
+            self.pageToHit = 'authDebug'
+        else:
+            self.web.registerGETHandler('debug', self.debug)
         self.startCheckingIncomingHTTP()
         self.startCheckingIncomingHTTP()
 
 
-    def listAllCategories(self, replyTo, optionalMessage = None):
+    def passwordPrompt(self, replyTo, **kw):
+        # This should get called if we need to prompt the user for
+        # a username and password.
+        try:
+            username = kw['username']
+            password = kw['password']
+        except KeyError:
+            # the user is probably making their initial connection to the
+            # password protected site. Present them with the login page
+            replyTo.respond('<HTML>\n<HEAD><TITLE>Direct Notify Web Interface - Username and Password Required</TITLE></HEAD>\n<BODY>\n<FONT SIZE=4>Username/Password authentication has been enabled. You must provide the following before gaining access to the system:<P><FORM action="debug" method="get">\nUsername: <INPUT type="text" name="username"><BR>\nPassword: <INPUT type="password" name="password"><BR>\n<input type=submit name="Submit" text="Login"></form>\n</BODY></HTML>')
+            return
+
+        # If the username and password are correct, we need to generate an
+        # auth token and place it in self.authTokens. If the username and
+        # password are incorrect. Return an error message indicating such.
+
+        if username == self.username and password == self.password:
+            # Username and Password match
+            # Generate auth token
+            authToken = self.genToken()
+            # Place the authToken in the list of valid auth tokens
+            self.authTokens.append(authToken)
+            
+            replyTo.respond('<HTML><HEAD><TITLE>Username and Password Good</TITLE></HEAD><BODY>Username and Password are good, please remember to logout when done. <A HREF=authDebug?authToken=%s>Click here to continue</a></BODY></HTML>' % (authToken))
+            return
+        else:
+            replyTo.respond('Username and/or password are incorrect')
+            return
+
+    def listAllCategories(self, replyTo, optionalMessage = None, authToken = None):
         # Return a web page with a list of all registered notify categories
         # Return a web page with a list of all registered notify categories
         # along with an HTML widget to chage their debug state
         # along with an HTML widget to chage their debug state
 
 
@@ -27,7 +72,10 @@ class webNotifyDebug:
 
 
         # define the static foot
         # define the static foot
 
 
-        foot = '</tbody></table></CENTER><BR><A HREF="debug">Main Menu</a></body></html>'
+        if authToken:
+            foot = '</tbody></table></CENTER><BR><A HREF="%s?authToken=%s">Main Menu</a></body></html>' % (self.pageToHit, authToken)
+        else:
+            foot = '</tbody></table></CENTER><BR><A HREF="%s">Main Menu</a></body></html>' % self.pageToHit
 
 
         # Sort our catagory list into alpha order
         # Sort our catagory list into alpha order
 
 
@@ -41,39 +89,45 @@ class webNotifyDebug:
             tempCategory = DirectNotifyGlobal.directNotify.getCategory(item)
             tempCategory = DirectNotifyGlobal.directNotify.getCategory(item)
             debugStatus = tempCategory.getDebug()
             debugStatus = tempCategory.getDebug()
             if debugStatus == 0:
             if debugStatus == 0:
-                body = '%s%s<A HREF="debug?command=on&item=%s">Off</a></td></tr>' % (body, select, item)
+                if authToken:
+                    body = '%s%s<A HREF="%s?command=on&item=%s&authToken=%s">Off</a></td></tr>' % (body, select, self.pageToHit, item, authToken)
+                else:
+                    body = '%s%s<A HREF="%s?command=on&item=%s">Off</a></td></tr>' % (body, select, self.pageToHit, item)
             else:
             else:
-                body = '%s%s<A HREF="debug?command=off&item=%s">On</a></td></tr>' % (body, select, item)
+                if authToken:
+                    body = '%s%s<A HREF="%s?command=off&item=%s&authToken=%s">On</a></td></tr>' % (body, select, self.pageToHit, item, authToken)
+                else:
+                    body = '%s%s<A HREF="%s?command=off&item=%s">On</a></td></tr>' % (body, select, self.pageToHit, item)
 
 
         replyTo.respond('%s\n%s\n%s\n' % (head, body, foot))
         replyTo.respond('%s\n%s\n%s\n' % (head, body, foot))
 
 
-    def turnCatOn(self, item, replyTo, sString = None):
+    def turnCatOn(self, item, replyTo, sString = None, authToken = None):
         # Used to turn a catagory (item), to the on state
         # Used to turn a catagory (item), to the on state
         try:
         try:
             notifyItem = DirectNotifyGlobal.directNotify.getCategory(item)
             notifyItem = DirectNotifyGlobal.directNotify.getCategory(item)
             notifyItem.setDebug(1)
             notifyItem.setDebug(1)
             updateMessage = 'Category <b>%s</b>, has been turned on' % (item)
             updateMessage = 'Category <b>%s</b>, has been turned on' % (item)
             if not sString:
             if not sString:
-                self.listAllCategories(replyTo, updateMessage)
+                self.listAllCategories(replyTo, updateMessage, authToken)
             else:
             else:
-                self.searchForCat(sString, replyTo, updateMessage)
+                self.searchForCat(sString, replyTo, updateMessage, authToken)
         except AttributeError:
         except AttributeError:
             replyTo.respond('Invalid Category Passed')
             replyTo.respond('Invalid Category Passed')
 
 
-    def turnCatOff(self, item, replyTo, sString = None):
+    def turnCatOff(self, item, replyTo, sString = None, authToken = None):
         # Used to turn a catagory (item), to the off state
         # Used to turn a catagory (item), to the off state
         try:
         try:
             notifyItem = DirectNotifyGlobal.directNotify.getCategory(item)
             notifyItem = DirectNotifyGlobal.directNotify.getCategory(item)
             notifyItem.setDebug(0)
             notifyItem.setDebug(0)
             updateMessage = 'Category <b>%s</b>, has been turned off' % (item)
             updateMessage = 'Category <b>%s</b>, has been turned off' % (item)
             if not sString:
             if not sString:
-                self.listAllCategories(replyTo, updateMessage)
+                self.listAllCategories(replyTo, updateMessage, authToken)
             else:
             else:
-                self.searchForCat(sString, replyTo, updateMessage)
+                self.searchForCat(sString, replyTo, updateMessage, authToken)
         except AttributeError:
         except AttributeError:
             replyTo.respond('Invalid Category Passed')
             replyTo.respond('Invalid Category Passed')
 
 
-    def searchForCat(self, searchString, replyTo, toggle = None):
+    def searchForCat(self, searchString, replyTo, toggle = None, authToken = None):
         # Used to execute a substring search for a category
         # Used to execute a substring search for a category
         completeList = DirectNotifyGlobal.directNotify.getCategories()
         completeList = DirectNotifyGlobal.directNotify.getCategories()
         resultList = []
         resultList = []
@@ -88,53 +142,110 @@ class webNotifyDebug:
             head = '<html>\n<head>\n<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">\n<title>DirectNotify - Search Results</title>\n</head>\n<body>\n<h1 style="text-align: center;">DirectNotify - Listing All Categories</h1>\n<CENTER><table style="text-align: left;" border="1" cellpadding="2" cellspacing="2">\n<tbody>\n<tr><th>Category</th><th>Debug Status</th></tr>\n'
             head = '<html>\n<head>\n<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">\n<title>DirectNotify - Search Results</title>\n</head>\n<body>\n<h1 style="text-align: center;">DirectNotify - Listing All Categories</h1>\n<CENTER><table style="text-align: left;" border="1" cellpadding="2" cellspacing="2">\n<tbody>\n<tr><th>Category</th><th>Debug Status</th></tr>\n'
         else:
         else:
             head = '<html>\n<head>\n<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">\n<title>DirectNotify - Search Results</title>\n</head>\n<body>\n<h1 style="text-align: center;">DirectNotify - Listing All Categories</h1>\n<CENTER><HR>%s<HR><br><table style="text-align: left;" border="1" cellpadding="2" cellspacing="2">\n<tbody>\n<tr><th>Category</th><th>Debug Status</th></tr>\n' % (toggle)
             head = '<html>\n<head>\n<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">\n<title>DirectNotify - Search Results</title>\n</head>\n<body>\n<h1 style="text-align: center;">DirectNotify - Listing All Categories</h1>\n<CENTER><HR>%s<HR><br><table style="text-align: left;" border="1" cellpadding="2" cellspacing="2">\n<tbody>\n<tr><th>Category</th><th>Debug Status</th></tr>\n' % (toggle)
-        foot = '</tbody></table></CENTER><BR><A HREF="debug">Main Menu</a></body></html>'
+        if authToken:
+            foot = '</tbody></table></CENTER><BR><A HREF="authDebug?authToken=%s">Main Menu</a></body></html>' % (authToken)
+        else:
+            foot = '</tbody></table></CENTER><BR><A HREF="debug">Main Menu</a></body></html>'
         body = ''
         body = ''
         for item in resultList:
         for item in resultList:
             select = '<tr><td>%s</td><td style="text-align: center;">' % (item)
             select = '<tr><td>%s</td><td style="text-align: center;">' % (item)
             tempCategory = DirectNotifyGlobal.directNotify.getCategory(item)
             tempCategory = DirectNotifyGlobal.directNotify.getCategory(item)
             debugStatus = tempCategory.getDebug()
             debugStatus = tempCategory.getDebug()
             if debugStatus == 0:
             if debugStatus == 0:
-                body = '%s%s<A HREF="debug?command=on&item=%s&sString=%s">Off</a></td></tr>' % (body, select, item, searchString)
+                if authToken:
+                    body = '%s%s<A HREF="%s?command=on&item=%s&sString=%s&authToken=%s">Off</a></td></tr>' % (body, select, self.pageToHit, item, searchString, authToken)
+                else:
+                    body = '%s%s<A HREF="%s?command=on&item=%s&sString=%s">Off</a></td></tr>' % (body, select, self.pageToHit, item, searchString)
             else:
             else:
-                body = '%s%s<A HREF="debug?command=off&item=%s&sString=%s">On</a></td></tr>' % (body, select, item, searchString)
+                if authToken:
+                    body = '%s%s<A HREF="%s?command=off&item=%s&sString=%s&authToken=%s">On</a></td></tr>' % (body, select, self.pageToHit, item, searchString, authToken)
+                else:
+                    body = '%s%s<A HREF="%s?command=off&item=%s&sString=%s">On</a></td></tr>' % (body, select, self.pageToHit, item, searchString)
 
 
         replyTo.respond('%s\n%s\n%s\n' % (head, body, foot))
         replyTo.respond('%s\n%s\n%s\n' % (head, body, foot))
 
 
-
-
-
     def debug(self, replyTo, **kw):
     def debug(self, replyTo, **kw):
+        try:
+            authToken = kw['authToken']
+        except KeyError:
+            authToken = None
         try:
         try:
             command = kw['command']
             command = kw['command']
             if command == 'listAll':
             if command == 'listAll':
-                self.listAllCategories(replyTo)
+                if self.passwordProtect:
+                    self.listAllCategories(replyTo, None, authToken)
+                else:
+                    self.listAllCategories(replyTo)
             elif command == 'on':
             elif command == 'on':
                 item = kw['item']
                 item = kw['item']
                 try:
                 try:
                     sString = kw['sString']
                     sString = kw['sString']
-                    self.turnCatOn(item, replyTo, sString)
+                    if self.passwordProtect:
+                        self.turnCatOn(item, replyTo, sString, authToken)
+                    else:
+                        self.turnCatOn(item, replyTo, sString)
                 except KeyError:
                 except KeyError:
-                    self.turnCatOn(item, replyTo)
+                    if self.passwordProtect:
+                        self.turnCatOn(item, replyTo, None, authToken)
+                    else:
+                        self.turnCatOn(item, replyTo)
             elif command == 'off':
             elif command == 'off':
                 item = kw['item']
                 item = kw['item']
                 try:
                 try:
                     sString = kw['sString']
                     sString = kw['sString']
-                    self.turnCatOff(item, replyTo, sString)
+                    if self.passwordProtect:
+                        self.turnCatOff(item, replyTo, sString, authToken)
+                    else:
+                        self.turnCatOff(item, replyTo, sString)
                 except KeyError:
                 except KeyError:
-                    self.turnCatOff(item, replyTo)
+                    if self.passwordProtect:
+                        self.turnCatOff(item, replyTo, None, authToken)
+                    else:
+                        self.turnCatOff(item, replyTo)
             elif command == 'search':
             elif command == 'search':
                 searchString = kw['searchString']
                 searchString = kw['searchString']
-                self.searchForCat(searchString, replyTo)
+                if self.passwordProtect:
+                    self.searchForCat(searchString, replyTo, None, authToken)
+                else:
+                    self.searchForCat(searchString, replyTo)
+            elif command == 'logOff' and authToken:
+                self.logOut(replyTo, authToken)
             else:
             else:
                 replyTo.respond('Error: Invalid args')
                 replyTo.respond('Error: Invalid args')
             return
             return
         except KeyError:
         except KeyError:
             pass
             pass
         # Basic Index Page
         # Basic Index Page
-        
-        replyTo.respond('<html><head>\n<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">\n<title>DirectNotify Web Interface</title>\n</head>\n<body>\n<div style="text-align: center;">\n<h1>DirectNotify Web Interface</h1>\n</div>\n<hr style="width: 100%; height: 2px;">\n<form method="get" action="debug" name="searchfom"><INPUT TYPE=HIDDEN NAME="command" VALUE="search">Search for a DirectNotify Category: <input name="searchString"> <input type=submit name="Submit"></button><br>\n</form>\n<br>\n<A HREF="debug?command=listAll">Display all DirectNotify Categories</a>\n</body>\n</html>')
 
 
+        if not authToken:
+            replyTo.respond('<html><head>\n<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">\n<title>DirectNotify Web Interface</title>\n</head>\n<body>\n<div style="text-align: center;">\n<h1>DirectNotify Web Interface</h1>\n</div>\n<hr style="height: 2px;">\n<form method="get" action="debug" name="searchfom"><INPUT TYPE=HIDDEN NAME="command" VALUE="search">Search for a DirectNotify Category: <input name="searchString"> <input type=submit name="Submit"></button><br>\n</form>\n<br>\n<A HREF="%s?command=listAll">Display all DirectNotify Categories</a>\n</body>\n</html>' % (self.pageToHit))
+        else:
+            replyTo.respond('<html><head>\n<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">\n<title>DirectNotify Web Interface</title>\n</head>\n<body>\n<div style="text-align: center;">\n<h1>DirectNotify Web Interface</h1>\n</div>\n<hr style="height: 2px;">\n<form method="get" action="authDebug" name="searchfom"><INPUT TYPE=HIDDEN NAME="command" VALUE="search"><INPUT TYPE=HIDDEN NAME="authToken" VALUE="%s">Search for a DirectNotify Category: <input name="searchString"> <input type=submit name="Submit"></button><br>\n</form>\n<br>\n<A HREF="%s?command=listAll&authToken=%s">Display all DirectNotify Categories</a><BR>\n<A HREF="authDebug?command=logOff&authToken=%s>Log Off</a></body>\n</html>' % (authToken, self.pageToHit, authToken, authToken))
+
+    def logOut(self, replyTo, authToken):
+        # Delete token from auth list
+        self.authTokens.remove(authToken)
+        replyTo.respond('<HTML><HEAD><TITLE>Logout Sucessful</TITLE></HEAD>\n<BODY>Logout complete. You will need to login again to use the system</BODY>\n</HTML>')
+
+    def authDebug(self, replyTo, **kw):
+        try:
+            authToken = kw['authToken']
+            try:
+                 pos = self.authTokens.index(authToken)
+            except ValueError:
+                # authToken passed is not in the list
+                replyTo.respond('Error: Client not authorized')
+                return
+        except (ValueError, KeyError):
+            # authToken not passed in GET. Return an error
+            replyTo.respond('Error: No auth token passed from client')
+            return
+
+        # If we've gotten this far, we have determined that an auth token was
+        # passed in the HTTP GET and it is on the list of auth tokens.
+        # Now we can pass this to the normal debug URI
+        kw['authToken'] = authToken
+        self.debug(replyTo, **kw)
 
 
     def startCheckingIncomingHTTP(self):
     def startCheckingIncomingHTTP(self):
         taskMgr.remove('pollDirectDebugHTTPTask')
         taskMgr.remove('pollDirectDebugHTTPTask')
@@ -146,3 +257,19 @@ class webNotifyDebug:
     def pollDirectDebugHTTPTask(self,task):
     def pollDirectDebugHTTPTask(self,task):
         self.web.poll()
         self.web.poll()
         return Task.again
         return Task.again
+
+    def genToken(self):
+        alpha = string.letters.upper()
+        num = string.digits
+        ranNumber = ''
+        ranAlpha = ''
+        for i in range(3):
+            ranNumberOne = ranNumber + random.choice(num)
+        for i in range(3):
+            ranAlphaOne = ranAlpha + random.choice(alpha)
+        for i in range(3):
+            ranNumberTwo = ranNumber + random.choice(num)
+        for i in range(3):
+            ranAlphaTwo = ranAlpha + random.choice(alpha)
+        token = "%s%s%s%s" % (ranAlphaOne, ranNumberOne, ranAlphaTwo, ranNumberTwo)
+        return token