Changeset 4155
- Timestamp:
- 01/09/08 11:39:28 (12 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid_provider.py
r4154 r4155 42 42 43 43 class OpenIDProviderMiddleware(object): 44 """WSGI Middleware to implement an OpenID Provider""" 44 """WSGI Middleware to implement an OpenID Provider 45 46 @cvar defKw: keywords to __init__ and their default values. Input 47 keywords must match these 48 @type defKw: dict 49 50 @cvar defPaths: subset of defKw. These are keyword items corresponding 51 to the URL paths to be set for the individual OpenID Provider functions 52 @type: defPaths: dict 53 54 @cvar formRespWrapperTmpl: If the response to the Relying Party is too long 55 it's rendered as form with the POST method instead of query arguments in a 56 GET 302 redirect. Wrap the form in this document to make the form submit 57 automatically without user intervention. See _displayResponse method 58 below... 59 @type formRespWrapperTmpl: basestring""" 60 61 formRespWrapperTmpl = """<html> 62 <head> 63 <script type="text/javascript"> 64 function doRedirect() 65 { 66 document.forms[0].submit(); 67 } 68 </script> 69 </head> 70 <body onLoad="doRedirect()"> 71 %s 72 </body> 73 </html>""" 45 74 46 75 defKw = dict(path_openidserver='/openidserver', … … 66 95 67 96 def __init__(self, app, app_conf=None, prefix='openid_provider.', **kw): 68 ''' 97 ''' 98 @type app: callable following WSGI interface 99 @param app: next middleware application in the chain 69 100 @type app_conf: dict 70 101 @param app_conf: PasteDeploy application configuration dictionary 102 @type prefix: basestring 103 @param prefix: prefix for OpenID Provider configuration items 104 @type kw: dict 105 @param kw: keyword dictionary - must follow format of defKw 106 class variable 71 107 ''' 72 108 … … 201 237 202 238 def __call__(self, environ, start_response): 203 239 """Standard WSGI interface. Intercepts the path if it matches any of 240 the paths set in the path_* keyword settings to the config 241 242 @type environ: dict 243 @param environ: dictionary of environment variables 244 @type start_response: callable 245 @param start_response: standard WSGI callable to set HTTP headers 246 @rtype: basestring 247 @return: WSGI response 248 """ 204 249 if not environ.has_key(self.session_middleware): 205 250 raise OpenIDProviderConfigError('The session middleware %r is not ' … … 249 294 250 295 def do_id(self, environ, start_response): 251 '''Handle ID request''' 296 '''Handle ID request 297 298 @type environ: dict 299 @param environ: dictionary of environment variables 300 @type start_response: callable 301 @param start_response: standard WSGI callable to set HTTP headers 302 @rtype: basestring 303 @return: WSGI response 304 305 ''' 252 306 response = self._renderer.renderIdentityPage(environ) 253 307 … … 259 313 260 314 def do_yadis(self, environ, start_response): 261 """Generate Yadis document""" 315 """Generate Yadis document 316 317 @type environ: dict 318 @param environ: dictionary of environment variables 319 @type start_response: callable 320 @param start_response: standard WSGI callable to set HTTP headers 321 @rtype: basestring 322 @return: WSGI response 323 324 """ 262 325 response = self._renderer.renderYadis(environ) 263 326 … … 269 332 270 333 def do_openidserver(self, environ, start_response): 271 """Handle OpenID Server Request""" 334 """Handle OpenID Server Request 335 336 @type environ: dict 337 @param environ: dictionary of environment variables 338 @type start_response: callable 339 @param start_response: standard WSGI callable to set HTTP headers 340 @rtype: basestring 341 @return: WSGI response 342 343 """ 272 344 273 345 try: … … 294 366 def do_allow(self, environ, start_response): 295 367 """Handle allow request - user allow credentials to be passed back to 296 the Relying Party?""" 368 the Relying Party? 369 370 @type environ: dict 371 @param environ: dictionary of environment variables 372 @type start_response: callable 373 @param start_response: standard WSGI callable to set HTTP headers 374 @rtype: basestring 375 @return: WSGI response 376 377 """ 297 378 298 379 oidRequest = self.session.get('lastCheckIDRequest') … … 346 427 347 428 def do_serveryadis(self, environ, start_response): 348 """Handle Server Yadis call""" 429 """Handle Server Yadis call 430 431 @type environ: dict 432 @param environ: dictionary of environment variables 433 @type start_response: callable 434 @param start_response: standard WSGI callable to set HTTP headers 435 @rtype: basestring 436 @return: WSGI response 437 438 """ 349 439 response = self._renderer.renderServerYadis(environ) 350 440 start_response("200 OK", … … 355 445 356 446 def do_login(self, environ, start_response, **kw): 357 """Display Login form""" 447 """Display Login form 448 449 @type environ: dict 450 @param environ: dictionary of environment variables 451 @type start_response: callable 452 @param start_response: standard WSGI callable to set HTTP headers 453 @type kw: dict 454 @param kw: keywords to login renderer - see RenderingInterface class 455 @rtype: basestring 456 @return: WSGI response 457 """ 358 458 359 459 if 'fail_to' not in kw: … … 368 468 369 469 def do_loginsubmit(self, environ, start_response): 370 """Handle user submission from login and logout""" 470 """Handle user submission from login and logout 471 472 @type environ: dict 473 @param environ: dictionary of environment variables 474 @type start_response: callable 475 @param start_response: standard WSGI callable to set HTTP headers 476 @rtype: basestring 477 @return: WSGI response 478 """ 371 479 372 480 if 'submit' in self.query: … … 421 529 422 530 def do_mainpage(self, environ, start_response): 423 531 '''Show an information page about the OpenID Provider 532 533 @type environ: dict 534 @param environ: dictionary of environment variables 535 @type start_response: callable 536 @param start_response: standard WSGI callable to set HTTP headers 537 @rtype: basestring 538 @return: WSGI response 539 ''' 540 424 541 response = self._renderer.renderMainPage(environ) 425 542 start_response('200 OK', … … 431 548 def do_decide(self, environ, start_response): 432 549 """Display page prompting the user to decide whether to trust the site 433 requesting their credentials""" 550 requesting their credentials 551 552 @type environ: dict 553 @param environ: dictionary of environment variables 554 @type start_response: callable 555 @param start_response: standard WSGI callable to set HTTP headers 556 @rtype: basestring 557 @return: WSGI response 558 """ 434 559 435 560 oidRequest = self.session.get('lastCheckIDRequest') … … 454 579 455 580 def _identityIsAuthorized(self, oidRequest): 456 '''The given identity URL matches with a logged in user''' 457 581 '''Check that a user is authorized i.e. does a session exist for their 582 username and if so does it correspond to the identity URL provided. 583 This last check doesn't apply for ID Select mode where no ID was input 584 at the Relying Party. 585 586 @type oidRequest: openid.server.server.CheckIDRequest 587 @param oidRequest: OpenID Request object 588 @rtype: bool 589 @return: True/False is user authorized 590 ''' 458 591 username = self.session.get('username') 459 592 if username is None: … … 478 611 479 612 def _trustRootIsAuthorized(self, trust_root): 480 '''The given trust root (Relying Party) has previously been approved 481 by the user''' 613 '''Return True/False for the given trust root (Relying Party) 614 previously been approved by the user 615 616 @type trust_root: dict 617 @param trust_root: keyed by trusted root (Relying Party) URL and 618 containing string item 'always' if approved 619 @rtype: bool 620 @return: True - trust has already been approved, False - trust root is 621 not approved''' 482 622 approvedRoots = self.session.get('approved', {}) 483 623 return approvedRoots.get(trust_root) is not None 484 624 485 625 486 def _addSRegResponse(self, request, response): 487 '''Add Simple Registration attributes to response to Relying Party''' 626 def _addSRegResponse(self, oidRequest, oidResponse): 627 '''Add Simple Registration attributes to response to Relying Party 628 629 @type oidRequest: openid.server.server.CheckIDRequest 630 @param oidRequest: OpenID Check ID Request object 631 @type oidResponse: openid.server.server.OpenIDResponse 632 @param oidResponse: OpenID response object''' 633 488 634 if self.sregResponseHandler is None: 635 # No Simple Registration response object was set 489 636 return 490 637 491 sreg_req = sreg.SRegRequest.fromOpenIDRequest( request)638 sreg_req = sreg.SRegRequest.fromOpenIDRequest(oidRequest) 492 639 493 640 # Callout to external callable sets additional user attributes to be … … 495 642 sreg_data = self.sregResponseHandler(self.session.get('username')) 496 643 sreg_resp = sreg.SRegResponse.extractResponse(sreg_req, sreg_data) 497 response.addExtension(sreg_resp)498 499 500 def _addAXResponse(self, request, response):644 oidResponse.addExtension(sreg_resp) 645 646 647 def _addAXResponse(self, oidRequest, oidResponse): 501 648 '''Add attributes to response based on the OpenID Attribute Exchange 502 interface''' 503 504 ax_req = ax.FetchRequest.fromOpenIDRequest(request) 649 interface 650 651 @type oidRequest: openid.server.server.CheckIDRequest 652 @param oidRequest: OpenID Check ID Request object 653 @type oidResponse: openid.server.server.OpenIDResponse 654 @param oidResponse: OpenID response object''' 655 656 657 ax_req = ax.FetchRequest.fromOpenIDRequest(oidRequest) 505 658 if ax_req is None: 506 659 log.debug("No Attribute Exchange extension set in request") … … 520 673 # release of attributes + assignment based on required attributes - 521 674 # possibly via FetchRequest.getRequiredAttrs() 522 # for typeURI, attrInfo in ax_req.requested_attributes.items():523 # # Value input must be list type524 # ax_resp.setValues(typeURI, [attrInfo.alias+"Value"])525 675 self.axResponseHandler(ax_req, ax_resp, self.session.get('username')) 526 676 527 response.addExtension(ax_resp) 528 529 530 def _identityApproved(self, request, identifier=None): 531 '''Action following approval of a Relying Party by the user''' 532 response = request.answer(True, identity=identifier) 533 self._addSRegResponse(request, response) 534 self._addAXResponse(request, response) 535 return response 677 oidResponse.addExtension(ax_resp) 678 679 680 def _identityApproved(self, oidRequest, identifier=None): 681 '''Action following approval of a Relying Party by the user. Add 682 Simple Registration and/or Attribute Exchange parameters if handlers 683 were specified - See _addSRegResponse and _addAXResponse methods 684 685 @type oidRequest: openid.server.server.CheckIDRequest 686 @param oidRequest: OpenID Check ID Request object 687 @type identifier: basestring 688 @param identifier: OpenID selected by user - for ID Select mode only 689 @rtype oidResponse: openid.server.server.OpenIDResponse 690 @return oidResponse: OpenID response object''' 691 692 oidResponse = oidRequest.answer(True, identity=identifier) 693 self._addSRegResponse(oidRequest, oidResponse) 694 self._addAXResponse(oidRequest, oidResponse) 695 696 return oidResponse 536 697 537 698 538 699 def _handleCheckIDRequest(self, oidRequest): 539 700 """Handle "checkid_immediate" and "checkid_setup" type requests from 701 Relying Party 702 703 @type oidRequest: openid.server.server.CheckIDRequest 704 @param oidRequest: OpenID Check ID request 705 @rtype: basestring 706 @return: WSGI response 707 """ 540 708 log.debug("OpenIDProviderMiddleware._handleCheckIDRequest ...") 541 709 … … 575 743 return response 576 744 577 # If the response to the Relying Party is too long it's rendered as form578 # with the POST method instead of query arguments in a GET 302 redirect.579 # Wrap the form in this document to make the form submit automatically580 # without user intervention. See _displayResponse method below...581 formRespWrapperTmpl = """<html>582 <head>583 <script type="text/javascript">584 function doRedirect()585 {586 document.forms[0].submit();587 }588 </script>589 </head>590 <body onLoad="doRedirect()">591 %s592 </body>593 </html>"""594 745 595 746 def _displayResponse(self, oidResponse): 747 """Serialize an OpenID Response object, set headers and return WSGI 748 response. 749 750 If the URL length for a GET request exceeds a maximum, then convert the 751 response into a HTML form and use POST method. 752 753 @type oidResponse: openid.server.server.OpenIDResponse 754 @param oidResponse: OpenID response object 755 756 @rtype: basestring 757 @return: WSGI response''' 758 """ 759 596 760 try: 597 761 webresponse = self.oidserver.encodeResponse(oidResponse) … … 622 786 623 787 def _redirect(self, start_response, url): 788 """Do a HTTP 302 redirect 789 790 @type start_response: callable following WSGI start_response convention 791 @param start_response: WSGI start response callable 792 @type url: basestring 793 @param url: URL to redirect to 794 @rtype: list 795 @return: empty HTML body 796 """ 624 797 start_response('302 %s' % httplib.responses[302], 625 798 [('Content-type', 'text/html'+self.charset), … … 629 802 630 803 def _showErrorPage(self, msg, code=500): 804 """Display error information to the user 805 806 @type msg: basestring 807 @param msg: error message 808 @type code: int 809 @param code: HTTP error code 810 """ 811 631 812 response = self._renderer.renderErrorPage(self.environ,cgi.escape(msg)) 632 813 self.start_response('%d %s' % (code, httplib.responses[code]), … … 640 821 derivative from this class to override the default look and feel and 641 822 behaviour of these pages. Pass the new class name via the renderClass 642 keyword to OpenIDProviderMiddleware.__init__""" 643 823 keyword to OpenIDProviderMiddleware.__init__ 824 825 @cvar tmplServerYadis: template for returning Yadis document to Relying 826 Party. Derived classes can reset this or completely override the 827 renderServerYadis method. 828 829 @type tmplServerYadis: basestring 830 831 @cvar tmplYadis: template for returning Yadis document containing user 832 URL to Relying Party. Derived classes can reset this or completely 833 override the renderYadis method. 834 835 @type tmplYadis: basestring""" 836 837 tmplServerYadis = """\ 838 <?xml version="1.0" encoding="UTF-8"?> 839 <xrds:XRDS 840 xmlns:xrds="xri://$xrds" 841 xmlns="xri://$xrd*($v*2.0)"> 842 <XRD> 843 844 <Service priority="0"> 845 <Type>%(openid20type)s</Type> 846 <URI>%(endpoint_url)s</URI> 847 </Service> 848 849 </XRD> 850 </xrds:XRDS> 851 """ 852 853 tmplYadis = """\ 854 <?xml version="1.0" encoding="UTF-8"?> 855 <xrds:XRDS 856 xmlns:xrds="xri://$xrds" 857 xmlns="xri://$xrd*($v*2.0)"> 858 <XRD> 859 860 <Service priority="0"> 861 <Type>%(openid20type)s</Type> 862 <Type>%(openid10type)s</Type> 863 <URI>%(endpoint_url)s</URI> 864 <LocalID>%(user_url)s</LocalID> 865 </Service> 866 867 </XRD> 868 </xrds:XRDS>""" 869 644 870 def __init__(self, base_url, urls): 871 """ 872 @type base_url: basestring 873 @param base_url: base URL for OpenID Provider to which individual paths 874 are appended 875 @type urls: dict 876 @param urls: full urls for all the paths used by all the exposed 877 methods - keyed by method name - see OpenIDProviderMiddleware.paths 878 """ 645 879 self.base_url = base_url 646 880 self.urls = urls 647 881 882 648 883 def renderIdentityPage(self, environ): 649 """Render the identity page.""" 884 """Render the identity page. 885 886 @type environ: dict 887 @param environ: dictionary of environment variables 888 @rtype: basestring 889 @return: WSGI response 890 """ 650 891 path = environ.get('PATH_INFO').rstrip('/') 651 892 username = path[len(self.paths['path_id'])+1:] … … 654 895 self.urls['url_openidserver'] 655 896 656 yadis_loc_tag = '<meta http-equiv="x-xrds-location" content="%s">' %\897 yadis_loc_tag = '<meta http-equiv="x-xrds-location" content="%s">' % \ 657 898 (self.urls['url_yadis']+'/'+path[4:]) 658 899 … … 668 909 ''' % (ident, msg)) 669 910 670 tmplServerYadis = """\ 671 <?xml version="1.0" encoding="UTF-8"?> 672 <xrds:XRDS 673 xmlns:xrds="xri://$xrds" 674 xmlns="xri://$xrd*($v*2.0)"> 675 <XRD> 676 677 <Service priority="0"> 678 <Type>%(openid20type)s</Type> 679 <URI>%(endpoint_url)s</URI> 680 </Service> 681 682 </XRD> 683 </xrds:XRDS> 684 """ 685 911 686 912 def renderServerYadis(self, environ): 687 '''Render Yadis info''' 913 '''Render Yadis info 914 915 @type environ: dict 916 @param environ: dictionary of environment variables 917 @rtype: basestring 918 @return: WSGI response 919 ''' 688 920 endpoint_url = self.urls['url_openidserver'] 689 921 return RenderingInterface.tmplServerYadis % \ 690 922 {'openid20type': discover.OPENID_IDP_2_0_TYPE, 691 'endpoint_url': endpoint_url} 692 693 tmplYadis = """\ 694 <?xml version="1.0" encoding="UTF-8"?> 695 <xrds:XRDS 696 xmlns:xrds="xri://$xrds" 697 xmlns="xri://$xrd*($v*2.0)"> 698 <XRD> 699 700 <Service priority="0"> 701 <Type>%(openid20type)s</Type> 702 <Type>%(openid10type)s</Type> 703 <URI>%(endpoint_url)s</URI> 704 <LocalID>%(user_url)s</LocalID> 705 </Service> 706 707 </XRD> 708 </xrds:XRDS>""" 709 710 923 'endpoint_url': endpoint_url} 924 925 711 926 def renderYadis(self, environ): 712 """Render Yadis document""" 927 """Render Yadis document containing user URL 928 929 @type environ: dict 930 @param environ: dictionary of environment variables 931 @rtype: basestring 932 @return: WSGI response 933 """ 934 713 935 username = environ['PATH_INFO'].rstrip('/').split('/')[-1] 714 936 … … 725 947 726 948 def renderLogin(self, environ, success_to=None, fail_to=None, msg=''): 727 """Render the login form.""" 949 """Render the login form. 950 951 @type environ: dict 952 @param environ: dictionary of environment variables 953 @type success_to: basestring 954 @param success_to: URL put into hidden field telling 955 OpenIDProviderMiddleware.do_loginsubmit() where to forward to on 956 successful login 957 @type fail_to: basestring 958 @param fail_to: URL put into hidden field telling 959 OpenIDProviderMiddleware.do_loginsubmit() where to forward to on 960 login error 961 @type msg: basestring 962 @param msg: display (error) message below login form e.g. following 963 previous failed login attempt. 964 @rtype: basestring 965 @return: WSGI response 966 """ 728 967 729 968 if success_to is None: … … 748 987 749 988 def renderMainPage(self, environ): 750 """Rendering the main page.""" 989 """Rendering the main page. 990 991 @type environ: dict 992 @param environ: dictionary of environment variables 993 @rtype: basestring 994 @return: WSGI response 995 """ 751 996 752 997 yadis_tag = '<meta http-equiv="x-xrds-location" content="%s">' % \ … … 774 1019 775 1020 def renderDecidePage(self, environ, oidRequest): 1021 """Show page giving the user the option to approve the return of their 1022 credentials to the Relying Party. This page is also displayed for 1023 ID select mode if the user is already logged in at the OpenID Provider. 1024 This enables them to confirm the OpenID to be sent back to the 1025 Relying Party 1026 1027 @type environ: dict 1028 @param environ: dictionary of environment variables 1029 @type oidRequest: openid.server.server.CheckIDRequest 1030 @param oidRequest: OpenID Check ID Request object 1031 @rtype: basestring 1032 @return: WSGI response 1033 """ 776 1034 id_url_base = self.urls['url_id'] + '/' 777 1035 … … 886 1144 def _showPage(self, environ, 887 1145 title, head_extras='', msg=None, err=None, form=None): 888 1146 """Generic page rendering method. Derived classes may ignore this. 1147 1148 @type environ: dict 1149 @param environ: dictionary of environment variables 1150 @type title: basestring 1151 @param title: page title 1152 @type head_extras: basestring 1153 @param head_extras: add extra HTML header elements 1154 @type msg: basestring 1155 @param msg: optional message for page body 1156 @type err: basestring 1157 @param err: optional error message for page body 1158 @type form: basestring 1159 @param form: optional form for page body 1160 @rtype: basestring 1161 @return: WSGI response 1162 """ 1163 889 1164 username = environ['beaker.session'].get('username') 890 1165 if username is None: … … 1008 1283 1009 1284 def renderErrorPage(self, environ, msg): 1285 """Display error page 1286 1287 @type environ: dict 1288 @param environ: dictionary of environment variables 1289 @type msg: basestring 1290 @param msg: optional message for page body 1291 @rtype: basestring 1292 @return: WSGI response 1293 """ 1294 1010 1295 response = self._showPage(environ, 'Error Processing Request', err='''\ 1011 1296 <p>%s</p>
Note: See TracChangeset
for help on using the changeset viewer.