From 2b4c8a984669605c4b3b4db4874e832078b23e48 Mon Sep 17 00:00:00 2001 From: Valentin Pichard Date: Wed, 28 Jul 2021 10:12:00 +0200 Subject: [PATCH] Add the allowed_email_domains and the allowed_groups on the auth_request endpoint + support standard wildcard char for validation with sub-domain and email-domain. Signed-off-by: Valentin Pichard --- CHANGELOG.md | 1 + docs/docs/configuration/overview.md | 4 +- docs/docs/features/endpoints.md | 8 +++ oauthproxy.go | 80 +++++++++++++++++-------- oauthproxy_test.go | 91 +++++++++++++++++++++++++++++ pkg/apis/options/options.go | 2 +- pkg/app/redirect/validator.go | 62 ++------------------ pkg/app/redirect/validator_test.go | 22 ++++++- pkg/util/util.go | 83 ++++++++++++++++++++++++++ validator.go | 7 ++- validator_test.go | 35 +++++++++++ 11 files changed, 305 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 085616e..5f66a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - [#1509](https://github.com/oauth2-proxy/oauth2-proxy/pull/1509) Update LoginGovProvider ValidateSession to pass access_token in Header (@pksheldon4) - [#1474](https://github.com/oauth2-proxy/oauth2-proxy/pull/1474) Support configuration of minimal acceptable TLS version (@polarctos) - [#1545](https://github.com/oauth2-proxy/oauth2-proxy/pull/1545) Fix issue with query string allowed group panic on skip methods (@andytson) +- [#1286](https://github.com/oauth2-proxy/oauth2-proxy/pull/1286) Add the `allowed_email_domains` and the `allowed_groups` on the `auth_request` + support standard wildcard char for validation with sub-domain and email-domain. (@w3st3ry @armandpicard) # V7.2.1 diff --git a/docs/docs/configuration/overview.md b/docs/docs/configuration/overview.md index 1b44011..89c472d 100644 --- a/docs/docs/configuration/overview.md +++ b/docs/docs/configuration/overview.md @@ -197,12 +197,12 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/ | `--allowed-role` | string \| list | restrict logins to users with this role (may be given multiple times). Only works with the keycloak-oidc provider. | | | `--validate-url` | string | Access token validation endpoint | | | `--version` | n/a | print version string | | -| `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (e.g. `.example.com`) \[[2](#footnote2)\] | | +| `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` or a `*.` to allow subdomains (e.g. `.example.com`, `*.example.com`) \[[2](#footnote2)\] | | | `--trusted-ip` | string \| list | list of IPs or CIDR ranges to allow to bypass authentication (may be given multiple times). When combined with `--reverse-proxy` and optionally `--real-client-ip-header` this will evaluate the trust of the IP stored in an HTTP header by a reverse proxy rather than the layer-3/4 remote address. WARNING: trusting IPs has inherent security flaws, especially when obtaining the IP address from an HTTP header (reverse-proxy mode). Use this option only if you understand the risks and how to manage them. | | \[1\]: Only these providers support `--cookie-refresh`: GitLab, Google and OIDC -\[2\]: When using the `whitelist-domain` option, any domain prefixed with a `.` will allow any subdomain of the specified domain as a valid redirect URL. By default, only empty ports are allowed. This translates to allowing the default port of the URL's protocol (80 for HTTP, 443 for HTTPS, etc.) since browsers omit them. To allow only a specific port, add it to the whitelisted domain: `example.com:8080`. To allow any port, use `*`: `example.com:*`. +\[2\]: When using the `whitelist-domain` option, any domain prefixed with a `.` or a `*.` will allow any subdomain of the specified domain as a valid redirect URL. By default, only empty ports are allowed. This translates to allowing the default port of the URL's protocol (80 for HTTP, 443 for HTTPS, etc.) since browsers omit them. To allow only a specific port, add it to the whitelisted domain: `example.com:8080`. To allow any port, use `*`: `example.com:*`. See below for provider specific options diff --git a/docs/docs/features/endpoints.md b/docs/docs/features/endpoints.md index a4a8994..4832a5d 100644 --- a/docs/docs/features/endpoints.md +++ b/docs/docs/features/endpoints.md @@ -34,3 +34,11 @@ X-Auth-Request-Redirect: https://my-oidc-provider/sign_out_page (The "sign_out_page" should be the [`end_session_endpoint`](https://openid.net/specs/openid-connect-session-1_0.html#rfc.section.2.1) from [the metadata](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig) if your OIDC provider supports Session Management and Discovery.) BEWARE that the domain you want to redirect to (`my-oidc-provider.example.com` in the example) must be added to the [`--whitelist-domain`](../configuration/overview) configuration option otherwise the redirect will be ignored. + +### Auth + +This endpoint returns 202 Accepted response or a 401 Unauthorized response. + +It can be configured using the following query parameters query parameters: +- `allowed_groups`: comma separated list of allowed groups +- `allowed_email_domains`: comma separated list of allowed email domains \ No newline at end of file diff --git a/oauthproxy.go b/oauthproxy.go index 1cd35e6..702c6a3 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -26,6 +26,7 @@ import ( "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/basic" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies" proxyhttp "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/http" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" @@ -971,28 +972,72 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R // authOnlyAuthorize handles special authorization logic that is only done // on the AuthOnly endpoint for use with Nginx subrequest architectures. -// -// TODO (@NickMeves): This method is a placeholder to be extended but currently -// fails the linter. Remove the nolint when functionality expands. -// -//nolint:gosimple func authOnlyAuthorize(req *http.Request, s *sessionsapi.SessionState) bool { // Allow requests previously allowed to be bypassed if s == nil { return true } - // Allow secondary group restrictions based on the `allowed_groups` - // querystring parameter - if !checkAllowedGroups(req, s) { - return false + constraints := []func(*http.Request, *sessionsapi.SessionState) bool{ + checkAllowedGroups, + checkAllowedEmailDomains, + } + + for _, constraint := range constraints { + if !constraint(req, s) { + return false + } } return true } +// extractAllowedEntities aims to extract and split allowed entities linked by a key, +// from an HTTP request query. Output is a map[string]struct{} where keys are valuable, +// the goal is to avoid time complexity O(N^2) while finding matches during membership checks. +func extractAllowedEntities(req *http.Request, key string) map[string]struct{} { + entities := map[string]struct{}{} + + query := req.URL.Query() + for _, allowedEntities := range query[key] { + for _, entity := range strings.Split(allowedEntities, ",") { + if entity != "" { + entities[entity] = struct{}{} + } + } + } + + return entities +} + +// checkAllowedEmailDomains allow email domain restrictions based on the `allowed_email_domains` +// querystring parameter +func checkAllowedEmailDomains(req *http.Request, s *sessionsapi.SessionState) bool { + allowedEmailDomains := extractAllowedEntities(req, "allowed_email_domains") + if len(allowedEmailDomains) == 0 { + return true + } + + splitEmail := strings.Split(s.Email, "@") + if len(splitEmail) != 2 { + return false + } + + endpoint, _ := url.Parse("") + endpoint.Host = splitEmail[1] + + allowedEmailDomainsList := []string{} + for ed := range allowedEmailDomains { + allowedEmailDomainsList = append(allowedEmailDomainsList, ed) + } + + return util.IsEndpointAllowed(endpoint, allowedEmailDomainsList) +} + +// checkAllowedGroups allow secondary group restrictions based on the `allowed_groups` +// querystring parameter func checkAllowedGroups(req *http.Request, s *sessionsapi.SessionState) bool { - allowedGroups := extractAllowedGroups(req) + allowedGroups := extractAllowedEntities(req, "allowed_groups") if len(allowedGroups) == 0 { return true } @@ -1006,21 +1051,6 @@ func checkAllowedGroups(req *http.Request, s *sessionsapi.SessionState) bool { return false } -func extractAllowedGroups(req *http.Request) map[string]struct{} { - groups := map[string]struct{}{} - - query := req.URL.Query() - for _, allowedGroups := range query["allowed_groups"] { - for _, group := range strings.Split(allowedGroups, ",") { - if group != "" { - groups[group] = struct{}{} - } - } - } - - return groups -} - // encodedState builds the OAuth state param out of our nonce and // original application redirect func encodeState(nonce string, redirect string) string { diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 14f1a02..6241e1b 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -2683,3 +2683,94 @@ func TestAuthOnlyAllowedGroupsWithSkipMethods(t *testing.T) { }) } } + +func TestAuthOnlyAllowedEmailDomains(t *testing.T) { + testCases := []struct { + name string + email string + querystring string + expectedStatusCode int + }{ + { + name: "NotEmailRestriction", + email: "toto@example.com", + querystring: "", + expectedStatusCode: http.StatusAccepted, + }, + { + name: "UserInAllowedEmailDomain", + email: "toto@example.com", + querystring: "?allowed_email_domains=example.com", + expectedStatusCode: http.StatusAccepted, + }, + { + name: "UserNotInAllowedEmailDomain", + email: "toto@example.com", + querystring: "?allowed_email_domains=a.example.com", + expectedStatusCode: http.StatusForbidden, + }, + { + name: "UserInAllowedEmailDomains", + email: "toto@example.com", + querystring: "?allowed_email_domains=a.example.com,b.example.com", + expectedStatusCode: http.StatusForbidden, + }, + { + name: "UserInAllowedEmailDomains", + email: "toto@example.com", + querystring: "?allowed_email_domains=a.example.com,example.com", + expectedStatusCode: http.StatusAccepted, + }, + { + name: "UserInAllowedEmailDomainWildcard", + email: "toto@foo.example.com", + querystring: "?allowed_email_domains=*.example.com", + expectedStatusCode: http.StatusAccepted, + }, + { + name: "UserNotInAllowedEmailDomainWildcard", + email: "toto@example.com", + querystring: "?allowed_email_domains=*.a.example.com", + expectedStatusCode: http.StatusForbidden, + }, + { + name: "UserInAllowedEmailDomainsWildcard", + email: "toto@example.com", + querystring: "?allowed_email_domains=*.a.example.com,*.b.example.com", + expectedStatusCode: http.StatusForbidden, + }, + { + name: "UserInAllowedEmailDomainsWildcard", + email: "toto@c.example.com", + querystring: "?allowed_email_domains=a.b.c.example.com,*.c.example.com", + expectedStatusCode: http.StatusAccepted, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + groups := []string{} + + created := time.Now() + + session := &sessions.SessionState{ + Groups: groups, + Email: tc.email, + AccessToken: "oauth_token", + CreatedAt: &created, + } + + test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {}) + if err != nil { + t.Fatal(err) + } + + err = test.SaveSession(session) + assert.NoError(t, err) + + test.proxy.ServeHTTP(test.rw, test.req) + + assert.Equal(t, tc.expectedStatusCode, test.rw.Code) + }) + } +} diff --git a/pkg/apis/options/options.go b/pkg/apis/options/options.go index 26f2fb7..bce1265 100644 --- a/pkg/apis/options/options.go +++ b/pkg/apis/options/options.go @@ -126,7 +126,7 @@ func NewFlagSet() *pflag.FlagSet { flagSet.StringSlice("extra-jwt-issuers", []string{}, "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)") flagSet.StringSlice("email-domain", []string{}, "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") - flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)") + flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . or a *. to allow subdomains (eg .example.com, *.example.com)") flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)") flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -B\" for bcrypt encryption") flagSet.StringSlice("htpasswd-user-group", []string{}, "the groups to be set on sessions for htpasswd users (may be given multiple times)") diff --git a/pkg/app/redirect/validator.go b/pkg/app/redirect/validator.go index 7883442..fd9074e 100644 --- a/pkg/app/redirect/validator.go +++ b/pkg/app/redirect/validator.go @@ -6,6 +6,8 @@ import ( "strings" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" + + util "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util" ) var ( @@ -50,28 +52,9 @@ func (v *validator) IsValidRedirect(redirect string) bool { logger.Printf("Rejecting invalid redirect %q: scheme unsupported or missing", redirect) return false } - redirectHostname := redirectURL.Hostname() - for _, allowedDomain := range v.allowedDomains { - allowedHost, allowedPort := splitHostPort(allowedDomain) - if allowedHost == "" { - continue - } - - if redirectHostname == strings.TrimPrefix(allowedHost, ".") || - (strings.HasPrefix(allowedHost, ".") && - strings.HasSuffix(redirectHostname, allowedHost)) { - // the domain names match, now validate the ports - // if the whitelisted domain's port is '*', allow all ports - // if the whitelisted domain contains a specific port, only allow that port - // if the whitelisted domain doesn't contain a port at all, only allow empty redirect ports ie http and https - redirectPort := redirectURL.Port() - if allowedPort == "*" || - allowedPort == redirectPort || - (allowedPort == "" && redirectPort == "") { - return true - } - } + if util.IsEndpointAllowed(redirectURL, v.allowedDomains) { + return true } logger.Printf("Rejecting invalid redirect %q: domain / port not in whitelist", redirect) @@ -81,40 +64,3 @@ func (v *validator) IsValidRedirect(redirect string) bool { return false } } - -// splitHostPort separates host and port. If the port is not valid, it returns -// the entire input as host, and it doesn't check the validity of the host. -// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric. -// *** taken from net/url, modified validOptionalPort() to accept ":*" -func splitHostPort(hostport string) (host, port string) { - host = hostport - - colon := strings.LastIndexByte(host, ':') - if colon != -1 && validOptionalPort(host[colon:]) { - host, port = host[:colon], host[colon+1:] - } - - if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { - host = host[1 : len(host)-1] - } - - return -} - -// validOptionalPort reports whether port is either an empty string -// or matches /^:\d*$/ -// *** taken from net/url, modified to accept ":*" -func validOptionalPort(port string) bool { - if port == "" || port == ":*" { - return true - } - if port[0] != ':' { - return false - } - for _, b := range port[1:] { - if b < '0' || b > '9' { - return false - } - } - return true -} diff --git a/pkg/app/redirect/validator_test.go b/pkg/app/redirect/validator_test.go index f7692bf..034a802 100644 --- a/pkg/app/redirect/validator_test.go +++ b/pkg/app/redirect/validator_test.go @@ -5,6 +5,7 @@ import ( "net/url" "os" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" @@ -22,6 +23,10 @@ var _ = Describe("Validator suite", func() { "anyport.bar:*", ".sub.anyport.bar:*", "www.whitelisteddomain.tld", + "*.wildcard.sub.port.bar:8080", + "*.wildcard.sub.anyport.bar:*", + "*.wildcard.bar", + "*.wildcard.proxy.foo.bar", } }) @@ -96,7 +101,20 @@ var _ = Describe("Validator suite", func() { Entry("Quad Tab 2", "/\t\t\\\t\t/evil.com", false), Entry("Relative Path", "/./\\evil.com", false), Entry("Relative Subpath", "/./../../\\evil.com", false), - Entry("Partial Subdomain", "evilbar.foo", false), + Entry("Valid HTTP Wildcard Subdomain", "http://foo.wildcard.bar/redirect", true), + Entry("Valid HTTPS Wildcard Subdomain", "https://foo.wildcard.bar/redirect", true), + Entry("Valid HTTP Wildcard Subdomain Root", "http://wildcard.bar/redirect", true), + Entry("Valid HTTPS Wildcard Subdomain Root", "https://wildcard.bar/redirect", true), + Entry("Valid HTTP Wildcard Subdomain anyport", "http://foo.wildcard.sub.anyport.bar:4242/redirect", true), + Entry("Valid HTTPS Wildcard Subdomain anyport", "https://foo.wildcard.sub.anyport.bar:4242/redirect", true), + Entry("Valid HTTP Wildcard Subdomain Anyport Root", "http://wildcard.sub.anyport.bar:4242/redirect", true), + Entry("Valid HTTPS Wildcard Subdomain Anyport Root", "https://wildcard.sub.anyport.bar:4242/redirect", true), + Entry("Valid HTTP Wildcard Subdomain Defined Port", "http://foo.wildcard.sub.port.bar:8080/redirect", true), + Entry("Valid HTTPS Wildcard Subdomain Defined Port", "https://foo.wildcard.sub.port.bar:8080/redirect", true), + Entry("Valid HTTP Wildcard Subdomain Defined Port Root", "http://wildcard.sub.port.bar:8080/redirect", true), + Entry("Valid HTTPS Wildcard Subdomain Defined Port Root", "https://wildcard.sub.port.bar:8080/redirect", true), + Entry("Missing Protocol Root Domain", "foo.bar/redirect", false), + Entry("Missing Protocol Wildcard Subdomain", "proxy.wildcard.bar/redirect", false), ) }) @@ -109,7 +127,7 @@ var _ = Describe("Validator suite", func() { DescribeTable("Should split the host and port", func(in splitHostPortTableInput) { - host, port := splitHostPort(in.hostport) + host, port := util.SplitHostPort(in.hostport) Expect(host).To(Equal(in.expectedHost)) Expect(port).To(Equal(in.expectedPort)) }, diff --git a/pkg/util/util.go b/pkg/util/util.go index d7a7f82..41c9a09 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -9,6 +9,8 @@ import ( "io/ioutil" "math/big" "net" + "net/url" + "strings" "time" ) @@ -66,3 +68,84 @@ func GenerateCert() ([]byte, []byte, error) { certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) return certBytes, keyBytes, err } + +// SplitHostPort separates host and port. If the port is not valid, it returns +// the entire input as host, and it doesn't check the validity of the host. +// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric. +// *** taken from net/url, modified validOptionalPort() to accept ":*" +func SplitHostPort(hostport string) (host, port string) { + host = hostport + + colon := strings.LastIndexByte(host, ':') + if colon != -1 && validOptionalPort(host[colon:]) { + host, port = host[:colon], host[colon+1:] + } + + if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { + host = host[1 : len(host)-1] + } + + return +} + +// validOptionalPort reports whether port is either an empty string +// or matches /^:\d*$/ +// *** taken from net/url, modified to accept ":*" +func validOptionalPort(port string) bool { + if port == "" || port == ":*" { + return true + } + if port[0] != ':' { + return false + } + for _, b := range port[1:] { + if b < '0' || b > '9' { + return false + } + } + return true +} + +// IsEndpointAllowed checks whether the endpoint URL is allowed based +// on an allowed domains list. +func IsEndpointAllowed(endpoint *url.URL, allowedDomains []string) bool { + hostname := endpoint.Hostname() + + for _, allowedDomain := range allowedDomains { + allowedHost, allowedPort := SplitHostPort(allowedDomain) + if allowedHost == "" { + continue + } + + if isHostnameAllowed(hostname, allowedHost) { + // the domain names match, now validate the ports + // if the allowed domain's port is '*', allow all ports + // if the allowed domain contains a specific port, only allow that port + // if the allowed domain doesn't contain a port at all, only allow empty redirect ports ie http and https + redirectPort := endpoint.Port() + if allowedPort == "*" || + allowedPort == redirectPort || + (allowedPort == "" && redirectPort == "") { + return true + } + } + } + + return false +} + +func isHostnameAllowed(hostname, allowedHost string) bool { + // check if we have a perfect match between hostname and allowedHost + if hostname == strings.TrimPrefix(allowedHost, ".") || + hostname == strings.TrimPrefix(allowedHost, "*.") { + return true + } + + // check if hostname is a sub domain of the allowedHost + if (strings.HasPrefix(allowedHost, ".") && strings.HasSuffix(hostname, allowedHost)) || + (strings.HasPrefix(allowedHost, "*.") && strings.HasSuffix(hostname, allowedHost[1:])) { + return true + } + + return false +} diff --git a/validator.go b/validator.go index 6d2a9b6..7f5349a 100644 --- a/validator.go +++ b/validator.go @@ -115,12 +115,15 @@ func isEmailValidWithDomains(email string, allowedDomains []string) bool { return true } - // allow if the domain is prefixed with . and + // allow if the domain is prefixed with . or *. and // the last element (split on @) has the suffix as the domain atoms := strings.Split(email, "@") - if strings.HasPrefix(domain, ".") && strings.HasSuffix(atoms[len(atoms)-1], domain) { + + if (strings.HasPrefix(domain, ".") && strings.HasSuffix(atoms[len(atoms)-1], domain)) || + (strings.HasPrefix(domain, "*.") && strings.HasSuffix(atoms[len(atoms)-1], domain[1:])) { return true } } + return false } diff --git a/validator_test.go b/validator_test.go index 5ea8ebf..0856ed9 100644 --- a/validator_test.go +++ b/validator_test.go @@ -153,6 +153,13 @@ func TestValidatorCases(t *testing.T) { email: "foo.bar@example0.com", expectedAuthZ: false, }, + { + name: "EmailNotInCorrect1stSubDomainsNotInEmailsWildcard", + allowedEmails: []string{"xyzzy@example.com", "plugh@example.com"}, + allowedDomains: []string{"*.example0.com", "*.example1.com"}, + email: "foo.bar@example0.com", + expectedAuthZ: false, + }, { name: "EmailInFirstDomain", allowedEmails: []string{"xyzzy@example.com", "plugh@example.com"}, @@ -160,6 +167,13 @@ func TestValidatorCases(t *testing.T) { email: "foo@bar.example0.com", expectedAuthZ: true, }, + { + name: "EmailInFirstDomainWildcard", + allowedEmails: []string{"xyzzy@example.com", "plugh@example.com"}, + allowedDomains: []string{"*.example0.com", "*.example1.com"}, + email: "foo@bar.example0.com", + expectedAuthZ: true, + }, { name: "EmailNotInCorrect2ndSubDomainsNotInEmails", allowedEmails: []string{"xyzzy@example.com", "plugh@example.com"}, @@ -174,6 +188,13 @@ func TestValidatorCases(t *testing.T) { email: "baz@quux.example1.com", expectedAuthZ: true, }, + { + name: "EmailInSecondDomainWildcard", + allowedEmails: []string{"xyzzy@example.com", "plugh@example.com"}, + allowedDomains: []string{"*.example0.com", "*.example1.com"}, + email: "baz@quux.example1.com", + expectedAuthZ: true, + }, { name: "EmailInFirstEmailList", allowedEmails: []string{"xyzzy@example.com", "plugh@example.com"}, @@ -181,6 +202,13 @@ func TestValidatorCases(t *testing.T) { email: "xyzzy@example.com", expectedAuthZ: true, }, + { + name: "EmailInFirstEmailListWildcard", + allowedEmails: []string{"xyzzy@example.com", "plugh@example.com"}, + allowedDomains: []string{"*.example0.com", "*.example1.com"}, + email: "xyzzy@example.com", + expectedAuthZ: true, + }, { name: "EmailNotInDomainsNotInEmails", allowedEmails: []string{"xyzzy@example.com", "plugh@example.com"}, @@ -370,6 +398,13 @@ func TestValidatorCases(t *testing.T) { allowedDomains: []string{"company.com"}, expectedAuthZ: false, }, + { + name: "CheckForEqualityNotSuffixWildcard", + email: "foo@evilcompany.com", + allowedEmails: []string(nil), + allowedDomains: []string{"*.company.com"}, + expectedAuthZ: false, + }, } for _, tc := range testCases {