5
5
from django .core .validators import URLValidator
6
6
from django .utils .encoding import force_str
7
7
8
+ from .settings import oauth2_settings
9
+
8
10
9
11
class URIValidator (URLValidator ):
10
12
scheme_re = r"^(?:[a-z][a-z0-9\.\-\+]*)://"
@@ -21,7 +23,16 @@ class URIValidator(URLValidator):
21
23
class AllowedURIValidator (URIValidator ):
22
24
# TODO: find a way to get these associated with their form fields in place of passing name
23
25
# 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
+ ):
25
36
"""
26
37
:param schemes: List of allowed schemes. E.g.: ["https"]
27
38
: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
34
45
self .allow_path = allow_path
35
46
self .allow_query = allow_query
36
47
self .allow_fragments = allow_fragments
48
+ self .allow_hostname_wildcard = allow_hostname_wildcard or True
37
49
38
50
def __call__ (self , value ):
39
51
value = force_str (value )
@@ -68,8 +80,56 @@ def __call__(self, value):
68
80
params = {"name" : self .name , "value" : value , "cause" : "path not allowed" },
69
81
)
70
82
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
+
71
131
try :
72
- super ().__call__ (value )
132
+ super ().__call__ (reassambled_uri )
73
133
except ValidationError as e :
74
134
raise ValidationError (
75
135
"%(name)s URI validation error. %(cause)s: %(value)s" ,
0 commit comments