OpenSecurity/bin/opensecurity_client_restful_server.py
author Oliver Maurhart <oliver.maurhart@ait.ac.at>
Wed, 30 Apr 2014 12:06:22 +0200
changeset 136 ac117cd7bab1
parent 134 f1c1c06c947d
child 142 709cbb3b16de
permissions -rwxr-xr-x
trayicon starts client rest server
     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 json
    36 import os
    37 import os.path
    38 import socket
    39 import subprocess
    40 import sys
    41 import urllib
    42 import urllib2
    43 import web
    44 import threading
    45 import time
    46 
    47 # local
    48 from environment import Environment
    49 
    50 
    51 # ------------------------------------------------------------
    52 # const
    53 
    54 
    55 __version__ = "0.2.5"
    56 
    57 
    58 """All the URLs we know mapping to class handler"""
    59 opensecurity_urls = (
    60     '/credentials',             'os_credentials',
    61     '/keyfile',                 'os_keyfile',
    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_notification:
   147 
   148     """OpenSecurity '/notification' handler.
   149     
   150     This is called on GET /notification?msgtype=TYPE&text=TEXT.
   151     This will pop up an OpenSecurity notifcation window
   152     """
   153     
   154     def GET(self):
   155         
   156         # pick the arguments
   157         args = web.input()
   158         
   159         # we _need_ a type
   160         if not "msgtype" in args:
   161             raise web.badrequest('no msgtype given')
   162             
   163         if not args.msgtype in ['information', 'warning', 'critical']:
   164             raise web.badrequest('Unknown value for msgtype')
   165             
   166         # we _need_ a text
   167         if not "text" in args:
   168             raise web.badrequest('no text given')
   169             
   170         # invoke the user dialog as a subprocess
   171         dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.py')
   172         process_command = [sys.executable, dlg_image, 'notification-' + args.msgtype, args.text]
   173         process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
   174 
   175         return "Ok"
   176 
   177 
   178 class os_password:
   179 
   180     """OpenSecurity '/password' handler.
   181     
   182     This is called on GET /password?text=TEXT.
   183     Ideally this should pop up a user dialog to insert his
   184     password based device name.
   185     """
   186     
   187     def GET(self):
   188         
   189         # pick the arguments
   190         args = web.input()
   191         
   192         # we _need_ a text
   193         if not "text" in args:
   194             raise web.badrequest('no text given')
   195             
   196         # remember remote ip
   197         remote_ip = web.ctx.environ['REMOTE_ADDR']
   198         
   199         # create the process which queries the user
   200         dlg_image = os.path.join(sys.path[0], 'opensecurity_dialog.pyw')
   201         process_command = [sys.executable, dlg_image, 'password', args.text]
   202         process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)        
   203         
   204         # run process result handling in seprate thread (not to block main one)
   205         bouncer = ProcessResultBouncer(process, remote_ip, '/password')
   206         bouncer.start()
   207         
   208         return 'user queried for password'
   209 
   210 
   211 class os_root:
   212 
   213     """OpenSecurity '/' handler"""
   214     
   215     def GET(self):
   216     
   217         res = "OpenSecurity-Client RESTFul Server { \"version\": \"%s\" }" % __version__
   218         
   219         # add some sample links
   220         res = res + """
   221         
   222 USAGE EXAMPLES:
   223         
   224 Request a password: 
   225     (copy paste this into your browser's address field after the host:port)
   226     
   227     /password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0)
   228     
   229     (eg.: http://127.0.0.1:8090/password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0))
   230     NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   231     
   232     
   233 Request a combination of user and password:
   234     (copy paste this into your browser's address field after the host:port)
   235     
   236     /credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.
   237     
   238     (eg.: http://127.0.0.1:8090/credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.)
   239     NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   240     
   241 
   242 Request a combination of password and keyfile:
   243     (copy paste this into your browser's address field after the host:port)
   244     
   245     /keyfile?text=Your%20private%20RSA%20Keyfile%3A
   246     
   247     (eg.: http://127.0.0.1:8090//keyfile?text=Your%20private%20RSA%20Keyfile%3A)
   248     NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   249     
   250 
   251 Start a Browser:
   252     (copy paste this into your browser's address field after the host:port)
   253 
   254     /application?vm=Debian+7&app=Browser
   255 
   256     (e.g. http://127.0.0.1:8090/application?vm=Debian+7&app=Browser)
   257         """
   258     
   259         return res
   260 
   261 
   262 class ProcessResultBouncer(threading.Thread):
   263 
   264     """A class to post the result of a given process - assuming it to be in JSON - to a REST Api."""
   265 
   266     def __init__(self, process, remote_ip, resource): 
   267 
   268         """ctor"""
   269 
   270         threading.Thread.__init__(self)
   271         self._process = process
   272         self._remote_ip = remote_ip
   273         self._resource = resource
   274  
   275     
   276     def stop(self):
   277 
   278         """stop thread"""
   279         self.running = False
   280         
   281     
   282     def run(self):
   283 
   284         """run the thread"""
   285 
   286         # invoke the user dialog as a subprocess
   287         result = self._process.communicate()[0]
   288         if self._process.returncode != 0:
   289             print 'user request has been aborted.'
   290             return
   291         
   292         # all ok, tell send request back appropriate destination
   293         try:
   294             j = json.loads(result)
   295         except:
   296             print 'error in password parsing'
   297             return
   298         
   299         # TODO: it would be WAY easier and secure if we just 
   300         #       add the result json to a HTTP-POST here.
   301         url_addr = 'http://' + self._remote_ip + ':58080' + self._resource
   302         url = url_addr + '?' + urllib.urlencode(j)
   303         req = urllib2.Request(url)
   304         try:
   305             res = urllib2.urlopen(req)
   306         except:
   307             print 'failed to contact: ' + url_addr
   308             return 
   309 
   310 
   311 class RESTServerThread(threading.Thread):
   312 
   313     """Thread for serving the REST API."""
   314 
   315     def __init__(self, port): 
   316 
   317         """ctor"""
   318         threading.Thread.__init__(self)
   319         self._port = port 
   320     
   321     def stop(self):
   322 
   323         """stop thread"""
   324         self.running = False
   325         
   326     
   327     def run(self):
   328 
   329         """run the thread"""
   330         _serve(self._port)
   331 
   332 
   333 
   334 def is_already_running(port = 8090):
   335 
   336     """check if this is started twice"""
   337 
   338     try:
   339         s = socket.create_connection(('127.0.0.1', port), 0.5)
   340     except:
   341         return False
   342 
   343     return True
   344 
   345 
   346 def _serve(port):
   347 
   348     """Start the REST server"""
   349 
   350     global server
   351 
   352     # trick the web.py server 
   353     sys.argv = [__file__, str(port)]
   354     server = web.application(opensecurity_urls, globals())
   355     server.run()
   356 
   357 
   358 def serve(port = 8090, background = False):
   359 
   360     """Start serving the REST Api
   361     port ... port number to listen on
   362     background ... cease into background (spawn thread) and return immediately"""
   363 
   364     # start threaded or direct version
   365     if background == True:
   366         t = RESTServerThread(port)
   367         t.start()
   368     else:
   369         _serve(port)
   370 
   371 def stop():
   372 
   373     """Stop serving the REST Api"""
   374 
   375     global server
   376     if server is None:
   377         return
   378 
   379     server.stop()
   380 
   381 
   382 # start
   383 if __name__ == "__main__":
   384     serve()
   385