OpenSecurity/bin/opensecurity_client_restful_server.py
author Oliver Maurhart <oliver.maurhart@ait.ac.at>
Mon, 12 May 2014 18:00:05 +0200
changeset 144 dd472ede7a9f
parent 142 709cbb3b16de
child 151 d0f24f265331
permissions -rwxr-xr-x
mail icon and a package wide __init__.py (with metadata)
     1 #!/bin/env python
     2 # -*- coding: utf-8 -*-
     3 
     4 # ------------------------------------------------------------
     5 # opensecurity_client_restful_server
     6 # 
     7 # the OpenSecurity client RESTful server
     8 #
     9 # Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    10 #
    11 # Copyright (C) 2013 AIT Austrian Institute of Technology
    12 # AIT Austrian Institute of Technology GmbH
    13 # Donau-City-Strasse 1 | 1220 Vienna | Austria
    14 # http://www.ait.ac.at
    15 #
    16 # This program is free software; you can redistribute it and/or
    17 # modify it under the terms of the GNU General Public License
    18 # as published by the Free Software Foundation version 2.
    19 # 
    20 # This program is distributed in the hope that it will be useful,
    21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
    22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    23 # GNU General Public License for more details.
    24 # 
    25 # You should have received a copy of the GNU General Public License
    26 # along with this program; if not, write to the Free Software
    27 # Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    28 # Boston, MA  02110-1301, USA.
    29 # ------------------------------------------------------------
    30 
    31 
    32 # ------------------------------------------------------------
    33 # imports
    34 
    35 import getpass
    36 import json
    37 import os
    38 import os.path
    39 import platform
    40 import socket
    41 import subprocess
    42 import sys
    43 import urllib
    44 import urllib2
    45 import web
    46 import threading
    47 import time
    48 
    49 # local
    50 import __init__ as opensecurity
    51 
    52 
    53 # ------------------------------------------------------------
    54 # const
    55 
    56 
    57 """All the URLs we know mapping to class handler"""
    58 opensecurity_urls = (
    59     '/credentials',             'os_credentials',
    60     '/keyfile',                 'os_keyfile',
    61     '/log',                     'os_log',
    62     '/notification',            'os_notification',
    63     '/password',                'os_password',
    64     '/',                        'os_root'
    65 )
    66 
    67 
    68 # ------------------------------------------------------------
    69 # vars
    70 
    71 
    72 """The REST server object"""
    73 server = None
    74 
    75 
    76 # ------------------------------------------------------------
    77 # code
    78 
    79 
    80 class os_credentials:
    81 
    82     """OpenSecurity '/credentials' handler.
    83     
    84     This is called on GET /credentials?text=TEXT.
    85     Ideally this should pop up a user dialog to insert his
    86     credentials based the given TEXT.
    87     """
    88     
    89     def GET(self):
    90         
    91         # pick the arguments
    92         args = web.input()
    93         
    94         # we _need_ a text
    95         if not "text" in args:
    96             raise web.badrequest('no text given')
    97         
    98         # remember remote ip
    99         remote_ip = web.ctx.environ['REMOTE_ADDR']
   100 
   101         # create the process which queries the user
   102         dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   103         process_command = [sys.executable, dlg_image, 'credentials', args.text]
   104         process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   105         
   106         # run process result handling in seprate thread (not to block main one)
   107         bouncer = ProcessResultBouncer(process, remote_ip, '/credentials')
   108         bouncer.start()
   109          
   110         return 'user queried for credentials'
   111 
   112 
   113 class os_keyfile:
   114 
   115     """OpenSecurity '/keyfile' handler.
   116     
   117     This is called on GET /keyfile?text=TEXT.
   118     Ideally this should pop up a user dialog to insert his
   119     password along with a keyfile.
   120     """
   121     
   122     def GET(self):
   123         
   124         # pick the arguments
   125         args = web.input()
   126         
   127         # we _need_ a text
   128         if not "text" in args:
   129             raise web.badrequest('no text given')
   130             
   131         # remember remote ip
   132         remote_ip = web.ctx.environ['REMOTE_ADDR']
   133         
   134         # create the process which queries the user
   135         dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   136         process_command = [sys.executable, dlg_image, 'keyfile', args.text]
   137         process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   138         
   139         # run process result handling in seprate thread (not to block main one)
   140         bouncer = ProcessResultBouncer(process, remote_ip, '/keyfile')
   141         bouncer.start()
   142          
   143         return 'user queried for password and keyfile'
   144 
   145 
   146 class os_log:
   147 
   148     """OpenSecurity '/log' handler.
   149     
   150     This is called on GET or POST on the log function /log
   151     """
   152     
   153     def GET(self):
   154         
   155         # pick the arguments
   156         self.POST()
   157 
   158 
   159     def POST(self):
   160         
   161         # pick the arguments
   162         args = web.input()
   163         args['user'] = getpass.getuser()
   164         args['system'] = platform.node() + " " + platform.system() + " " + platform.release()
   165 
   166         # bounce log data
   167         url_addr = 'http://GIMME-SERVER-TO-LOG-TO/log'
   168 
   169         # by provided a 'data' we turn this into a POST statement
   170         d = urllib.urlencode(args)
   171         req = urllib2.Request(url_addr, d)
   172         try:
   173             res = urllib2.urlopen(req)
   174         except:
   175             print('failed to contact: ' + url_addr)
   176             print('log data: ' + d)
   177             return "Failed"
   178          
   179         return "Ok"
   180 
   181 
   182 class os_notification:
   183 
   184     """OpenSecurity '/notification' handler.
   185     
   186     This is called on GET /notification?msgtype=TYPE&text=TEXT.
   187     This will pop up an OpenSecurity notifcation window
   188     """
   189     
   190     def GET(self):
   191         
   192         # pick the arguments
   193         args = web.input()
   194         
   195         # we _need_ a type
   196         if not "msgtype" in args:
   197             raise web.badrequest('no msgtype given')
   198             
   199         if not args.msgtype in ['information', 'warning', 'critical']:
   200             raise web.badrequest('Unknown value for msgtype')
   201             
   202         # we _need_ a text
   203         if not "text" in args:
   204             raise web.badrequest('no text given')
   205             
   206         # invoke the user dialog as a subprocess
   207         dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.py')
   208         process_command = [sys.executable, dlg_image, 'notification-' + args.msgtype, args.text]
   209         process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
   210 
   211         return "Ok"
   212 
   213 
   214 class os_password:
   215 
   216     """OpenSecurity '/password' handler.
   217     
   218     This is called on GET /password?text=TEXT.
   219     Ideally this should pop up a user dialog to insert his
   220     password based device name.
   221     """
   222     
   223     def GET(self):
   224         
   225         # pick the arguments
   226         args = web.input()
   227         
   228         # we _need_ a text
   229         if not "text" in args:
   230             raise web.badrequest('no text given')
   231             
   232         # remember remote ip
   233         remote_ip = web.ctx.environ['REMOTE_ADDR']
   234         
   235         # create the process which queries the user
   236         dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   237         process_command = [sys.executable, dlg_image, 'password', args.text]
   238         process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   239         
   240         # run process result handling in seprate thread (not to block main one)
   241         bouncer = ProcessResultBouncer(process, remote_ip, '/password')
   242         bouncer.start()
   243         
   244         return 'user queried for password'
   245 
   246 
   247 class os_root:
   248 
   249     """OpenSecurity '/' handler"""
   250     
   251     def GET(self):
   252     
   253         res = "OpenSecurity-Client RESTFul Server { \"version\": \"%s\" }" % opensecurity.__version__
   254         
   255         # add some sample links
   256         res = res + """
   257         
   258 USAGE EXAMPLES:
   259         
   260 Request a password: 
   261     (copy paste this into your browser's address field after the host:port)
   262     
   263     /password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0)
   264     
   265     (eg.: http://127.0.0.1:8090/password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0))
   266     NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   267     
   268     
   269 Request a combination of user and password:
   270     (copy paste this into your browser's address field after the host:port)
   271     
   272     /credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.
   273     
   274     (eg.: http://127.0.0.1:8090/credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.)
   275     NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   276     
   277 
   278 Request a combination of password and keyfile:
   279     (copy paste this into your browser's address field after the host:port)
   280     
   281     /keyfile?text=Your%20private%20RSA%20Keyfile%3A
   282     
   283     (eg.: http://127.0.0.1:8090//keyfile?text=Your%20private%20RSA%20Keyfile%3A)
   284     NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   285     
   286 
   287 Start a Browser:
   288     (copy paste this into your browser's address field after the host:port)
   289 
   290     /application?vm=Debian+7&app=Browser
   291 
   292     (e.g. http://127.0.0.1:8090/application?vm=Debian+7&app=Browser)
   293         """
   294     
   295         return res
   296 
   297 
   298 class ProcessResultBouncer(threading.Thread):
   299 
   300     """A class to post the result of a given process - assuming it to be in JSON - to a REST Api."""
   301 
   302     def __init__(self, process, remote_ip, resource): 
   303 
   304         """ctor"""
   305 
   306         threading.Thread.__init__(self)
   307         self._process = process
   308         self._remote_ip = remote_ip
   309         self._resource = resource
   310  
   311     
   312     def stop(self):
   313 
   314         """stop thread"""
   315         self.running = False
   316         
   317     
   318     def run(self):
   319 
   320         """run the thread"""
   321 
   322         # invoke the user dialog as a subprocess
   323         result = self._process.communicate()[0]
   324         if self._process.returncode != 0:
   325             print 'user request has been aborted.'
   326             return
   327         
   328         # all ok, tell send request back appropriate destination
   329         try:
   330             j = json.loads(result)
   331         except:
   332             print 'error in password parsing'
   333             return
   334         
   335         # TODO: it would be WAY easier and secure if we just 
   336         #       add the result json to a HTTP-POST here.
   337         url_addr = 'http://' + self._remote_ip + ':58080' + self._resource
   338 
   339         # by provided a 'data' we turn this into a POST statement
   340         req = urllib2.Request(url_addr, urllib.urlencode(j))
   341         try:
   342             res = urllib2.urlopen(req)
   343         except:
   344             print 'failed to contact: ' + url_addr
   345             return 
   346 
   347 
   348 class RESTServerThread(threading.Thread):
   349 
   350     """Thread for serving the REST API."""
   351 
   352     def __init__(self, port): 
   353 
   354         """ctor"""
   355         threading.Thread.__init__(self)
   356         self._port = port 
   357     
   358     def stop(self):
   359 
   360         """stop thread"""
   361         self.running = False
   362         
   363     
   364     def run(self):
   365 
   366         """run the thread"""
   367         _serve(self._port)
   368 
   369 
   370 
   371 def is_already_running(port = 8090):
   372 
   373     """check if this is started twice"""
   374 
   375     try:
   376         s = socket.create_connection(('127.0.0.1', port), 0.5)
   377     except:
   378         return False
   379 
   380     return True
   381 
   382 
   383 def _serve(port):
   384 
   385     """Start the REST server"""
   386 
   387     global server
   388 
   389     # trick the web.py server 
   390     sys.argv = [__file__, str(port)]
   391     server = web.application(opensecurity_urls, globals())
   392     server.run()
   393 
   394 
   395 def serve(port = 8090, background = False):
   396 
   397     """Start serving the REST Api
   398     port ... port number to listen on
   399     background ... cease into background (spawn thread) and return immediately"""
   400 
   401     # start threaded or direct version
   402     if background == True:
   403         t = RESTServerThread(port)
   404         t.start()
   405     else:
   406         _serve(port)
   407 
   408 def stop():
   409 
   410     """Stop serving the REST Api"""
   411 
   412     global server
   413     if server is None:
   414         return
   415 
   416     server.stop()
   417 
   418 
   419 # start
   420 if __name__ == "__main__":
   421     serve()
   422