def __init__(self, identity, return_to, trust_root=None, immediate=False, assoc_handle=None): """Construct me. These parameters are assigned directly as class attributes, see my L{class documentation<CheckIDRequest>} for their descriptions. @raises MalformedReturnURL: When the C{return_to} URL is not a URL. """ self.assoc_handle = assoc_handle self.identity = identity self.return_to = return_to self.trust_root = trust_root or return_to if immediate: self.immediate = True self.mode = "checkid_immediate" else: self.immediate = False self.mode = "checkid_setup" if not TrustRoot.parse(self.return_to): raise MalformedReturnURL(None, self.return_to) if not self.trustRootValid(): raise UntrustedReturnURL(None, self.return_to, self.trust_root)
def runTest(self): tr = TrustRoot.parse(self.tr) match = tr.validateURL(self.rt) if self.match: assert match else: assert not match
def runTest(self): tr = TrustRoot.parse(self.case) if self.sanity == 'sane': assert tr.isSane(), self.case elif self.sanity == 'insane': assert not tr.isSane(), self.case else: assert tr is None, tr
def trustRootValid(self): """Is my return_to under my trust_root? @returntype: bool """ if not self.trust_root: return True tr = TrustRoot.parse(self.trust_root) if tr is None: raise MalformedTrustRoot(None, self.trust_root) return tr.validateURL(self.return_to)
def test(self): ph, pdat, mh, mdat = parseTests(trustroot_test_data) for expected_match, desc, line in getTests([1, 0], mh, mdat): tr, rt = line.split() tr = TrustRoot.parse(tr) self.assertIsNotNone(tr) match = tr.validateURL(rt) if expected_match: assert match else: assert not match
def fromOpenIDRequest(cls, openid_request): """Extract a FetchRequest from an OpenID message @param openid_request: The OpenID authentication request containing the attribute fetch request @type openid_request: C{L{openid.server.server.CheckIDRequest}} @rtype: C{L{FetchRequest}} or C{None} @returns: The FetchRequest extracted from the message or None, if the message contained no AX extension. @raises KeyError: if the AuthRequest is not consistent in its use of namespace aliases. @raises AXError: When parseExtensionArgs would raise same. @see: L{parseExtensionArgs} """ message = openid_request.message ax_args = message.getArgs(cls.ns_uri) self = cls() try: self.parseExtensionArgs(ax_args) except NotAXMessage: return None if self.update_url: # Update URL must match the openid.realm of the underlying # OpenID 2 message. realm = message.getArg(OPENID_NS, 'realm', message.getArg(OPENID_NS, 'return_to')) if not realm: raise AXError( ("Cannot validate update_url %r " + "against absent realm") % (self.update_url, )) tr = TrustRoot.parse(realm) if not tr.validateURL(self.update_url): raise AXError( "Update URL %r failed validation against realm %r" % ( self.update_url, realm, )) return self
def fromOpenIDRequest(cls, openid_request): """Extract a FetchRequest from an OpenID message @param openid_request: The OpenID authentication request containing the attribute fetch request @type openid_request: C{L{openid.server.server.CheckIDRequest}} @rtype: C{L{FetchRequest}} or C{None} @returns: The FetchRequest extracted from the message or None, if the message contained no AX extension. @raises KeyError: if the AuthRequest is not consistent in its use of namespace aliases. @raises AXError: When parseExtensionArgs would raise same. @see: L{parseExtensionArgs} """ message = openid_request.message ax_args = message.getArgs(cls.ns_uri) self = cls() try: self.parseExtensionArgs(ax_args) except NotAXMessage as err: return None if self.update_url: # Update URL must match the openid.realm of the underlying # OpenID 2 message. realm = message.getArg(OPENID_NS, 'realm', message.getArg(OPENID_NS, 'return_to')) if not realm: raise AXError( ("Cannot validate update_url %r " + "against absent realm") % (self.update_url, )) tr = TrustRoot.parse(realm) if not tr.validateURL(self.update_url): raise AXError( "Update URL %r failed validation against realm %r" % (self.update_url, realm, )) return self
def validate_trust_root(openid_request): """ Only allow OpenID requests from valid trust roots """ trusted_roots = getattr(settings, 'OPENID_PROVIDER_TRUSTED_ROOT', None) if not trusted_roots: # not using trusted roots return True # don't allow empty trust roots if (not hasattr(openid_request, 'trust_root') or not openid_request.trust_root): log.error('no trust_root') return False # ensure trust root parses cleanly (one wildcard, of form *.foo.com, etc.) trust_root = TrustRoot.parse(openid_request.trust_root) if not trust_root: log.error('invalid trust_root') return False # don't allow empty return tos if (not hasattr(openid_request, 'return_to') or not openid_request.return_to): log.error('empty return_to') return False # ensure return to is within trust root if not trust_root.validateURL(openid_request.return_to): log.error('invalid return_to') return False # check that the root matches the ones we trust if not any(r for r in trusted_roots if fnmatch.fnmatch(trust_root, r)): log.error('non-trusted root') return False return True
def fromQuery(klass, query): """Construct me from a web query. @raises ProtocolError: When not all required parameters are present in the query. @raises MalformedReturnURL: When the C{return_to} URL is not a URL. @raises UntrustedReturnURL: When the C{return_to} URL is outside the C{trust_root}. @param query: The query parameters as a dictionary with each key mapping to one value. @type query: dict @returntype: L{CheckIDRequest} """ self = klass.__new__(klass) mode = query[OPENID_PREFIX + 'mode'] if mode == "checkid_immediate": self.immediate = True self.mode = "checkid_immediate" else: self.immediate = False self.mode = "checkid_setup" required = [ 'identity', 'return_to', ] for field in required: value = query.get(OPENID_PREFIX + field) if not value: raise ProtocolError( query, text="Missing required field %s from %r" % (field, query)) setattr(self, field, value) # There's a case for making self.trust_root be a TrustRoot # here. But if TrustRoot isn't currently part of the "public" API, # I'm not sure it's worth doing. self.trust_root = query.get(OPENID_PREFIX + 'trust_root', self.return_to) self.assoc_handle = query.get(OPENID_PREFIX + 'assoc_handle') # Using TrustRoot.parse here is a bit misleading, as we're not # parsing return_to as a trust root at all. However, valid URLs # are valid trust roots, so we can use this to get an idea if it # is a valid URL. Not all trust roots are valid return_to URLs, # however (particularly ones with wildcards), so this is still a # little sketchy. if not TrustRoot.parse(self.return_to): raise MalformedReturnURL(query, self.return_to) # I first thought that checking to see if the return_to is within # the trust_root is premature here, a logic-not-decoding thing. But # it was argued that this is really part of data validation. A # request with an invalid trust_root/return_to is broken regardless of # application, right? if not self.trustRootValid(): raise UntrustedReturnURL(query, self.return_to, self.trust_root) return self
def _request_has_sane_trust_root(openid_request): """Return True if the RP's trust root looks sane.""" assert openid_request is not None, ( 'Could not find the OpenID request') trust_root = TrustRoot.parse(openid_request.trust_root) return trust_root.isSane()
def test_double_port_py3(self): # Python 3 urllib.parse complains about invalid port self.assertIsNone(TrustRoot.parse('http://*.example.com:80:90/'))
def test_double_port_py2(self): # Python 2 urlparse silently drops the ':90' port trust_root = TrustRoot.parse('http://*.example.com:80:90/') self.assertTrue(trust_root.isSane()) self.assertEqual(trust_root.buildDiscoveryURL(), 'http://www.example.com/')
def fromQuery(klass, query): """Construct me from a web query. @raises ProtocolError: When not all required parameters are present in the query. @raises MalformedReturnURL: When the C{return_to} URL is not a URL. @raises UntrustedReturnURL: When the C{return_to} URL is outside the C{trust_root}. @param query: The query parameters as a dictionary with each key mapping to one value. @type query: dict @returntype: L{CheckIDRequest} """ self = klass.__new__(klass) mode = query[OPENID_PREFIX + 'mode'] if mode == "checkid_immediate": self.immediate = True self.mode = "checkid_immediate" else: self.immediate = False self.mode = "checkid_setup" required = [ 'identity', 'return_to', ] for field in required: value = query.get(OPENID_PREFIX + field) if not value: raise ProtocolError(query, text="Missing required field %s from %r" % (field, query)) setattr(self, field, value) # There's a case for making self.trust_root be a TrustRoot # here. But if TrustRoot isn't currently part of the "public" API, # I'm not sure it's worth doing. self.trust_root = query.get(OPENID_PREFIX + 'trust_root', self.return_to) self.assoc_handle = query.get(OPENID_PREFIX + 'assoc_handle') # Using TrustRoot.parse here is a bit misleading, as we're not # parsing return_to as a trust root at all. However, valid URLs # are valid trust roots, so we can use this to get an idea if it # is a valid URL. Not all trust roots are valid return_to URLs, # however (particularly ones with wildcards), so this is still a # little sketchy. if not TrustRoot.parse(self.return_to): raise MalformedReturnURL(query, self.return_to) # I first thought that checking to see if the return_to is within # the trust_root is premature here, a logic-not-decoding thing. But # it was argued that this is really part of data validation. A # request with an invalid trust_root/return_to is broken regardless of # application, right? if not self.trustRootValid(): raise UntrustedReturnURL(query, self.return_to, self.trust_root) return self
class FetchRequest(AXMessage): """An attribute exchange 'fetch_request' message. This message is sent by a relying party when it wishes to obtain attributes about the subject of an OpenID authentication request. @ivar requested_attributes: The attributes that have been requested thus far, indexed by the type URI. @type requested_attributes: {str:AttrInfo} @ivar update_url: A URL that will accept responses for this attribute exchange request, even in the absence of the user who made this request. """ mode = 'fetch_request' def __init__(self, update_url=None): AXMessage.__init__(self) self.requested_attributes = {} self.update_url = update_url def add(self, attribute): """Add an attribute to this attribute exchange request. @param attribute: The attribute that is being requested @type attribute: C{L{AttrInfo}} @returns: None @raise KeyError: when the requested attribute is already present in this fetch request. """ if attribute.type_uri in self.requested_attributes: raise KeyError('The attribute %r has already been requested' % (attribute.type_uri, )) self.requested_attributes[attribute.type_uri] = attribute def getExtensionArgs(self): """Get the serialized form of this attribute fetch request. @returns: The fetch request message parameters @rtype: {unicode:unicode} """ aliases = NamespaceMap() required = [] if_available = [] ax_args = self._newArgs() for type_uri, attribute in self.requested_attributes.iteritems(): if attribute.alias is None: alias = aliases.add(type_uri) else: # This will raise an exception when the second # attribute with the same alias is added. I think it # would be better to complain at the time that the # attribute is added to this object so that the code # that is adding it is identified in the stack trace, # but it's more work to do so, and it won't be 100% # accurate anyway, since the attributes are # mutable. So for now, just live with the fact that # we'll learn about the error later. # # The other possible approach is to hide the error and # generate a new alias on the fly. I think that would # probably be bad. alias = aliases.addAlias(type_uri, attribute.alias) if attribute.required: required.append(alias) else: if_available.append(alias) if attribute.count != 1: ax_args['count.' + alias] = str(attribute.count) ax_args['type.' + alias] = type_uri if required: ax_args['required'] = ','.join(required) if if_available: ax_args['if_available'] = ','.join(if_available) return ax_args def getRequiredAttrs(self): """Get the type URIs for all attributes that have been marked as required. @returns: A list of the type URIs for attributes that have been marked as required. @rtype: [str] """ required = [] for type_uri, attribute in self.requested_attributes.iteritems(): if attribute.required: required.append(type_uri) return required def fromOpenIDRequest(cls, openid_request): """Extract a FetchRequest from an OpenID message @param openid_request: The OpenID authentication request containing the attribute fetch request @type openid_request: C{L{openid.server.server.CheckIDRequest}} @rtype: C{L{FetchRequest}} or C{None} @returns: The FetchRequest extracted from the message or None, if the message contained no AX extension. @raises KeyError: if the AuthRequest is not consistent in its use of namespace aliases. @raises AXError: When parseExtensionArgs would raise same. @see: L{parseExtensionArgs} """ message = openid_request.message ax_args = message.getArgs(cls.ns_uri) self = cls() try: self.parseExtensionArgs(ax_args) except NotAXMessage, err: return None if self.update_url: # Update URL must match the openid.realm of the underlying # OpenID 2 message. realm = message.getArg(OPENID_NS, 'realm', message.getArg(OPENID_NS, 'return_to')) if not realm: raise AXError( ("Cannot validate update_url %r " + "against absent realm") % (self.update_url, )) tr = TrustRoot.parse(realm) if not tr.validateURL(self.update_url): raise AXError( "Update URL %r failed validation against realm %r" % ( self.update_url, realm, )) return self