Project

General

Profile

0001-idp-saml2-start-federation-termination-handling.patch

Benjamin Dauvergne, 14 Aug 2014 02:40 PM

Download (11 KB)

View differences:

Subject: [PATCH] [idp/saml2] start federation termination handling

 authentic2/idp/saml/backend.py         |    5 +
 authentic2/idp/saml/saml2_endpoints.py |  210 +++++++++++++++++++++++++++++++-
 authentic2/idp/saml/urls.py            |    2 +
 3 files changed, 216 insertions(+), 1 deletion(-)
authentic2/idp/saml/backend.py
36 36
                    actions.append(('logout', 'POST',
37 37
                        '/idp/%s/idp_slo/' % protocol,
38 38
                        (( 'provider_id', entity_id ),)))
39
                if models.LibertyFederation.objects.filter(
40
                        user=request.user, sp_id=entity_id).exists():
41
                    actions.append(('defederate', 'POST',
42
                        '/idp/%s/name_id_management/idp/' % protocol,
43
                        (( 'provider_id', entity_id),('terminate','1'))))
39 44
                if actions:
40 45
                    ls.append(Service(url=None, name=liberty_provider.name,
41 46
                        actions=actions))
authentic2/idp/saml/saml2_endpoints.py
92 92
        (saml2utils.Saml2Metadata.SINGLE_LOGOUT_SERVICE,
93 93
            soap_bindings, '/slo/soap'),
94 94
        (saml2utils.Saml2Metadata.ARTIFACT_RESOLUTION_SERVICE,
95
            lasso.SAML2_METADATA_BINDING_SOAP, '/artifact')
95
            lasso.SAML2_METADATA_BINDING_SOAP, '/artifact'),
96
        (saml2utils.Saml2Metadata.MANAGE_NAME_ID_SERVICE,
97
            soap_bindings+asynchronous_bindings, '/name_id_management'),
96 98
)
97 99
metadata_options = {'key': settings.SAML_SIGNATURE_PUBLIC_KEY}
98 100

  
......
1641 1643
        logger.warning('failure, expected: %r got: %r ' \
1642 1644
            % (destination, req_or_res.destination))
1643 1645
    return result
1646

  
1647
class NoLogger(object):
1648
    def error(self, *args, **kwargs):
1649
        pass
1650
    def warning(self, *args, **kwargs):
1651
        pass
1652
    def info(self, *args, **kwargs):
1653
        pass
1654
    def critical(self, *args, **kwargs):
1655
        pass
1656
    def debug(self, *args, **kwargs):
1657
        pass
1658

  
1659
class RequestProcessing(object):
1660
    def __init__(self, request, profile_name, request_method, logger):
1661
        self.profile_name = profile_name
1662
        self.request_method = request_method
1663
        self.logger = logger or NoLogger()
1664
        self.id = 'request-id-unknown'
1665

  
1666
    def log(self, fn, message, args, kwargs):
1667
        fn('[%s] %s: %s' % (self.id, self.profile_name, message), *args, **kwargs)
1668

  
1669
    def info(self, *args, **kwargs):
1670
        self.log(self.logger.info, args[0], args[1:], kwargs)
1671

  
1672
    def debug(self, *args, **kwargs):
1673
        self.log(self.logger.debug, args[0], args[1:], kwargs)
1674

  
1675
    def error(self, *args, **kwargs):
1676
        self.log(self.logger.error, args[0], args[1:], kwargs)
1677

  
1678
    def load_provider(self):
1679
        remote_provider_id = self.profile.remoteProviderId
1680
        self.info('loading provider %s', remote_provider_id)
1681
        self.provider = load_provider(self.request, remote_provider_id, server=self.server)
1682
        if not self.provider:
1683
            self.error('unknown provider')
1684
            return False
1685
        return True
1686

  
1687
    def error_response(self, code, requester=True):
1688
        # build an error response, and return it
1689
        self.info('returning status code %s', code)
1690
        return 'not done'
1691

  
1692
    def set_id(self):
1693
        if self.profile.request and self.profile.request.id:
1694
            self.id = '[%s]' % self.profile.request.id
1695

  
1696
    def call_process_request(self, provider_loaded=False):
1697
        try:
1698
            getattr(profile, request_method)(message)
1699
        except (lasso.ServerProviderNotFoundError, lasso.ProfileUnknownProviderError):
1700
            self.set_id()
1701
            if not provider_loaded:
1702
                if self.load_provider():
1703
                    return self.call_process_request(self, True)
1704
            return self.error_response(AUTHENTIC_STATUS_CODE_UNKNOWN_PROVIDER)
1705
        except lasso.DsError:
1706
            self.set_id()
1707
            self.error('signature error', self.id)
1708
            return self.error_response(lasso.LIB_STATUS_CODE_INVALID_SIGNATURE)
1709
        except lasso.Error, e:
1710
            self.set_id()
1711
            self.error('lasso error on processing: %s', lasso.strError(e.code))
1712
            return self.error_response(None)
1713
        self.set_id()
1714
        self.info('request processed')
1715
        return None
1716

  
1717
    def __call__(self, request):
1718
        self.request = request
1719
        self.logger.info('start processing request for %s', self.profile_name)
1720
        self.server = create_server(self.request)
1721
        self.profile = getattr(lasso, object_name)(server)
1722
        if request.method == 'POST':
1723
            # POST
1724
            if lasso.SAML2_FIELD_REQUEST in request.POST:
1725
                message = request.POST[lasso.SAML2_FIELD_REQUEST]
1726
                self.method = lasso.HTTP_METHOD_POST
1727
            else: # SOAP
1728
                message = get_soap_message(request)
1729
                self.method = lasso.HTTP_METHOD_SOAP
1730
        elif request.method == 'GET':
1731
            message = request.META['QUERY_STRING']
1732
            self.method = lasso.HTTP_METHOD_REDIRECT
1733
        self.call_process_request()
1734

  
1735
    def validate(self, request):
1736
        try:
1737
            self.profile.validate_request()
1738
        except lasso.Error, e:
1739
            self.error('lasso error on validation: %s', lasso.strError(e.code))
1740
            return self.error_response(None)
1741
        return None
1742

  
1743
def name_id_management(request):
1744
    '''Handle samlp2:NameIDManagementRequest messages'''
1745
    request_process = RequestProcessing('NameIdManagement', 'process_request_msg')
1746
    premature_response = request_process(request)
1747
    if premature_response:
1748
        return premature_response
1749
    load_federation(request, request_process.profile)
1750
    premature_response = request_process.validate(request)
1751
    if premature_response:
1752
        return premature_response
1753
    save_federation(request, request_process.profile)
1754
    if request_process.profile.request.Terminate:
1755
        request_process.info('request to terminate federation')
1756
        # search all federation of current user with provider, and erase them
1757
        q = LibertyFederation.objects.filter(
1758
            sp_id=request_process.provider.entity_id)
1759
        count = q.count()
1760
        q.delete()
1761
        if count:
1762
            request_process.info('deleted %d federation', count)
1763
        else:
1764
            request_process.info('no federation found to delete')
1765
    else:
1766
        True
1767

  
1768
def name_id_management_idp_response(request, profile=None, next_url=None,
1769
        response=None):
1770
    if not profile:
1771
        next_url, provider
1772

  
1773
def name_id_management_idp(request):
1774
    '''Initiate a samlp2:NameIDManagementRequest, some parameters can be passed
1775
       to the method:
1776
         - provider_id: the entity ID of the targetted provider (required)
1777
         - terminate: if present means that we want to terminate the current
1778
           federation with the provider.
1779
         - new_name_id: if terminate is absent, give the name of the new
1780
           federation to create with the provider.
1781
         - http_method: an integer giving the http method to use (see Lasso for
1782
           the integer values) (optional). If absent the first declared
1783
           http_method in the metadata file is used.
1784
         - next_url: a return URL, default from settings used if absent
1785
           (optional)
1786
    '''
1787
    if 'SAMLResponse' in request.GET or 'SAMLResponse' in request.POST:
1788
        return name_id_management_idp_response(request,
1789
                response=request.POST.get('SAMLResponse') or
1790
                    request.META['QUERY_STRING'])
1791
    elif request.method != 'POST':
1792
        return HttpResponseBadRequest('Only the POST method is supported')
1793
    try:
1794
        provider_id = request.POST['provider_id']
1795
    except KeyError:
1796
        return HttpResponseBadRequest('Missing provider_id parameter')
1797
    server = create_server(request)
1798
    provider = load_provider(request, provider_id, server=server)
1799
    if not provider:
1800
        return HttpResponseBadRequest('Unknown provider')
1801
    http_method = server.getProvider(provider_id) \
1802
        .getFirstHttpMethod(server, lasso.MD_PROTOCOL_TYPE_MANAGE_NAME_ID)
1803
    if not http_method:
1804
        return HttpResponseBadRequest('No HTTP method available')
1805
    next_url = request.POST.get('next_url', settings.LOGIN_REDIRECT_URL)
1806
    if 'http_method' in request.POST:
1807
        try:
1808
            passed_http_method = int(request.POST['http_method'])
1809
            if lasso.HTTP_METHOD_NONE < passed_http_method < lasso.HTTP_METHOD_LAST:
1810
                http_method = passed_http_method
1811
        except ValueError:
1812
            pass
1813
    terminate = 'terminate' in request.POST
1814
    if not terminate:
1815
        if 'new_name_id' not in request.POST:
1816
            return HttpResponseBadRequest('Missing new_name_id parameter')
1817
        new_name_id = request.POST['new_name_id']
1818
    else:
1819
        new_name_id = None
1820
    profile = lasso.NameIdManagement(server)
1821
    load_federation(request, profile)
1822
    try:
1823
        profile.initRequest(provider_id, new_name_id, http_method)
1824
    except lasso.Error, e:
1825
        raise
1826
    if profile.msgBody and profile.msgUrl: # SOAP
1827
        try:
1828
            soap_response = send_soap_request
1829
        except Exception, e:
1830
            raise
1831
        return name_id_management_idp(request, response=soap_response,
1832
                profile=profile, next_url=next_url)
1833
    else:
1834
        save_key_values(profile.request.id, next_url, provider_id)
1835
        return return_saml2_request(request, provider_id, profile)
1836

  
1837

  
1838
urlpatterns = patterns('',
1839
    (r'^metadata$', metadata),
1840
    (r'^sso$', sso),
1841
    (r'^continue$', continue_sso),
1842
    (r'^slo$', slo),
1843
    (r'^slo/soap$', slo_soap),
1844
    (r'^idp_slo/(.*)$', idp_slo),
1845
    (r'^slo_return$', slo_return),
1846
    (r'^finish_slo$', finish_slo),
1847
    (r'^artifact$', artifact),
1848
    (r'^idp_sso/(.*)$', idp_sso),
1849
    (r'^idp_sso/([^/]*)/([^/]*)$', idp_sso),
1850
    (r'^idp_sso/([^/]*)/([^/]*)/([^/]*)$', idp_sso),
1851
)
authentic2/idp/saml/urls.py
13 13
    url(r'^idp_sso/(.*)$', 'idp_sso'),
14 14
    url(r'^idp_sso/([^/]*)/([^/]*)$', 'idp_sso'),
15 15
    url(r'^idp_sso/([^/]*)/([^/]*)/([^/]*)$', 'idp_sso'),
16
    url(r'^name_id_management', 'name_id_management'),
17
    url(r'^name_id_management/idp', 'name_id_management_idp'),
16 18
)
17
-