-
-
Notifications
You must be signed in to change notification settings - Fork 250
refactor: move domain check into small helper util #1000
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
25a41bf
fix: fix domain check in acl service
steveiliop56 3008ffa
feat: add domain validator pkg
steveiliop56 0c1cc98
feat: adopt domain validator in oauth controller and acls service
steveiliop56 25b434a
tests: fix validator tests
steveiliop56 db1d423
fix: review comments
steveiliop56 8f81309
fix: lowercase app name check
steveiliop56 edb2829
fix: add pkg to dockerfiles
steveiliop56 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| // Package validators provides validators for various types of data. | ||
| // | ||
| // Domain validator is a simple utility that ensures two domains are exact | ||
| // matches while ensuring that techniques used to bypass such checks do | ||
| // not impact the validation. | ||
|
|
||
| package validators | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "net" | ||
| "net/url" | ||
| "slices" | ||
| "strings" | ||
|
|
||
| "golang.org/x/net/idna" | ||
| ) | ||
|
|
||
| var ( | ||
| ErrInvalidURL = fmt.Errorf("invalid url") | ||
| ErrSchemeMismatch = fmt.Errorf("scheme mismatch") | ||
| ErrPortMismatch = fmt.Errorf("port mismatch") | ||
| ErrHostnameMismatch = fmt.Errorf("hostname mismatch") | ||
| ) | ||
|
|
||
| // DomainValidatorOptions is a set of options for DomainValidator. | ||
| type DomainValidatorOptions struct { | ||
| // Ensure domains have the same scheme. | ||
| WithScheme bool | ||
| // Ensure domains have the same port. | ||
| WithPort bool | ||
| // Specify a list of allowed schemes IF WithScheme is set to true. | ||
| // Leave empty to allow any scheme. | ||
| AllowedSchemes []string | ||
| } | ||
|
|
||
| // DomainValidator is a simple utility that ensures two domains are exact | ||
| // matches while ensuring that techniques used to bypass such checks do | ||
| // not impact the validation. | ||
| type DomainValidator struct { | ||
| opts DomainValidatorOptions | ||
| } | ||
|
|
||
| // NewDomainValidator creates a new DomainValidator. | ||
| func NewDomainValidator(opts DomainValidatorOptions) *DomainValidator { | ||
| return &DomainValidator{ | ||
| opts: opts, | ||
| } | ||
| } | ||
|
|
||
| func (v *DomainValidator) getURL(i string) (*url.URL, error) { | ||
| u, err := url.Parse(i) | ||
|
|
||
| if !v.opts.WithScheme && (err != nil || u.Host == "") { | ||
| u, err = url.Parse("tinyauth://" + i) | ||
| } | ||
|
|
||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to parse input url: %w", err) | ||
| } | ||
|
|
||
| if u.Host == "" { | ||
| return nil, ErrInvalidURL | ||
| } | ||
|
|
||
| if v.opts.WithPort && !v.opts.WithScheme && u.Port() == "" { | ||
| return nil, fmt.Errorf("port validation is enabled but port is missing in input url and schemes are not enabled") | ||
| } | ||
|
|
||
| if v.opts.WithScheme { | ||
| // Empty scheme means that we parsed the url with the tinyauth:// placeholder | ||
| if u.Scheme == "tinyauth" { | ||
| return nil, fmt.Errorf("input url is missing scheme") | ||
| } | ||
| if len(v.opts.AllowedSchemes) > 0 && !slices.Contains(v.opts.AllowedSchemes, u.Scheme) { | ||
| return nil, fmt.Errorf("scheme %s not allowed", u.Scheme) | ||
| } | ||
| } | ||
|
|
||
| return u, nil | ||
| } | ||
|
|
||
| func (v *DomainValidator) getEffectivePort(u *url.URL) (string, bool) { | ||
| if u.Port() != "" { | ||
| return u.Port(), true | ||
| } | ||
| switch u.Scheme { | ||
| case "http": | ||
| return "80", true | ||
| case "https": | ||
| return "443", true | ||
| default: | ||
| return "", false | ||
| } | ||
| } | ||
|
|
||
| func (v *DomainValidator) formatHostname(hostname string) (string, error) { | ||
| hostname = strings.ToLower(hostname) | ||
| hostname = strings.TrimSuffix(hostname, ".") | ||
| if net.ParseIP(hostname) != nil { | ||
| return "", fmt.Errorf("ip addresses are not supported") | ||
| } | ||
| hostname, err := idna.Lookup.ToASCII(hostname) | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to convert hostname to ascii: %w", err) | ||
| } | ||
| return hostname, nil | ||
| } | ||
|
|
||
| // Validate ensures that two domains are exact matches with the | ||
| // options defined in the DomainValidatorOptions. It ensures that the | ||
| // inputs are proper URLs and contain a host. It lowercases the hostnames | ||
| // and removes the trailing dot. Finally, it checks that the hostnames are | ||
| // equal unless WithScheme or WithPort is set to true where it also | ||
| // validates the scheme and port respectively. | ||
| func (v *DomainValidator) Validate(expected, actual string) error { | ||
| eu, err := v.getURL(expected) | ||
|
|
||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| au, err := v.getURL(actual) | ||
|
|
||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if v.opts.WithScheme { | ||
| if eu.Scheme != au.Scheme { | ||
| return ErrSchemeMismatch | ||
| } | ||
| } | ||
|
|
||
| if v.opts.WithPort { | ||
| eup, ok := v.getEffectivePort(eu) | ||
| if !ok { | ||
| return fmt.Errorf("failed to get effective port for url: %s", eu.String()) | ||
| } | ||
| aup, ok := v.getEffectivePort(au) | ||
| if !ok { | ||
| return fmt.Errorf("failed to get effective port for url: %s", au.String()) | ||
| } | ||
| if eup != aup { | ||
| return ErrPortMismatch | ||
| } | ||
| } | ||
|
|
||
| euf, err := v.formatHostname(eu.Hostname()) | ||
|
|
||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| auf, err := v.formatHostname(au.Hostname()) | ||
|
|
||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if euf != auf { | ||
| return ErrHostnameMismatch | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // SafeHostname uses the internal validation for domains that Validator uses | ||
| // to parse a hostname. It ensures the input URL is a valid URL, that a host | ||
| // is present and that the hostname is lowercased and without a trailing dot. | ||
| func (v *DomainValidator) SafeHostname(input string) (string, error) { | ||
| u, err := v.getURL(input) | ||
|
|
||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| return v.formatHostname(u.Hostname()) | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.