This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
authentic-adeline/extra/modules/liberty.py

1135 lines
48 KiB
Python

import time
import sys
import re
import base64
import Cookie
import StringIO
import cgi
import traceback
import socket
import xml.sax.saxutils
import string
import random
import md5
import lasso
try:
import lassodgme
except ImportError:
print >> sys.stderr, 'Missing lassodgme module; ID-WSF proxy has been disabled'
lassodgme = None
from quixote import get_session, get_session_manager, get_request, get_response, redirect, get_field, get_publisher
from quixote.http_request import parse_header
from qommon import get_cfg, get_logger
from qommon import errors, template
import misc
import authentic.liberty.root
from authentic.liberty.root import SOAPError
from alternatespui import AlternateSpDir
import stores
ED_MIGRATION_DISABLED = False
ED_DOCUMENTS_MIGRATION_DISABLED = True
def isotime(offset = 0):
return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(time.time()+offset))
def get_unique_id():
return base64.encodestring("".join([random.choice("0123456789ABCDEF") for x in range(40)]).decode('hex'))
msp_urn = 'urn:dgme:msp:ed:2007-01'
adeline_urn = 'urn:fr.icdc.dei.adeline:ppAdeline:2008-01'
dummy_value = 'd41d8cd98f00b204e9800998ecf8427e'
try:
from lxml import etree as ET
except ImportError:
try:
import cElementTree as ET
except ImportError:
try:
import elementtree.ElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
class RedirectException(Exception):
def __init__(self, redirect_url):
self.redirect_url = redirect_url
class AlternateLibertyDirectory(authentic.liberty.root.RootDirectory):
_q_exports = ["", "sp", "singleSignOn", "soapEndpoint",
'metadata', ('metadata.xml', 'metadata'), 'public_key',
"singleLogout", "singleLogoutReturn", "federationTermination",
"federationTerminationReturn", "registerNameIdentifier", "consent",
"proxySoapEndpoint", "proxyAssertionConsumer", "proxySingleLogout",
"proxySingleLogoutNext", 'mspProxyEndpoint', 'proxySingleLogoutReturn']
sp = AlternateSpDir()
def perform_proxy_login(self, idp = None, nameIdPolicy = None, extensions = None):
session = get_session()
server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
login = lasso.Login(server)
login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
login.request.nameIdPolicy = 'federated'
login.request.forceAuthn = False
login.request.isPassive = False
login.request.consent = "urn:liberty:consent:obtained"
login.request.requestAuthnContext = lasso.LibRequestAuthnContext()
login.request.requestAuthnContext.authnContextClassRef = (
lasso.LIB_AUTHN_CONTEXT_CLASS_REF_PASSWORD_PROTECTED_TRANSPORT, )
if session.lasso_login_dump:
# replicates parameters from the request we proxy
orig_login = lasso.Login.newFromDump(authentic.misc.get_lasso_server(),
session.lasso_login_dump)
login.request.nameIdPolicy = orig_login.request.nameIdPolicy
login.request.forceAuthn = orig_login.request.forceAuthn
login.request.isPassive = orig_login.request.isPassive
login.request.consent = orig_login.request.consent
if nameIdPolicy: # forced value
login.request.nameIdPolicy = nameIdPolicy
if extensions:
ext_list = lasso.StringList()
for ext in extensions:
ext_list.append('<lib:Extension xmlns:lib="urn:liberty:iff:2003-08"><%s>%s</%s></lib:Extension>' % (ext[0], ext[1], ext[0]))
login.request.extension = tuple(ext_list)
login.buildAuthnRequestMsg()
return redirect(login.msgUrl)
def proxyAssertionConsumer(self):
session = get_session()
request = get_request()
continue_home = ('/',_('Home'))
if session.after_url:
continue_home = (session.after_url, _('request origin'))
server = authentic.misc.get_lasso_server(lasso.PROVIDER_ROLE_SP)
if session.msp_login_dump:
login = lasso.Login.newFromDump(server, session.msp_login_dump)
session.msp_login_dump = None
identity = None
else:
login = lasso.Login(server)
if request.get_method() == 'GET' or get_field('LAREQ'):
if request.get_method() == 'GET':
login.initRequest(request.get_query(), lasso.HTTP_METHOD_REDIRECT)
else:
login.initRequest(get_field('LAREQ'), lasso.HTTP_METHOD_POST)
login.buildRequestMsg()
try:
soap_answer = soap_call(login.msgUrl, login.msgBody)
except authentic.liberty.root.SOAPError:
return template.error_page(_('Failed to get Assertion from identity provider'),continue_to=continue_home)
try:
login.processResponseMsg(soap_answer)
except lasso.Error, error:
# Traitement d'une demande de federation existante
# apres desynchro, on recree a la volee
if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
t = self.proxy_auth_federation_not_found(login)
if t:
return t
get_response().expire_cookie('msp-user',
domain = get_publisher().config.session_cookie_domain,
path = '/')
return redirect('/federate_msp')
# Traitement particulie pour le cas d'une demande de creation
# alors que la federation existe
if error[0] == lasso.LOGIN_ERROR_STATUS_NOT_SUCCESS and\
login.response.status and\
login.response.status.statusCode and\
login.response.status.statusCode.statusCode and\
login.response.status.statusCode.statusCode.statusCode and\
login.response.status.statusCode.statusCode.statusCode.value == 'msp:AlreadyFederated':
print 'Interception already federated'
return redirect('/login_msp')
if error[0] == lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
get_response().expire_cookie('msp-user',
domain = get_publisher().config.session_cookie_domain,
path = '/')
if not session.lasso_login_dump:
# probably user clicked on "back" on MSP (see bug 138)
if not session.user:
return redirect('/login')
else:
try:
msg = login.response.status.statusMessage
return template.error_page(_('Response is not Success (%s)') % msg, continue_to=('/', _('Home')))
except:
return redirect('/')
login = lasso.Login.newFromDump(authentic.misc.get_lasso_server(),
session.lasso_login_dump)
session.lasso_login_dump = None
return self.sso_after_authentication(login, False, proxied = True)
try:
msg = login.response.status.statusMessage
return template.error_page(_('Response is not Success (%s)') % msg, continue_to=('/', _('Home')))
except:
pass
if error[0] == lasso.PROFILE_ERROR_INVALID_MSG:
print 'Received invalid SOAP answer when resolving artifact: \'%s\'' % soap_answer
raise error
t = self.proxy_auth_peer_cancelled(login)
if t:
return t
else:
self.proxy_auth_ok(login)
else:
login.processAuthnResponseMsg(get_field('LARES'))
if session.lasso_proxy_session_dump:
login.setSessionFromDump(session.lasso_proxy_session_dump)
ni = login.nameIdentifier.content
authentic.identities.get_store().load_identities()
identity = authentic.identities.get_store().get_identity_for_name_identifier(ni)
if identity is None and get_session().user:
identity = authentic.identities.get_store().get_identity(get_session().user)
if identity:
if identity.lasso_proxy_dump:
login.setIdentityFromDump(identity.lasso_proxy_dump)
session.set_user(identity.id)
else:
session.msp_login_dump = login.dump()
return redirect(request.environ['SCRIPT_NAME'] + '/login')
session.proxied_idp = login.remoteProviderId
if not session.name_identifiers:
session.name_identifiers = []
session.name_identifiers.append(login.nameIdentifier.content)
migration_url = None
login.acceptSso()
if login.isIdentityDirty:
if not identity.lasso_proxy_dump:
migration_url = self.migrationMsp(login, identity, session)
identity.lasso_proxy_dump = login.identity.dump()
if login.isSessionDirty:
session.lasso_proxy_session_dump = login.session.dump()
authentic.identities.get_store().save(identity)
response = get_response()
response.set_cookie('msp-user', 'true',
domain = get_publisher().config.session_cookie_domain,
expires = Cookie._getdate(3*365*86400),
path = '/')
if migration_url:
return redirect(migration_url)
if session.lasso_login_dump:
login = lasso.Login.newFromDump(authentic.misc.get_lasso_server(), session.lasso_login_dump)
session.lasso_login_dump = None
return self.sso_after_authentication(login, True, proxied = True)
if session.after_url:
after_url = session.after_url
session.after_url = None
return redirect(after_url)
return redirect(get_request().environ['SCRIPT_NAME'] + '/')
def migrationMsp(self, login, identity, session):
if not identity.lasso_dump:
# create empty identity
identity.lasso_dump = '<Identity xmlns="http://www.entrouvert.org/namespaces/lasso/0.0" Version="2"></Identity>'
get_logger().info('ID-WSF migration to MSP')
server = authentic.misc.get_lasso_server()
lasso_identity = lasso.Identity.newFromDump(identity.lasso_dump)
lst = []
offerings = lasso_identity.getOfferings(lasso.PP_HREF)
if offerings:
lst.extend([x.entryId for x in offerings if \
x.serviceInstance.providerId == server.providerId])
offerings = lasso_identity.getOfferings(adeline_urn)
if offerings:
server = authentic.misc.get_lasso_server()
lst.extend([x.entryId for x in offerings if \
x.serviceInstance.providerId == server.providerId])
# Migrate documents from Adeline to MSP
# First get documents list
if not ED_MIGRATION_DISABLED and not ED_DOCUMENTS_MIGRATION_DISABLED:
adeline_service = lasso.DataService.newFull(server, offerings[0])
lasso.registerDstService('ad', adeline_urn)
adeline_service.initQuery('/ad:PersonalDocumentList', 'doclist')
adeline_service.buildRequestMsg()
try:
soap_anwser = soap_call(adeline_service.msgUrl, adeline_service.msgBody + ' ')
adeline_service.processQueryResponseMsg(soap_anwser)
doc_list = adeline_service.getAnswer('/ad:PersonalDocumentList')
if doc_list:
doc_keys = re.findall('documentKey="(.+)"', doc_list)
if doc_keys:
# then register after job to get and transfer all documents
adeline_service = lasso.DataService.newFull(server, offerings[0])
get_response().add_after_job('migrate data space',
MigrateDataSpace(identity, session, adeline_service, doc_keys),
fire_and_forget = True)
except (SOAPError, lasso.ProfileInvalidSoapMsgError), error:
pass
if lst:
# identity had data services, remove them
for x in lst:
lasso_identity.removeResourceOffering(x)
if not identity.resource_id:
identity.resource_id = '%s/resources/identity/%s' % (
get_cfg('idp')['base_url'],
md5.md5("".join([random.choice(string.ascii_letters+string.digits) for x in range(40)])).hexdigest())
resource_offering = lasso.DiscoResourceOffering(self.get_pp_proxy_service())
resource_offering.resourceId = lasso.DiscoResourceID(identity.resource_id)
resource_offering.abstract = 'Fake Personal Profile Service, Proxying to MSP'
lasso_identity.addResourceOffering(resource_offering)
resource_offering = lasso.DiscoResourceOffering(self.get_ppa_proxy_service())
resource_offering.resourceId = lasso.DiscoResourceID(identity.resource_id)
resource_offering.abstract = 'Fake Adeline Personal Profile Service, Proxying to MSP'
lasso_identity.addResourceOffering(resource_offering)
# and save that
identity.lasso_dump = lasso_identity.dump()
authentic.identities.get_store().save(identity)
if lst and not ED_MIGRATION_DISABLED:
# identity had data services, notify them
data_migration_url = get_cfg('adeline', {}).get('data_migration_url')
return data_migration_url
return None
def get_identity_by_resource_id(self, resource_id):
identity = stores.get_collectivity_store(None).get_identity_by_resource_id(
resource_id.content)
if not identity:
return None
if lasso.WSF_SUPPORT:
lasso_identity = lasso.Identity.newFromDump(identity.lasso_dump)
server = authentic.misc.get_lasso_server()
offerings = lasso_identity.getOfferings(adeline_urn)
if offerings:
lst = [x for x in lasso_identity.getOfferings(adeline_urn) if \
x.serviceInstance.providerId == server.providerId]
else:
lst = []
if len(lst) == 0:
# adds authentic id-sis ppa offering
resource_offering = lasso.DiscoResourceOffering(self.get_ppa_proxy_service())
resource_offering.resourceId = lasso.DiscoResourceID(identity.resource_id)
resource_offering.abstract = "Adeline Personal Profile with Authentic informations"
lasso_identity.addResourceOffering(resource_offering)
identity.lasso_dump = lasso_identity.dump()
authentic.identities.get_store().save(identity)
return identity
def get_pp_proxy_service(self):
server = authentic.misc.get_lasso_server()
return lasso.DiscoServiceInstance(
lasso.PP_HREF,
server.providerId,
lasso.DiscoDescription_newWithBriefSoapHttpDescription(
lasso.SECURITY_MECH_NULL,
'%s/mspProxyEndpoint' % get_cfg('idp', {}).get('base_url', None)))
def get_ppa_proxy_service(self):
server = authentic.misc.get_lasso_server()
return lasso.DiscoServiceInstance(
adeline_urn,
server.providerId,
lasso.DiscoDescription_newWithBriefSoapHttpDescription(
lasso.SECURITY_MECH_NULL,
'%s/mspProxyEndpoint' % get_cfg('idp', {}).get('base_url', None)))
def getMessageId(self, soap_envelope):
header = soap_envelope.header
if header:
headers = header.other
for x in headers:
if isinstance(x, lasso.SoapBindingCorrelation):
return x.messageId
def getRefToMessageId(self, soap_envelope):
header = soap_envelope.header
if header:
headers = header.other
for x in headers:
if isinstance(x, lasso.SoapBindingCorrelation):
return x.refToMessageId
def setMessageId(self, soap_envelope, messageId):
headers = soap_envelope.header.other
for x in headers:
if isinstance(x, lasso.SoapBindingCorrelation):
x.messageId = messageId
def setRefToMessageId(self, soap_envelope, messageId):
headers = soap_envelope.header.other
for x in headers:
if isinstance(x, lasso.SoapBindingCorrelation):
x.refToMessageId = messageId
def get_msp_document(self, identity, messageId):
# 1st, get current user session
sessions = [x for x in get_session_manager().values() if x.user == identity.id]
sessions.sort(lambda x,y: cmp(x.get_creation_time(), y.get_creation_time()))
if not sessions:
return None
latest_session = sessions[-1]
# 2nd, query MSP
# Init DownloadFile request
wsc_proxy_service = self.msp_disco_query(latest_session)
wsc_proxy_service.initDownloadFileRequest(lasso.SECURITY11_MECH_TLS_SAML)
# FIXME: set appropriate teleserviceId
wsc_proxy_service.request.teleserviceId = '1001'
# Add interaction service header
headers = wsc_proxy_service.soapEnvelopeRequest.header.other
user_interaction = lasso.IsUserInteraction()
user_interaction.id = 'userinteractid'
user_interaction.redirect = 1
user_interaction.maxInteractTime = 600
headers = headers + ( user_interaction, )
wsc_proxy_service.soapEnvelopeRequest.header.other = headers
if messageId:
self.setRefToMessageId(wsc_proxy_service.soapEnvelopeRequest, messageId)
wsc_proxy_service.buildRequestMsg()
more_headers = {}
if get_cfg('adeline', {}).get('http_auth_tuple'):
more_headers['Authorization'] = 'Basic ' + base64.encodestring(
get_cfg('adeline', {}).get('http_auth_tuple'))
# Send request to MSP
body = wsc_proxy_service.msgBody
# FIXME: MSP is void !
body = body.replace('xmlns:is="urn:liberty:is:2003-08"','').replace(
'<is:UserInteraction', '<is:UserInteraction xmlns:is="urn:liberty:is:2003-08"')
soap_answer = soap_call(wsc_proxy_service.msgUrl, body + ' ',
more_headers = more_headers)
soap_answer = soap_answer.replace('S:detail', 'S:Detail')
try:
wsc_proxy_service.processResponseMsg(soap_answer)
except lasso.Error, error:
if wsc_proxy_service.response and wsc_proxy_service.response.detail and wsc_proxy_service.response.detail.any[0]:
print wsc_proxy_service.response.detail.any[0].dump()
if error[0] != lasso.SOAP_FAULT_REDIRECT_REQUEST or not wsc_proxy_service.msgUrl:
raise
messageId = self.getMessageId(wsc_proxy_service.soapEnvelopeResponse)
return None, wsc_proxy_service.msgUrl, messageId
# Convert DownloadFileReponse to a document
# Content of Response:
# response->file -> {
# int type
# string nom
# string binaryFile # base64 encoded content
# string xmlMetadata (xml fragment for the metatada)
# }
# to convert to
# ad:PersonalDocument
# ad:PersonalDocumentMetadata
# ad:Content
#
response = wsc_proxy_service.response
if response.status.code == 'Ok':
template = '''<PersonalDocument xmlns='%(urn)s'>
<PersonalDocumentMetadata fileName="%(nom)s" documentType="%(type_urn)s" mimeType="application/octet-stream" modifyTime="%(date)s" documentKey="%(nom)s">
<ExternMetadata>%(other_metadata)s</ExternMetadata>
<Title>%(nom)s</Title>
</PersonalDocumentMetadata>
<Content>%(content)s</Content>
</PersonalDocument>'''
context = {
'urn' : adeline_urn,
'nom' : response.file.nom,
'date' : '2008-07-01T14:12:00',
'type_urn' : 'urn:fr.icdc.dei.adeline:document-type:msp:%s' % response.file.type,
'other_metadata' : response.file.xmlMetadata,
'content' : response.file.binaryFile
}
metadata = ET.XML(response.file.xmlMetadata)
documentmsp = metadata.findall('documentMSPType')
if documentmsp:
modifytimes = documentmsp[0].findall('dateReference')
if modifytimes and modifytimes[0].text:
context['date'] = modifytimes[0].text
return (template % context), None, None
else:
return None, None, None
def get_blank_soap_fault(self, message_id):
header = lasso.SoapHeader()
if (message_id):
correlation = lasso.SoapBindingCorrelation()
correlation.messageId = get_unique_id()
correlation.refToMessageId = message_id
correlation.mustUnderstand = 1
correlation.id = correlation.messageId
correlation.actor = "http://schemas.xmlsoap.org/soap/actor/next"
corrleation.timestamp = isotime()
header.other = (correlation,)
fault = lasso.SoapFault()
fault.faultcode = 'S:Server'
fault.faultstring = 'Server Error'
body = lasso.SoapBody()
body.any = (fault,)
envelope = lasso.SoapEnvelope(body)
envelope.header = header
return envelope
def get_redirect_soap_fault(self, redirect_url,message_id):
soap_fault = self.get_blank_soap_fault(message_id)
if lasso.WSF_SUPPORT:
redirectrequest = lasso.IsRedirectRequest(redirect_url)
detail = lasso.SoapDetail()
detail.any = (redirectrequest,)
soap_fault.body.any[0].detail = detail
else:
print 'Pas de support ID-WSF, redirect request impossible'
return soap_fault
def mspProxyEndpoint(self):
request = get_request()
ctype = request.environ.get("CONTENT_TYPE")
body = None
if not ctype:
get_logger().warn('SOAP Endpoint got message without content-type')
return
ctype, ctype_params = parse_header(ctype)
if ctype != 'text/xml':
get_logger().warn('SOAP Endpoint got message with wrong content-type (%s)' % ctype)
return
get_response().set_content_type('text/xml')
length = int(request.environ.get('CONTENT_LENGTH'))
soap_message = request.stdin.read(length)
print 'Received on mspProxySoapEndpoint: %s' % soap_message
request_type = lasso.getRequestTypeFromSoapMsg(soap_message)
service = lasso.DataService(authentic.misc.get_lasso_server())
messageId = None
try:
if request_type == lasso.REQUEST_TYPE_DST_QUERY:
try:
service_prefix = re.findall('<([a-z]*?):Query>', soap_message)[0]
service_href = re.findall('xmlns:%s="(.*?)"' % service_prefix, soap_message)[0]
lasso.registerDstService(service_prefix, service_href)
except IndexError: # too bad, this hacky thing didn't work.
service_href = lasso.PP_HREF
service.processQueryMsg(soap_message)
messageId = self.getRefToMessageId(service.soapEnvelopeRequest)
query_items = []
for item in service.request.queryItem:
get_logger().info('Received DST_QUERY for %s' % item.select)
query_items.append(item.select)
try:
identity = self.get_identity_by_resource_id(service.resourceId)
except:
pass # XXX: build deny request ?
# fill resource data, query MSP for values
if '/ad:PersonalDocument' in query_items:
resource, redirect_url, messageId = self.get_msp_document(identity, messageId)
else:
resource = self.query_msp_for_pp(service_href, identity)
redirect_url = None
if resource:
service.resourceData = resource
elif redirect_url and messageId:
service.soapEnvelopeResponse.body.any = ()
# Keep messageId of SOAP Fault
# if the request is repeated we will use
# the refToMessageId on the proxyed request
# to set refToMessageId on the request to MSP
self.setMessageId(service.soapEnvelopeResponse, messageId)
service.needRedirectUser(redirect_url)
else:
raise Exception('No Resource!!!')
service.buildResponseMsg()
body = service.msgBody
if request_type == lasso.REQUEST_TYPE_DST_MODIFY:
try:
service_prefix = re.findall('<([a-z]*?):Modify>', soap_message)[0]
service_href = re.findall('xmlns:%s="(.*?)"' % service_prefix, soap_message)[0]
lasso.registerDstService(service_prefix, service_href)
except IndexError: # too bad, this hacky thing didn't work.
service_href = lasso.PP_HREF
service.processModifyMsg(soap_message)
messageId = self.getRefToMessageId(service.soapEnvelopeRequest)
modify_items = {}
for item in service.request.modification:
get_logger().info('Received DST_MODIFY for %s' % item.select)
modify_items[item.select] = item.newData.any
try:
identity = self.get_identity_by_resource_id(service.resourceId)
except:
pass # XXX: build deny request
resource = self.init_modify_msp_for_pp(service_href, modify_items)
service.resourceData = resource
service.buildModifyResponseMsg()
result, messageId = self.do_modify_msp_for_pp(service_href, identity, service.resourceData, messageId)
if result and result != 'Ok':
service.soapEnvelopeResponse.body.any = ()
# Keep messageId of SOAP Fault
# if the request is repeated we will use
# the refToMessageId on the proxyed request
# to set refToMessageId on the request to MSP
self.setMessageId(service.soapEnvelopeResponse, messageId)
print 'Redirection sur %s' % result
service.needRedirectUser(result)
service.buildModifyResponseMsg()
body = service.msgBody
except RedirectException, redirect_exception:
soap_fault = self.get_redirect_soap_fault(redirect_exception.redirect_url, messageId)
body = soap_fault.exportToXml()
except Exception, exception:
fp = StringIO.StringIO()
traceback.print_exc(file=fp)
msg = fp.getvalue()
print msg
body = '''<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Header/>
<Body>
<Fault>
<faultcode>PythonException</faultcode>
<faultstring>Python Exception</faultstring>
<Detail>%s</Detail>
</Fault>
</Body>
</Envelope>''' % xml.sax.saxutils.escape(msg)
return body
msp_oids = {
'OID.1.1.1' : { 'name': 'lbNomFamille'},
'OID.1.1.2' : { 'name': 'lbNomUsage'},
'OID.1.1.3' : { 'name': 'lbSurnom'},
'OID.1.1.4.1' : { 'name': 'lbPrenoms0'},
'OID.1.1.4.2' : { 'name': 'lbPrenoms1'},
'OID.1.1.4.3' : { 'name': 'lbPrenoms2'},
'OID.1.1.4.4' : { 'name': 'lbPrenoms3'},
'OID.1.1.5' : { 'name': 'cdSexe' },
'OID.1.1.6' : { 'name': 'cdCivilite'},
'OID.1.1.7' : { 'name': 'lbTitre'},
'OID.1.1.8' : { 'name': 'cdSituationFamiliale'},
'OID.1.1.9' : { 'name': 'dtNaissance'},
'OID.1.1.10' : { 'name': 'lbLocaliteNaissance'},
'OID.1.1.11' : { 'name': 'cdPostalNaissance'},
'OID.1.1.12' : { 'name': 'lbPaysNaissance'},
'OID.1.1.13' : { 'name': 'lbDivisionTerritorialeNaissance',},
'OID.1.2.1.1.1' : { 'name': 'lbPointRemise'},
'OID.1.2.1.1.2' : { 'name': 'lbComplement'},
'OID.1.2.1.1.3' : { 'name': 'noVoirie'},
'OID.1.2.1.1.4' : { 'name': 'lbExtension'},
'OID.1.2.1.1.5' : { 'name': 'lbTypeVoie'},
'OID.1.2.1.1.6' : { 'name': 'lbNomVoie'},
'OID.1.2.1.1.7' : { 'name': 'lbLieuDit'},
'OID.1.2.1.1.8' : { 'name': 'lbMentionPartDistrib'},
'OID.1.2.1.1.9' : { 'name': 'cdCedex'},
'OID.1.2.1.1.10' : { 'name': 'lbBureauCedex'},
'OID.1.2.1.1.11' : { 'name': 'cdPostal'},
'OID.1.2.1.1.12' : { 'name': 'lbLocaliteResidence'},
'OID.1.2.1.1.13' : { 'name': 'lbPays'},
'OID.1.2.1.1.14' : { 'name': 'lbDivisionTerritoriale'},
'OID.1.3.1.1.1' : { 'name': 'lbEmail'},
'OID.1.3.2.1.1' : { 'name': 'noTelFixe'},
'OID.1.3.2.1.2' : { 'name': 'lbIndPaysTelFixe'},
'OID.1.3.3.1.1' : { 'name': 'noTelPortable'},
'OID.1.3.3.1.2' : { 'name': 'lbIndPaysTelPortable'},
'OID.1.3.4.1.1' : { 'name': 'noFax'},
'OID.1.3.4.1.2' : { 'name': 'lbIndPaysFax'}
}
# SIS-PP content
# <InformalName>%(lbInformalName)s</InformalName>
# <CommonName>
# <AnalyzedName>
# <PersonalTitle>%(cdCivilite)s</PersonalTitle>
# <FN>%(lbPrenoms)s</FN>
# <SN>%(lbNomFamille)s</SN>
# </AnalyzedName>
# </CommonName>
# <LegalIdentity>
# <DOB>%(dtNaissance)s</DOB>
# </LegalIdentity>
# <AddressCard>
# <AddrType>urn:liberty:id-sis-pp:addrType:home</AddrType>
# <Address>
# <PostalAddress></PostalAddress>
# <PostalCode>%(cdPostal)s</PostalCode>
# <L>%(lbLocaliteResidence)s</L>
# <C>%(lbPays)s</C>
# </Address>
# </AddressCard>
# <MsgContact>
# <MsgType>urn:liberty:id-sis-pp:msgType:personal</MsgType>
# <MsgMethod>urn:liberty:id-sis-pp:msgMethod:voice</MsgMethod>
# <MsgAccount>%(noTelFixe)s</MsgAccount>
# </MsgContact>
# <MsgContact>
# <MsgType>urn:liberty:id-sis-pp:msgType:personal</MsgType>
# <MsgMethod>urn:liberty:id-sis-pp:msgMethod:email</MsgMethod>
# <MsgProvider>%(emailAccount)s</MsgProvider>
# <MsgAccount>%(emailProvider)s</MsgAccount>
# </MsgContact>
# <MsgContact>
# <MsgType>urn:liberty:id-sis-pp:msgType:mobile</MsgType>
# <MsgMethod>urn:liberty:id-sis-pp:msgMethod:voice</MsgMethod>
# <MsgAccount>%(noTelPortable)s</MsgAccount>
# </MsgContact>
pp_format = '''
<PP xmlns="%(urn)s">
%(extension)s
</PP>
'''
def query_msp_for_pp(self, service_type, identity, wsc_proxy_service = None):
# 1st, get current user session
sessions = [x for x in get_session_manager().values() if x.user == identity.id]
sessions.sort(lambda x,y: cmp(x.get_creation_time(), y.get_creation_time()))
if not sessions:
return None
latest_session = sessions[-1]
# 2nd, query MSP
# Init ReadOid request
if not wsc_proxy_service:
wsc_proxy_service = self.msp_disco_query(latest_session)
wsc_proxy_service.initReadOidRequest(lasso.SECURITY11_MECH_TLS_SAML)
# FIXME: set appropriate teleserviceId
wsc_proxy_service.request.teleserviceId = '1001'
# Request complete description
for oid in self.msp_oids:
wsc_proxy_service.addReadOidItem(oid)
wsc_proxy_service.buildRequestMsg()
more_headers = {}
if get_cfg('adeline', {}).get('http_auth_tuple'):
more_headers['Authorization'] = 'Basic ' + base64.encodestring(
get_cfg('adeline', {}).get('http_auth_tuple'))
# Send request to MSP
if identity.id == 11:
soap_answer = soap_call(wsc_proxy_service.msgUrl, wsc_proxy_service.msgBody,
more_headers = more_headers)
else:
soap_answer = soap_call(wsc_proxy_service.msgUrl, wsc_proxy_service.msgBody + ' ',
more_headers = more_headers)
soap_answer = soap_answer.replace('S:detail', 'S:Detail')
# Process ReadOid response from MSP
wsc_proxy_service.processResponseMsg(soap_answer)
if wsc_proxy_service.response.status.code == 'Ok':
# Build a profile
profile = {}
extension = []
for oid in wsc_proxy_service.response.oId:
try:
desc = self.msp_oids[oid.oid]
except Exception:
continue
profile[desc['name']] = oid.content
extension.append('<%s>%s</%s>' % (desc['name'], cgi.escape(oid.content), desc['name']))
for oid in self.msp_oids:
if not self.msp_oids[oid]['name'] in profile:
name = self.msp_oids[oid]['name']
profile[name] = ''
extension.append('<%s></%s>' % (name, name))
extension = '\n'.join(extension)
profile['extension'] = extension
if service_type == lasso.PP_HREF:
profile['urn'] = lasso.PP_HREF
user_data = self.pp_format % profile
elif service_type == adeline_urn:
profile['urn'] = adeline_urn
user_data = self.pp_format % profile
else:
user_data = '<PP xmlns="urn:liberty:id-sis-pp:2003-08">Erreur ID-WSF</PP>'
return user_data
def init_modify_msp_for_pp(self, service_type, modify_items):
if service_type == adeline_urn:
content = []
for oid in self.msp_oids:
name = self.msp_oids[oid]['name']
content.append('<%s>%s</%s>' % (name, dummy_value, name))
content = '\n'.join(content)
return '<PP xmlns="%s">\n%s\n</PP>' % (adeline_urn, content)
else:
return ''
def do_modify_msp_for_pp(self, service_type, identity, resource_data, messageId):
# 1st, get current user session
sessions = [x for x in get_session_manager().values() if x.user == identity.id]
sessions.sort(lambda x,y: cmp(x.get_creation_time(), y.get_creation_time()))
if not sessions:
return (None, None)
latest_session = sessions[-1]
# 2nd, query MSP
# Init UpdateOid request
wsc_proxy_service = self.msp_disco_query(latest_session)
try:
# Get previous datas
old_datas = self.query_msp_for_pp(service_type, identity)
wsc_proxy_service.initUpdateOidRequest(lasso.SECURITY11_MECH_TLS_SAML)
wsc_proxy_service.request.teleserviceId = '1001'
if service_type == adeline_urn:
modify_oids = []
pp = ET.XML(resource_data)
if old_datas:
oldpp = ET.XML(old_datas)
else:
oldpp = None
for oid in self.msp_oids:
name = self.msp_oids[oid]['name']
item = pp.findall('{%s}%s' % (adeline_urn, name))
olditem = oldpp.findall('{%s}%s' % (adeline_urn, name))
if item:
item = item[0]
if item.text != dummy_value:
text = item.text
if text == None:
text = ""
if olditem:
olditem = olditem[0]
if olditem is not None:
olditem = olditem.text
if olditem is None:
olditem = ""
if olditem is not None and text != olditem:
wsc_proxy_service.addUpdateOidItem(oid, text.encode('utf-8'))
except Exception, exception:
print 'Got an exception in new code'
print exception
raise exception
if service_type == lasso.PP_HREF:
pass # FIXME
# Add interaction service header
headers = wsc_proxy_service.soapEnvelopeRequest.header.other
user_interaction = lasso.IsUserInteraction()
user_interaction.id = 'userinteractid'
user_interaction.redirect = 1
user_interaction.maxInteractTime = 600
headers = headers + ( user_interaction, )
wsc_proxy_service.soapEnvelopeRequest.header.other = headers
if messageId:
self.setRefToMessageId(wsc_proxy_service.soapEnvelopeRequest, messageId)
wsc_proxy_service.buildRequestMsg()
more_headers = {}
if get_cfg('adeline', {}).get('http_auth_tuple'):
more_headers['Authorization'] = 'Basic ' + base64.encodestring(
get_cfg('adeline', {}).get('http_auth_tuple'))
body = wsc_proxy_service.msgBody
# FIXME: MSP is void !
body = body.replace('xmlns:is="urn:liberty:is:2003-08"','').replace(
'<is:UserInteraction', '<is:UserInteraction xmlns:is="urn:liberty:is:2003-08"')
soap_answer = soap_call(wsc_proxy_service.msgUrl, body + ' ',
more_headers = more_headers)
# FIXME: MSP for dummies !
soap_answer = soap_answer.replace('S:detail', 'S:Detail')
try:
wsc_proxy_service.processResponseMsg(soap_answer)
except lasso.Error, error:
if error[0] != lasso.SOAP_FAULT_REDIRECT_REQUEST or not wsc_proxy_service.msgUrl:
raise
messageId = self.getMessageId(wsc_proxy_service.soapEnvelopeResponse)
return (wsc_proxy_service.msgUrl, messageId)
if wsc_proxy_service.response.status.code == 'Ok':
return ('Ok',None)
return (None,None)
def msp_disco_query(self, session):
'''Standard discovery query with MSP service type'''
disco = lasso.Discovery(authentic.misc.get_lasso_server())
if session.lasso_proxy_session_dump:
disco.setSessionFromDump(session.lasso_proxy_session_dump)
else:
raise RedirectException('http://' + get_request().get_server().replace('-app','') + '/login_msp_for_proxy')
# XXX: else build an error response ?
# if CredentialRef is present can activate lasso.SECURITY11_MECH_TLS_SAML
try:
disco.initQuery(lasso.SECURITY11_MECH_TLS_SAML)
except lasso.ProfileMissingResourceOfferingError:
return None
disco.addRequestedServiceType(lassodgme.MSPED_HREF)
disco.buildRequestMsg()
soap_answer = soap_call(disco.msgUrl, disco.msgBody)
disco.processQueryResponseMsg(soap_answer)
return disco.getService()
# Query for an UploadFileToken, approximate date of depot is set to
# now + 10 minutes
def getUploadToken(self, identity, filename):
datedepot = isotime(600)
# 1st, get current user session
sessions = [x for x in get_session_manager().values() if x.user == identity.id]
sessions.sort(lambda x,y: cmp(x.get_creation_time(), y.get_creation_time()))
if not sessions:
return None
latest_session = sessions[-1]
# 2nd, query MSP
# Init UpdateOid request
wsc_proxy_service = self.msp_disco_query(latest_session)
wsc_proxy_service.initUploadToken(lasso.SECURITY11_MECH_TLS_SAML, 1, filename, datedepot)
wsc_proxy_service.request.teleserviceId = '1001'
wsc_proxy_service.buildRequestMsg()
more_headers = {}
if get_cfg('adeline', {}).get('http_auth_tuple'):
more_headers['Authorization'] = 'Basic ' + base64.encodestring(
get_cfg('adeline', {}).get('http_auth_tuple'))
body = wsc_proxy_service.msgBody
# FIXME: MSP is void !
soap_answer = soap_call(wsc_proxy_service.msgUrl, body + ' ',
more_headers = more_headers)
# FIXME: MSP for dummies !
soap_answer = soap_answer.replace('faultcode','S:faultcode').replace('faultstring', 'S:faultstring').replace(
'faultactor', 'S:faultactor').replace('detail', 'S:Detail')
try:
wsc_proxy_service.processResponseMsg(soap_answer)
except lasso.Error, error:
if error[0] != lasso.SOAP_FAULT_REDIRECT_REQUEST or not wsc_proxy_service.msgUrl:
raise
messageId = self.getMessageId(wsc_proxy_service.soapEnvelopeResponse)
return None
if wsc_proxy_service.response.status.code == 'Ok':
base64encoded_assertion = wsc_proxy_service.response.token
if base64encoded_assertion:
assertion = base64.b64decode(base64encoded_assertion)
return assertion
return None
import urllib
import httplib
def soap_call(url, msg, client_cert = None, more_headers = None, timeout = 3600):
print 'Entering SOAP_CALL for %s' % url
if url.startswith('http://'):
host, query = urllib.splithost(url[5:])
conn = httplib.HTTPConnection(host)
else:
host, query = urllib.splithost(url[6:])
conn = httplib.HTTPSConnection(host,
key_file = client_cert, cert_file = client_cert)
headers = {'Content-Type': 'text/xml'}
if more_headers:
headers.update(more_headers)
conn.set_debuglevel(3)
oldtimeout = socket.getdefaulttimeout()
start = time.time()
try:
try:
socket.setdefaulttimeout(timeout)
conn.request('POST', query, msg, headers)
response = conn.getresponse()
data = response.read()
print 'reponse: %s' % str(data)
if response.status not in (200, 204): # 204 ok for federation termination
get_logger().warn('SOAP error (%s) (on %s)' % (response.status, url))
raise SOAPError()
except Exception, exception:
get_logger().warn('SOAP error (%s) (on %s)' % (exception, url))
raise SOAPError(str(exception))
finally:
socket.setdefaulttimeout(oldtimeout)
conn.close()
print "Temps ecoule: %s" % str(time.time()-start)
return data
authentic.liberty.root.soap_call = soap_call
# import uuid # only in 2.5
import smtplib
try:
from M2Crypto import BIO, SMIME, X509
except ImportError:
m2crypto = False
else:
m2crypto = True
def send_smime(from_addr, to_addrs, subject, msg, from_key=None, from_cert=None, to_certs=None, smtpd='localhost'):
if not m2crypto:
return
msg_bio = BIO.MemoryBuffer(msg)
sign = from_key and from_cert
encrypt = to_certs
s = SMIME.SMIME()
if sign:
s.load_key(from_key, from_cert)
p7 = s.sign(msg_bio, flags=SMIME.PKCS7_TEXT)
msg_bio = BIO.MemoryBuffer(msg) # Recreate coz sign() has consumed it.
if encrypt:
sk = X509.X509_Stack()
for x in to_certs:
sk.push(X509.load_cert(x))
s.set_x509_stack(sk)
s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
tmp_bio = BIO.MemoryBuffer()
if sign:
s.write(tmp_bio, p7)
else:
tmp_bio.write(msg)
p7 = s.encrypt(tmp_bio)
out = BIO.MemoryBuffer()
out.write('From: %s\r\n' % from_addr)
out.write('To: %s\r\n' % ", ".join(to_addrs))
out.write('Subject: %s\r\n' % subject)
if encrypt:
s.write(out, p7)
else:
if sign:
s.write(out, p7, msg_bio, SMIME.PKCS7_TEXT)
else:
out.write('\r\n')
out.write(msg)
out.close()
smtp = smtplib.SMTP()
smtp.connect(smtpd)
smtp.sendmail(from_addr, to_addrs, out.read())
smtp.quit()
import random
import email.Encoders
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
def envoie_fichier(adeline_service, filename, filecontent, identity):
assertion = adeline_service.getUploadToken(identity, filename)
context = dict()
context['assertion'] = assertion
context['filename'] = filename
context['date'] = isotime()
context['dateperemption'] = isotime(1200)
# context['uuid'] = uuid.uuid4().hex # only in 2.5
context['uuid'] = '%x' % random.randint(0,1000000000000000000000)
template = '''<enveloppeMSP xmlns="urn://application.mon.service-public.fr/xmlns/routeur/2007/05/01">
<infoRoutageMSP>
%(assertion)s
</infoRoutageMSP>
<messageMSP>
<infoMSP>
<version>1.1</version>
<typeMessage>03</typeMessage>
<contenuMsg>Message hors demarche Cdc Num</contenuMsg>
<dtEnvoi>%(date)s</dtEnvoi>
<titreMessage>Message hors demarche avec PJ</titreMessage>
<emailTransportResponse>admin@entrouvert.com</emailTransportResponse>
<arDemande>true</arDemande>
<idTSEmetteur>1001</idTSEmetteur>
<refMessage>abcd</refMessage>
<reponseAutorisee>true</reponseAutorisee>
<isurgent>true</isurgent>
<datePeremption>%(dateperemption)s</datePeremption>
<idTSCible>0</idTSCible>
<refMessageOrigine>refmesOrigine</refMessageOrigine>
</infoMSP>
<documentMSP>
<intitule>%(filename)s</intitule>
<lbNom>%(filename)s</lbNom>
<typeDocument>1</typeDocument>
<infoEmeteur>1001</infoEmeteur>
<infoAuteur>1001</infoAuteur>
<description>%(filename)s</description>
<dateReference>%(date)s</dateReference>
<dateFinValidite>2100-12-17T09:30:47.0Z</dateFinValidite>
<lbDoc>%(uuid)s<lbDoc>
</documentMSP>
</messageMSP>
</enveloppeMSP>'''
description = MIMEText(template % context)
description.set_param('name', 'informationMSP.txt')
fichier = MIMEBase('application','octet-stream')
fichier.set_payload(filecontent)
fichier.set_param('name', filename)
email.Encoders.encode_base64(fichier)
fichier['Content-ID'] = context['uuid']
msg = MIMEMultipart()
msg.attach(description)
msg.attach(fichier)
send_smime('admin@entrouvert.com',
('tm.routeurmsp.fr@capgemini.com',),
'Uploading document from Adeline to MSP',
msg.as_string(),
'/var/lib/authentic/adelauth.servicepubliclocal.net/private-key.pem',
'/var/lib/authentic/adelauth.servicepubliclocal.net/certificate.pem',
('/var/lib/authentic/adelauth.servicepubliclocal.net/AC2_routeur_SMIME.pem',))
class MigrateDataSpace:
def __init__(self, identity, session, adeline_service, doc_keys):
self.identity = identity
self.session = session
self.adeline_service = adeline_service
self.doc_keys = doc_keys
def __call__(self):
if ED_MIGRATION_DISABLED or ED_DOCUMENTS_MIGRATION_DISABLED:
return
# 1st, ask local data space for documents
docs = []
adeline_service = self.adeline_service
for key in self.doc_keys:
doc_xpath = "/ad:PersonalDocument[ad:PersonalDocumentMetadata/@documentKey='%s']" % key
adeline_service.initQuery(doc_xpath, key)
adeline_service.buildRequestMsg()
soap_anwser = soap_call(adeline_service.msgUrl, adeline_service.msgBody)
adeline_service.processQueryResponseMsg(soap_anwser)
docs.append(adeline_service.getAnswer(doc_xpath))
# TODO: get files and metadata out of "docs"
files = []
#for file in files:
#envoie_fichier(self.adeline_service, '', '', self.identity)