You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-core/core/server/server_class.py

455 lines
18 KiB

#-*- coding: utf-8 -*-
# Copyright 2012 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from soaplib.wsgi import Application
import re
import logging, os, OpenSSL
logger = logging.getLogger(__name__)
from lxml import etree
import soaplib
from soaplib.serializers.exception import Fault
from soaplib.serializers.primitive import string_encoding
from soaplib.soap import apply_mtom
import threading
import datetime,pickle
from decorators import Dec
HTTP_500 = '500 Internal server error'
HTTP_200 = '200 OK'
HTTP_405 = '405 Method Not Allowed'
HTTP_403 = '403 Forbidden'
class ClApplication(Application):
def __init__(self, services, tns, name=None, _with_partnerlink=False, \
log=None):
'''
@param A ServiceBase subclass that defines the exposed services.
'''
Application.__init__(self, services, tns)
# add object logging
self.log = log
# verification of compliance certificate and session (sid)
def check_cert_sid (self, sid, server):
import threading
curThread = threading.currentThread()
cert = curThread.client_cert
from cert_cmd import find_cert_id
cert_id = find_cert_id(cert, server.data_path, server.certbase)
cert_id = int(cert_id)
if cert_id == 0:
return 0
# session file
if not os.path.exists(server.sids):
os.system('mkdir %s' %server.sids)
fd = open(server.sids_file, 'r')
while 1:
try:
# read all on one record
list_sid = pickle.load(fd)
except Exception, e:
break
# find session id in sids file
if cert_id == list_sid[1]:
if sid == list_sid[0]:
return 1
return 0
# input parameters - certificate and name method
def check_rights(self, method_name, req_env, params):
""" check right client certificate for the method """
rmethod = re.compile('[{\w]+[}]')
method_rep = rmethod.findall(method_name)
method_name = method_name.replace(method_rep[0],'')
curThread = threading.currentThread()
cert = curThread.client_cert
server_cert = curThread.server.ssl_certificate
server_key = curThread.server.ssl_private_key
certbase = curThread.server.certbase
rights = curThread.server.rights
group_rights = curThread.server.group_rights
data_path = curThread.server.data_path
ip = req_env.get('REMOTE_ADDR')
permitted_methods = ['post_server_request', 'post_client_request', \
'get_server_cert', 'get_client_cert', \
'get_crl', 'get_server_host_name', 'get_ca']
if method_name in permitted_methods:
return 1
if cert == None:
if not method_name in permitted_methods:
self.log.debug('%s NoneCert %s 0 %s' \
%(datetime.datetime.now().__str__(), ip, method_name))
return 0
self.log.debug('%s NoneCert %s 1 %s' \
%(datetime.datetime.now().__str__(), ip, method_name))
return 1
if params:
if hasattr (params, 'sid') and (method_name in Dec.rightsMethods \
or (method_name.endswith('_view') and \
method_name[:-5] in Dec.rightsMethods)):
if not self.check_cert_sid(params.sid, curThread.server):
return 0
data_server_cert = open(server_cert,'r').read()
certobj = OpenSSL.crypto.load_certificate \
(OpenSSL.SSL.FILETYPE_PEM, data_server_cert)
data_server_key = open(server_key,'r').read()
Pkey = OpenSSL.crypto.load_privatekey \
(OpenSSL.SSL.FILETYPE_PEM, data_server_key)
signature = OpenSSL.crypto.sign(Pkey, cert, 'SHA1')
try:
OpenSSL.crypto.verify(certobj, signature, cert, 'SHA1')
except Exception, e:
print e
return 0
if method_name == 'cert_add':
return 1
certobj_cl = OpenSSL.crypto.load_certificate \
(OpenSSL.SSL.FILETYPE_PEM, cert)
try:
com = certobj_cl.get_extension(certobj_cl.get_extension_count()-1)
groups = com.get_data().split(':')[1]
groups_list = groups.split(',')
except:
return 0
cert_id = 0
# open certificates database
if not os.path.exists(certbase):
fc = open(certbase,"w")
fc.close()
from cert_cmd import find_cert_id
checked_id = find_cert_id(cert, data_path, certbase)
cert_id = int(checked_id)
count = 0
flag = 0
# if certificate found
if cert_id > 0:
if not method_name in Dec.rightsMethods:
return 1
# if group = all and not redefined group all
if 'all' in groups_list:
find_flag = False
fd = open(group_rights, 'r')
t = fd.read()
# find all in group_rights file
for line in t.splitlines():
if line.split()[0] == 'all':
find_flag = True
break
if not find_flag:
return 1
for right_param in Dec.rightsMethods[method_name]:
flag = 0
try:
# check rights
if not os.path.exists (rights):
open(rights,'w')
with open(rights) as fr:
t = fr.read()
for line in t.splitlines():
words = line.split()
# first word in line equal name input method
if words[0] == right_param:
for word in words:
try:
word = int(word)
except:
continue
# compare with certificat number
if cert_id == word:
# if has right
count += 1
flag = 1
break
if cert_id == -word:
return 0
if flag: break
if flag: break
# open file with groups rights
if not os.path.exists (group_rights):
open(group_rights,'w')
with open(group_rights) as fd:
t = fd.read()
for line in t.splitlines():
words = line.split(' ',1)
# first word in line equal name input method
if words[0] in groups_list:
methods = words[1].split(',')
for word in methods:
# compare with certificat number
if right_param == word.strip():
# if has right
count += 1
flag = 1
break
if flag: break
except:
return 0
if count == len (Dec.rightsMethods[method_name]):
return 1
elif method_name == 'post_cert':
return 1
return 0
def create_path(self):
""" create paths for server files """
curThread = threading.currentThread()
data_path = curThread.server.data_path
sids = curThread.server.sids
pids = curThread.server.pids
cert_path = curThread.server.cert_path
if not os.path.exists(sids):
if not os.path.exists(data_path):
os.makedirs(data_path)
os.makedirs(sids)
if not os.path.exists(pids):
if not os.path.exists(data_path):
os.makedirs(data_path)
os.makedirs(pids)
if not os.path.exists(data_path + '/conf'):
if not os.path.exists(data_path):
os.makedirs(data_path)
os.makedirs(data_path + '/conf')
if not os.path.exists(data_path + '/conf/right.conf'):
open(data_path + '/conf/right.conf', 'w')
if not os.path.exists(data_path + '/conf/group_right.conf'):
open(data_path + '/conf/group_right.conf', 'w')
if not os.path.exists(data_path + '/client_certs'):
os.makedirs(data_path + '/client_certs')
if not os.path.exists(data_path + '/server_certs'):
os.makedirs(data_path + '/server_certs')
if not os.path.exists(cert_path):
os.makedirs(cert_path)
def _Application__handle_soap_request(self, req_env, start_response, url):
"""
This function is too big.
"""
curThread = threading.currentThread()
curThread.REMOTE_ADDR = req_env.get('REMOTE_ADDR')
curThread.REMOTE_PORT = req_env.get('REMOTE_PORT')
ip = req_env.get('REMOTE_ADDR')
http_resp_headers = {
'Content-Type': 'text/xml',
'Content-Length': '0',
}
method_name = None
self.create_path()
try:
# implementation hook
self.on_call(req_env)
if req_env['REQUEST_METHOD'].lower() != 'post':
http_resp_headers['Allow'] = 'POST'
start_response(HTTP_405, http_resp_headers.items())
return ['']
input = req_env.get('wsgi.input')
length = req_env.get("CONTENT_LENGTH")
body = input.read(int(length))
try:
service = None
soap_req_header, soap_req_payload = \
self._Application__decode_soap_request(
req_env, body)
if not (soap_req_payload is None):
self.validate_request(soap_req_payload)
method_name = \
self._Application__get_method_name(req_env, soap_req_payload)
if method_name is None:
resp = "Could not extract method name from the request!"
http_resp_headers['Content-Length'] = str(len(resp))
start_response(HTTP_500, http_resp_headers.items())
return [resp]
service_class = self.get_service_class(method_name)
service = self.get_service(service_class, req_env)
finally:
# for performance reasons, we don't want the following to run
# in production even though we won't see the results.
if logger.level == logging.DEBUG:
try:
logger.debug(etree.tostring(etree.fromstring(body),
pretty_print=True))
except etree.XMLSyntaxError,e:
logger.debug(body)
raise Fault('Client.XMLSyntax',\
'Error at line: %d, col: %d' % e.position)
# retrieve the method descriptor
descriptor = service.get_method(method_name)
func = getattr(service, descriptor.name)
# decode header object
if soap_req_header is not None and len(soap_req_header) > 0:
in_header = descriptor.in_header
service.soap_in_header = in_header.from_xml(soap_req_header)
# decode method arguments
if soap_req_payload is not None and len(soap_req_payload) > 0:
params = descriptor.in_message.from_xml(soap_req_payload)
else:
params = [None] * len(descriptor.in_message._type_info)
#### check_rights
curThread = threading.currentThread()
if hasattr (params, 'sid'):
curThread.lang = service.get_lang(params.sid)
# check exists client certificate
if not hasattr (curThread, 'client_cert'):
curThread.client_cert = None
# check rights client certificate for the method
check = self.check_rights(method_name, req_env, params)
if not check:
if curThread.client_cert:
certobj = OpenSSL.crypto.load_certificate \
(OpenSSL.SSL.FILETYPE_PEM, curThread.client_cert)
finger = certobj.digest('SHA1')
self.log.debug('%s %s %s 0 %s' \
%(datetime.datetime.now().__str__(), finger, ip, \
method_name[5:]))
resp = "Permission denied: " + method_name
http_resp_headers['Content-Length'] = str(len(resp))
start_response(HTTP_403, http_resp_headers.items())
return [resp]
#### logging
if curThread.client_cert:
certobj = OpenSSL.crypto.load_certificate \
(OpenSSL.SSL.FILETYPE_PEM, curThread.client_cert)
finger = certobj.digest('SHA1')
self.log.debug('%s %s %s 1 %s' \
%(datetime.datetime.now().__str__(), finger, ip, \
method_name[5:]))
# implementation hook
service.on_method_call(req_env, method_name, params,
soap_req_payload)
# call the method
result_raw = service.call_wrapper(func, params)
# construct the soap response, and serialize it
envelope = etree.Element('{%s}Envelope' % soaplib.ns_soap_env,
nsmap=soaplib.nsmap)
#
# header
#
soap_header_elt = etree.SubElement(envelope,
'{%s}Header' % soaplib.ns_soap_env)
if service.soap_out_header != None:
if descriptor.out_header is None:
logger.warning("Skipping soap response header as %r method "
"is not published to have a soap response "
"header" % method_name)
else:
descriptor.out_header.to_xml(
service.soap_out_header,
self.get_tns(),
soap_header_elt,
descriptor.out_header.get_type_name()
)
if len(soap_header_elt) > 0:
envelope.append(soap_header_elt)
#
# body
#
soap_body = etree.SubElement(envelope,
'{%s}Body' % soaplib.ns_soap_env)
# instantiate the result message
result_message = descriptor.out_message()
# assign raw result to its wrapper, result_message
out_type = descriptor.out_message._type_info
if len(out_type) > 0:
if len(out_type) == 1:
attr_name = descriptor.out_message._type_info.keys()[0]
setattr(result_message, attr_name, result_raw)
else:
for i in range(len(out_type)):
attr_name = descriptor.out_message._type_info.keys()[i]
setattr(result_message, attr_name, result_raw[i])
# transform the results into an element
descriptor.out_message.to_xml(result_message, self.get_tns(),
soap_body)
# implementation hook
service.on_method_return(req_env, result_raw, soap_body,
http_resp_headers)
#
# misc
#
results_str = etree.tostring(envelope, xml_declaration=True,
encoding=string_encoding)
if descriptor.mtom:
http_resp_headers, results_str = apply_mtom(http_resp_headers,
results_str, descriptor.out_message._type_info,[result_raw])
# implementation hook
self.on_return(req_env, http_resp_headers, results_str)
# initiate the response
http_resp_headers['Content-Length'] = str(len(results_str))
start_response(HTTP_200, http_resp_headers.items())
if logger.level == logging.DEBUG:
logger.debug('\033[91m'+ "Response" + '\033[0m')
logger.debug(etree.tostring(envelope, xml_declaration=True,
pretty_print=True))
# return the serialized results
return [results_str]
# The user issued a Fault, so handle it just like an exception!
except Fault, e:
return self._Application__handle_fault(req_env, start_response,
http_resp_headers, service, e)
except Exception, e:
fault = Fault('Server', str(e))
return self._Application__handle_fault(req_env, start_response,
http_resp_headers, service, fault)