Changeset 4537
- Timestamp:
- 04/12/08 17:09:59 (12 years ago)
- Location:
- TI12-security/trunk/python
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid_provider.py
r4528 r4537 39 39 A standard message is raised set by the msg class variable but the actual 40 40 exception details are logged to the error log. The use of a standard 41 message en bales callers to use its content for user error messages.41 message enables callers to use its content for user error messages. 42 42 43 43 @type msg: basestring 44 44 @cvar msg: standard message to be raised for this exception""" 45 msg = "An error occured with login" 45 userMsg = ("An internal error occurred during login, Please contact your " 46 "system administrator") 47 errorMsg = "AuthNInterface error" 48 46 49 def __init__(self, *arg, **kw): 47 Exception.__init__(self, AuthNInterfaceError.msg, *arg, **kw)48 50 if len(arg) > 0: 49 51 msg = arg[0] 50 52 else: 51 msg = AuthNInterfaceError.msg53 msg = self.__class__.errorMsg 52 54 53 55 log.error(msg) 56 Exception.__init__(self, msg, **kw) 54 57 55 58 class AuthNInterfaceInvalidCredentials(AuthNInterfaceError): 56 59 """User has provided incorrect username/password. Raise from logon""" 57 msg = "Invalid username/password provided" 60 userMsg = ("Invalid username / password provided. Please try again. If " 61 "the problem persists please contact your system administrator") 62 errorMsg = "Invalid username/password provided" 63 64 class AuthNInterfaceUsername2IdentifierMismatch(AuthNInterfaceError): 65 """User has provided a username which doesn't match the identifier from 66 the OpenID URL that they provided. DOESN'T apply to ID Select mode where 67 the user has given a generic URL for their OpenID Provider.""" 68 userMsg = ("Invalid username for the OpenID entered. Please ensure you " 69 "have the correct OpenID and username and try again. If the " 70 "problem persists contact your system administrator") 71 errorMsg = "invalid username / OpenID identifier combination" 58 72 59 73 class AuthNInterfaceRetrieveError(AuthNInterfaceError): 60 74 """Error with retrieval of information to authenticate user e.g. error with 61 75 database look-up. Raise from logon""" 62 msg = \63 "An error occured retrieving information to check the login credentials"76 errorMsg = ("An error occurred retrieving information to check the login " 77 "credentials") 64 78 65 79 class AuthNInterfaceInitError(AuthNInterfaceError): 66 80 """Error with initialisation of AuthNInterface. Raise from __init__""" 67 msg = "An error occured with the initialisation of the OpenID " + \ 68 "Provider's Authentication Service" 81 errorMsg = "AuthNInterface initialisation error" 69 82 70 83 class AuthNInterfaceConfigError(AuthNInterfaceError): 71 84 """Error with Authentication configuration. Raise from __init__""" 72 msg = "An error occured with the OpenID Provider's Authentication " + \ 73 "Service configuration" 85 errorMsg = "AuthNInterface configuration error" 74 86 75 87 class AbstractAuthNInterface(object): … … 92 104 other specific exception types. 93 105 """ 94 95 96 def logon(self, username, password): 106 107 def logon(self, userIdentifier, username, password): 97 108 """Interface login method 98 109 110 @type userIdentifier: basestring or None 111 @param userIdentifier: OpenID user identifier - this implementation of 112 an OpenID Provider uses the suffix of the user's OpenID URL to specify 113 a unique user identifier. It ID Select mode was chosen, the identifier 114 will be None and can be ignored. In this case, the implementation of 115 the decide method in the rendering interface must match up the username 116 to a corresponding identifier in order to construct a complete OpenID 117 user URL. 118 99 119 @type username: basestring 100 @param username: user identifier 120 @param username: user identifier for authentication 101 121 102 122 @type password: basestring … … 104 124 105 125 @raise AuthNInterfaceInvalidCredentials: invalid username/password 106 @raise AuthNInterfaceError: error 126 @raise AuthNInterfaceUsername2IdentifierMismatch: username doesn't 127 match the OpenID URL provided by the user. (Doesn't apply to ID Select 128 type requests). 107 129 @raise AuthNInterfaceRetrieveError: error with retrieval of information 108 130 to authenticate user e.g. error with database look-up. … … 112 134 raise NotImplementedError(self.logon.__doc__.replace('\n ','')) 113 135 136 def username2UserIdentifiers(self, username): 137 """Map the login username to an identifier which will become the 138 unique path suffix to the user's OpenID identifier. The 139 OpenIDProviderMiddleware takes self.urls['id_url'] and adds it to this 140 identifier: 141 142 identifier = self._authN.username2UserIdentifiers(username) 143 identityURL = self.urls['url_id'] + '/' + identifier 144 145 @type username: basestring 146 @param username: user identifier 147 148 @rtype: tuple 149 @return: one or more identifiers to be used to make OpenID user 150 identity URL(s). 151 152 @raise AuthNInterfaceConfigError: problem with the configuration 153 @raise AuthNInterfaceRetrieveError: error with retrieval of information 154 to identifier e.g. error with database look-up. 155 @raise AuthNInterfaceError: generic exception not described by the 156 other specific exception types. 157 """ 158 raise NotImplementedError( 159 self.username2UserIdentifiers.__doc__.replace('\n ','')) 160 114 161 115 162 class BasicAuthNInterface(AbstractAuthNInterface): … … 131 178 """ 132 179 # Test/Admin username/password set from ini/kw args 133 userCreds = prop.get('user creds')180 userCreds = prop.get('userCreds') 134 181 if userCreds: 135 self._userCreds = dict([i.strip().split(':') \182 self._userCreds = dict([i.strip().split(':') 136 183 for i in userCreds.split(',')]) 137 184 else: 138 185 raise AuthNInterfaceConfigError('No "userCreds" config option ' 139 186 "found") 140 141 def logon(self, username, password): 187 188 user2Identifier = prop.get('username2UserIdentifiers') 189 if user2Identifier: 190 self._username2Identifier = {} 191 for i in user2Identifier.split(): 192 username, identifierStr = i.strip().split(':') 193 identifiers = tuple(identifierStr.split(',')) 194 self._username2Identifier[username] = identifiers 195 else: 196 raise AuthNInterfaceConfigError('No "user2Identifier" config ' 197 'option found') 198 199 userCredNames = self._userCreds.keys() 200 userCredNames.sort() 201 username2IdentifierNames = self._username2Identifier.keys() 202 username2IdentifierNames.sort() 203 if userCredNames != username2IdentifierNames: 204 raise AuthNInterfaceConfigError('Mismatch between usernames in ' 205 '"userCreds" and ' 206 '"username2UserIdentifiers" options') 207 208 def logon(self, userIdentifier, username, password): 142 209 """Interface login method 143 210 … … 150 217 @raise AuthNInterfaceInvalidCredentials: invalid username/password 151 218 """ 152 if self._userCreds.get(username , '') != password:219 if self._userCreds.get(username) != password: 153 220 raise AuthNInterfaceInvalidCredentials() 221 222 if userIdentifier is not None and \ 223 userIdentifier not in self._username2Identifier.get(username): 224 raise AuthNInterfaceUsername2IdentifierMismatch() 225 226 def username2UserIdentifiers(self, username): 227 """Map the login username to an identifier which will become the 228 unique path suffix to the user's OpenID identifier. The 229 OpenIDProviderMiddleware takes self.urls['id_url'] and adds it to this 230 identifier: 231 232 identifier = self._authN.username2UserIdentifiers(username) 233 identityURL = self.urls['url_id'] + '/' + identifier 234 235 @type username: basestring 236 @param username: user identifier 237 238 @rtype: tuple 239 @return: identifiers to be used to make OpenID user identity URLs. This 240 implementation only returns one 241 242 @raise AuthNInterfaceRetrieveError: error with retrieval of information 243 to identifier e.g. error with database look-up. 244 """ 245 try: 246 return (self._username2Identifier[username],) 247 except KeyError: 248 raise AuthNInterfaceRetrieveError('No entries for "%s" user' % 249 username) 154 250 155 251 … … 286 382 287 383 # Paths relative to base URL - Nb. remove trailing '/' 288 self.paths = dict([(k, opt[k].rstrip('/')) \384 self.paths = dict([(k, opt[k].rstrip('/')) 289 385 for k in OpenIDProviderMiddleware.defPaths]) 290 386 … … 295 391 296 392 # Full Paths 297 self.urls = dict([(k.replace('path_', 'url_'), self.base_url+v) \393 self.urls = dict([(k.replace('path_', 'url_'), self.base_url+v) 298 394 for k,v in self.paths.items()]) 299 395 300 self.method = dict([(v, k.replace('path_', 'do_')) \396 self.method = dict([(v, k.replace('path_', 'do_')) 301 397 for k,v in self.paths.items()]) 302 398 … … 324 420 325 421 try: 326 self._render = renderingClass(self.base_url, self.urls) 422 self._render = renderingClass(self._authN, 423 self.base_url, 424 self.urls) 327 425 except Exception, e: 328 426 log.error("Error instantiating rendering interface...") … … 421 519 422 520 elif self.path.startswith(self.paths['path_id']) or \ 423 self.path.startswith(self.paths['path_yadis']):521 self.path.startswith(self.paths['path_yadis']): 424 522 425 523 # Match against path minus ID as this is not known in advance … … 478 576 479 577 578 def do_serveryadis(self, environ, start_response): 579 """Yadis based discovery for ID Select mode i.e. no user ID given for 580 OpenID identifier at Relying Party 581 582 @type environ: dict 583 @param environ: dictionary of environment variables 584 @type start_response: callable 585 @param start_response: standard WSGI callable to set HTTP headers 586 @rtype: basestring 587 @return: WSGI response 588 589 """ 590 response = self._render.serverYadis(environ, start_response) 591 return response 592 593 480 594 def do_openidserver(self, environ, start_response): 481 """Handle OpenID Server Request for ID Select mode i.e. no user id 482 given OpenID URL at Relying Party 595 """OpenID Server endpoint - handles OpenID Request following discovery 483 596 484 597 @type environ: dict … … 563 676 else: 564 677 response = self._render.errorPage(environ, start_response, 565 'Expecting yes/no in allow '678 'Expecting Yes/No in allow ' 566 679 'post. %r' % self.query, 567 680 code=400) 568 681 569 return response570 571 def do_serveryadis(self, environ, start_response):572 """Yadis based discovery for ID Select mode i.e. no user ID given for573 OpenID identifier at Relying Party574 575 @type environ: dict576 @param environ: dictionary of environment variables577 @type start_response: callable578 @param start_response: standard WSGI callable to set HTTP headers579 @rtype: basestring580 @return: WSGI response581 582 """583 response = self._render.serverYadis(environ, start_response)584 682 return response 585 683 … … 625 723 return self._redirect(start_response,self.query['fail_to']) 626 724 725 oidRequest = self.session.get('lastCheckIDRequest') 726 if oidRequest is None: 727 log.error("Getting OpenID request for login - no request " 728 "found in session") 729 return self._render.errorPage(environ, start_response, 730 "An internal error occured " 731 "during login. Please " 732 "report the problem to your " 733 "site administrator.") 734 735 # Get user identifier to check against credentials provided 736 if oidRequest.idSelect(): 737 # ID select mode enables the user to request specifying 738 # their OpenID Provider without giving a personal user URL 739 userIdentifier = None 740 else: 741 # Get the unique user identifier from the user's OpenID URL 742 userIdentifier = oidRequest.identity.split('/')[-1] 743 627 744 # Invoke custom authentication interface plugin 628 745 try: 629 self._authN.logon(self.query['username'], 746 self._authN.logon(userIdentifier, 747 self.query['username'], 630 748 self.query.get('password', '')) 631 749 632 750 except AuthNInterfaceError, e: 633 # A known exception was raised 634 log.error("Authenticating: %s" % e) 635 msg = "<p>%s. " + \ 636 "Please try again or if the problems persists " + \ 637 "contact your system administrator.</p>" % e 638 639 response = self._render.login(environ, start_response, 640 msg=msg, 641 success_to=self.urls['url_decide']) 642 return response 643 751 return self._render.login(environ, start_response, 752 msg=e.userMsg, 753 success_to=self.urls['url_decide']) 644 754 except Exception, e: 645 755 log.error("Unexpected exception raised during " 646 756 "authentication: %s" % e) 647 msg = "<p>An internal error occured. " + \648 "Please try again or if the problems persists " + \649 "contact your system administrator.</p>" % e757 msg = ("An internal error occured. " 758 "Please try again or if the problems persists " 759 "contact your system administrator.") 650 760 651 761 response = self._render.login(environ, start_response, … … 705 815 if oidRequest is None: 706 816 log.error("No OpenID request set in session") 707 return self.do_mainpage(environ, start_response) 817 return self._render.errorPage(environ, start_response, 818 "Invalid request. Please report " 819 "the error to your site " 820 "administrator.", 821 code=400) 708 822 709 823 approvedRoots = self.session.get('approved', {}) … … 717 831 log.error("Setting response following ID Approval: %s" % e) 718 832 response = self._render.errorPage(environ, start_response, 719 'Error setting response. Please report the error to your'720 'site administrator.')721 833 'Error setting response. ' 834 'Please report the error to ' 835 'your site administrator.') 722 836 return response 723 837 724 return self. _displayResponse(response)838 return self.oidResponse(response) 725 839 else: 726 840 return self._render.decidePage(environ, start_response, oidRequest) … … 747 861 return True 748 862 749 identityURL = self.urls['url_id']+'/'+username 863 identifier = self._authN.username2UserIdentifiers(username) 864 identityURL = self.urls['url_id']+'/'+identifier 750 865 if oidRequest.identity != identityURL: 751 866 log.debug("OpenIDProviderMiddleware._identityIsAuthorized - " … … 815 930 if len(requiredAttr) > 0: 816 931 msg = ("Relying party requires these attributes: %s; but no" 817 818 932 "Attribute exchange handler 'axResponseHandler' has " 933 "been set" % requiredAttr) 819 934 log.error(msg) 820 935 raise OpenIDProviderConfigError(msg) … … 969 1084 ('Location', url)]) 970 1085 return [] 971 972 973 def _showErrorPage(self, msg, code=500):974 """Display error information to the user975 976 @type msg: basestring977 @param msg: error message978 @type code: int979 @param code: HTTP error code980 """981 982 response = self._render.errorPage(self.environ, start_response,983 cgi.escape(msg), code=code)984 return response985 1086 986 1087 … … 1040 1141 </xrds:XRDS>""" 1041 1142 1042 def __init__(self, base_url, urls, **opt): 1043 """ 1143 def __init__(self, authN, base_url, urls, **opt): 1144 """ 1145 @type authN: AuthNInterface 1146 @param param: reference to authentication interface to enable OpenID 1147 user URL construction from username 1044 1148 @type base_url: basestring 1045 1149 @param base_url: base URL for OpenID Provider to which individual paths … … 1052 1156 OpenIDProviderMiddleware config 1053 1157 """ 1158 self._authN = authN 1054 1159 self.base_url = base_url 1055 1160 self.urls = urls … … 1058 1163 1059 1164 def serverYadis(self, environ, start_response): 1060 '''Render Yadis info 1165 '''Render Yadis info for ID Select mode request 1061 1166 1062 1167 @type environ: dict … … 1092 1197 # Override this method to implement an alternate means to derive the 1093 1198 # username identifier 1094 user name= environ['PATH_INFO'].rstrip('/').split('/')[-1]1199 userIdentifier = environ['PATH_INFO'].rstrip('/').split('/')[-1] 1095 1200 1096 1201 endpoint_url = self.urls['url_openidserver'] 1097 user_url = self.urls['url_id'] + '/' + user name1202 user_url = self.urls['url_id'] + '/' + userIdentifier 1098 1203 1099 1204 yadisDict = dict(openid20type=discover.OPENID_2_0_TYPE, … … 1226 1331 """Example rendering interface class for demonstration purposes""" 1227 1332 1228 def __init__(self, base_url, urls):1229 """1230 @type base_url: basestring1231 @param base_url: base URL for OpenID Provider to which individual paths1232 are appended1233 @type urls: dict1234 @param urls: full urls for all the paths used by all the exposed1235 methods - keyed by method name - see OpenIDProviderMiddleware.paths1236 """1237 self.base_url = base_url1238 self.urls = urls1239 self.charset = ''1240 1241 1242 1333 def identityPage(self, environ, start_response): 1243 1334 """Render the identity page. … … 1252 1343 """ 1253 1344 path = environ.get('PATH_INFO').rstrip('/') 1254 user name= path.split('/')[-1]1345 userIdentifier = path.split('/')[-1] 1255 1346 1256 1347 link_tag = '<link rel="openid.server" href="%s">' % \ … … 1258 1349 1259 1350 yadis_loc_tag = '<meta http-equiv="x-xrds-location" content="%s">' % \ 1260 (self.urls['url_yadis']+'/'+user name)1351 (self.urls['url_yadis']+'/'+userIdentifier) 1261 1352 1262 1353 disco_tags = link_tag + yadis_loc_tag … … 1264 1355 1265 1356 response = self._showPage(environ, 1266 'Identity Page',1267 head_extras=disco_tags,1268 msg='<p>This is the identity page for %s.</p>' %1269 ident)1357 'Identity Page', 1358 head_extras=disco_tags, 1359 msg='<p>This is the identity page for %s.' 1360 '</p>' % ident) 1270 1361 1271 1362 start_response("200 OK", … … 1406 1497 # assert oidRequest.identity.startswith(id_url_base), \ 1407 1498 # repr((oidRequest.identity, id_url_base)) 1408 expected_user = oidRequest.identity[len(id_url_base):]1499 userIdentifier = oidRequest.identity[len(id_url_base):] 1409 1500 username = environ['beaker.session']['username'] 1410 1501 1411 1502 if oidRequest.idSelect(): # We are being asked to select an ID 1503 userIdentifier = self._authN.username2UserIdentifiers(username)[0] 1504 userOpenIDURL = id_url_base + userIdentifier 1505 1412 1506 msg = '''\ 1413 1507 <p>A site has asked for your identity. You may select an … … 1439 1533 ''' % fdata 1440 1534 1441 elif expected_user == username:1535 elif userIdentifier == username: 1442 1536 msg = '''\ 1443 1537 <p>A new site has asked to confirm your identity. If you … … 1462 1556 /><label for="remember">Remember this 1463 1557 decision</label><br /> 1464 <input type="submit" name=" yes" value="yes" />1465 <input type="submit" name=" no" value="no" />1558 <input type="submit" name="Yes" value="Yes" /> 1559 <input type="submit" name="No" value="No" /> 1466 1560 </form>''' % fdata 1467 1561 else: 1468 1562 mdata = { 1469 ' expected_user': expected_user,1563 'userIdentifier': userIdentifier, 1470 1564 'username': username, 1471 1565 } 1472 1566 msg = '''\ 1473 1567 <p>A site has asked for an identity belonging to 1474 %( expected_user)s, but you are logged in as %(username)s. To1475 log in as %( expected_user)s and approve the login oidRequest,1568 %(userIdentifier)s, but you are logged in as %(username)s. To 1569 log in as %(userIdentifier)s and approve the login oidRequest, 1476 1570 hit OK below. The "Remember this decision" checkbox 1477 1571 applies only to the trust root decision.</p>''' % mdata … … 1481 1575 'identity': oidRequest.identity, 1482 1576 'trust_root': oidRequest.trust_root, 1483 ' expected_user': expected_user,1577 'username': username, 1484 1578 } 1485 1579 form = '''\ … … 1493 1587 /><label for="remember">Remember this 1494 1588 decision</label><br /> 1495 <input type="hidden" name="login_as" value="%( expected_user)s"/>1496 <input type="submit" name=" yes" value="yes" />1497 <input type="submit" name=" no" value="no" />1589 <input type="hidden" name="login_as" value="%(username)s"/> 1590 <input type="submit" name="Yes" value="Yes" /> 1591 <input type="submit" name="No" value="No" /> 1498 1592 </form>''' % fdata 1499 1593 -
TI12-security/trunk/python/ndg.security.test/ndg/security/test/combinedservices/services.ini
r4528 r4537 308 308 [filter:OpenIDProviderFilter] 309 309 paste.filter_app_factory=ndg.security.server.wsgi.openid_provider:OpenIDProviderMiddleware 310 openid.provider.path.openidserver=/openid/ openidserver310 openid.provider.path.openidserver=/openid/endpoint 311 311 openid.provider.path.login=/openid/login 312 312 openid.provider.path.loginsubmit=/openid/loginsubmit 313 314 # Comment out next two lines and uncomment the third to disable URL based 315 # discovery and allow only Yadis based instead 313 316 openid.provider.path.id=/openid/id 314 317 openid.provider.path.yadis=/openid/yadis 318 #openid.provider.path.yadis=/id/ 319 315 320 openid.provider.path.serveryadis=/openid/serveryadis 316 321 openid.provider.path.allow=/openid/allow 317 322 openid.provider.path.decide=/openid/decide 318 openid.provider.path.mainpage=/openid 323 openid.provider.path.mainpage=/openid/ 319 324 openid.provider.session_middleware=beaker.session 320 325 openid.provider.base_url=http://localhost:8000 … … 326 331 openid.provider.axResponseHandler=ndg.security.server.pylons.container.lib.openid_provider_util:esgAXResponseHandler 327 332 openid.provider.authNInterface=ndg.security.server.wsgi.openid_provider.BasicAuthNInterface 328 openid.provider.authN.usercreds=pjk:test 333 openid.provider.authN.userCreds=pjk:test 334 openid.provider.authN.username2UserIdentifiers=pjk:PhilipKershaw,P.J.Kershaw 329 335 330 336 # Basic authentication for testing/admin - comma delimited list of
Note: See TracChangeset
for help on using the changeset viewer.