55from django .core .validators import URLValidator
66from django .utils .encoding import force_str
77
8+ from .settings import oauth2_settings
9+
810
911class URIValidator (URLValidator ):
1012 scheme_re = r"^(?:[a-z][a-z0-9\.\-\+]*)://"
@@ -21,7 +23,16 @@ class URIValidator(URLValidator):
2123class AllowedURIValidator (URIValidator ):
2224 # TODO: find a way to get these associated with their form fields in place of passing name
2325 # TODO: submit PR to get `cause` included in the parent class ValidationError params`
24- def __init__ (self , schemes , name , allow_path = False , allow_query = False , allow_fragments = False ):
26+ def __init__ (
27+ self ,
28+ schemes ,
29+ name ,
30+ allow_path = False ,
31+ allow_query = False ,
32+ allow_fragments = False ,
33+ allow_hostname_wildcard = False ,
34+ allow_path_wildcard = False ,
35+ ):
2536 """
2637 :param schemes: List of allowed schemes. E.g.: ["https"]
2738 :param name: Name of the validated URI. It is required for validation message. E.g.: "Origin"
@@ -34,6 +45,7 @@ def __init__(self, schemes, name, allow_path=False, allow_query=False, allow_fra
3445 self .allow_path = allow_path
3546 self .allow_query = allow_query
3647 self .allow_fragments = allow_fragments
48+ self .allow_hostname_wildcard = allow_hostname_wildcard or True
3749
3850 def __call__ (self , value ):
3951 value = force_str (value )
@@ -68,8 +80,56 @@ def __call__(self, value):
6880 params = {"name" : self .name , "value" : value , "cause" : "path not allowed" },
6981 )
7082
83+ if (
84+ oauth2_settings .ALLOW_REDIRECT_URI_WILDCARDS
85+ and self .allow_hostname_wildcard
86+ and "*" in netloc
87+ ):
88+ domain_parts = netloc .split ("." )
89+ if netloc .count ("*" ) > 1 :
90+ raise ValidationError (
91+ "%(name)s URI validation error. %(cause)s: %(value)s" ,
92+ params = {
93+ "name" : self .name ,
94+ "value" : value ,
95+ "cause" : "only one wildcard is allowed in the hostname" ,
96+ },
97+ )
98+ if not netloc .startswith ("*" ):
99+ raise ValidationError (
100+ "%(name)s URI validation error. %(cause)s: %(value)s" ,
101+ params = {
102+ "name" : self .name ,
103+ "value" : value ,
104+ "cause" : "wildcards must be at the beginning of the hostname" ,
105+ },
106+ )
107+ if len (domain_parts ) < 3 :
108+ raise ValidationError (
109+ "%(name)s URI validation error. %(cause)s: %(value)s" ,
110+ params = {
111+ "name" : self .name ,
112+ "value" : value ,
113+ "cause" : "wildcards cannot be in the top level or second level domain" ,
114+ },
115+ )
116+
117+ # strip the wildcard from the netloc, we'll reassamble the value later to pass to URI Validator
118+ if netloc .startswith ("*." ):
119+ netloc = netloc [2 :]
120+ else :
121+ netloc = netloc [1 :]
122+
123+ # we stripped the wildcard from the netloc and path if they were allowed and present since they would
124+ # fail validation we'll reassamble the URI to pass to the URIValidator
125+ reassambled_uri = f"{ scheme } ://{ netloc } { path } "
126+ if query :
127+ reassambled_uri += f"?{ query } "
128+ if fragment :
129+ reassambled_uri += f"#{ fragment } "
130+
71131 try :
72- super ().__call__ (value )
132+ super ().__call__ (reassambled_uri )
73133 except ValidationError as e :
74134 raise ValidationError (
75135 "%(name)s URI validation error. %(cause)s: %(value)s" ,
0 commit comments