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