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

1048 lines
44 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 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
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))
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 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']
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'))
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'], identity.id)
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_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:
print 'erreur', error
print 'dump', wsc_proxy_service.response.dump()
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
print 'redirect'
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 mspProxyEndpoint(self):
request = get_request()
ctype = request.environ.get("CONTENT_TYPE")
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())
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, query_items)
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()
return 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)
if resource:
service.resourceData = resource
else:
return 'ERROR'
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()
return service.msgBody
except:
fp = StringIO.StringIO()
traceback.print_exc(file=fp)
msg = fp.getvalue()
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, query_items):
# 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
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)
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)
for oid in self.msp_oids:
name = self.msp_oids[oid]['name']
item = pp.findall('{%s}%s' % (adeline_urn, name))
if item:
item = item[0]
if item.text != dummy_value:
text = item.text
if text == None:
text = ""
wsc_proxy_service.addUpdateOidItem(oid, text.encode('utf-8'))
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')
print 'after replace: ', soap_answer
try:
wsc_proxy_service.processResponseMsg(soap_answer)
except lasso.Error, error:
print 'erreur sur update', error
print 'dump', wsc_proxy_service.response.dump()
if error[0] != lasso.SOAP_FAULT_REDIRECT_REQUEST or not wsc_proxy_service.msgUrl:
raise
print 'redirect'
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)
# 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)