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