Skip to content

Commit

Permalink
feature: Add support for whitelists and domains in rules
Browse files Browse the repository at this point in the history
Builds on thomseddon#63, adding support for the matchWhitelistOrDomain flag
  • Loading branch information
tdorsey committed Jun 6, 2020
1 parent fb8b216 commit cf2962f
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 37 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ You can restrict who can login with the following parameters:

Note, if you pass both `whitelist` and `domain`, then the default behaviour is for only `whitelist` to be used and `domain` will be effectively ignored. You can allow users matching *either* `whitelist` or `domain` by passing the `match-whitelist-or-domain` parameter (this will be the default behaviour in v3).

Domains and whitelists that are set in rules will always apply regardless of the `match-whitelist-or-domain` parameter

### Forwarded Headers

The authenticated user is set in the `X-Forwarded-User` header, to pass this on add this to the `authResponseHeaders` config option in traefik, as shown below in the [Applying Authentication](#applying-authentication) section.
Expand Down
58 changes: 38 additions & 20 deletions internal/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,39 +59,57 @@ func ValidateCookie(r *http.Request, c *http.Cookie) (string, error) {
// ValidateEmail checks if the given email address matches either a whitelisted
// email address, as defined by the "whitelist" config parameter. Or is part of
// a permitted domain, as defined by the "domains" config parameter
// if an email or domain is added by a rule, it is merged with the config before being evaluated
// in order to respect the match whitelist or domain parameter
func ValidateEmail(email string) bool {

// Do we have any validation to perform?
if len(config.Whitelist) == 0 && len(config.Domains) == 0 {
if len(config.Whitelist) == 0 && len(config.Domains) == 0 && len(config.Rules) == 0 {
return true
}
var whitelists []string
var domains []string

// Email whitelist validation
if len(config.Whitelist) > 0 {
for _, whitelist := range config.Whitelist {
if email == whitelist {
return true
}
// rule validation
for _, rule := range config.Rules {
for _, whitelisted := range rule.Whitelist {
whitelists = append(whitelists, whitelisted)
}
for _, domain := range rule.Domains {
domains = append(domains, domain)
}
}
domains = append(domains, config.Domains...)
whitelists = append(whitelists, config.Whitelist...)

// If we're not matching *either*, stop here
if !config.MatchWhitelistOrDomain {
return false
// Email whitelist validation
for _, whitelist := range whitelists {
if email == whitelist {
return true
}
}

// If there's a whitelist and we're not matching *either*, stop here
if len(whitelists) > 0 && !config.MatchWhitelistOrDomain {
return false
}

// Domain validation
if len(config.Domains) > 0 {
parts := strings.Split(email, "@")
if len(parts) < 2 {
return false
}
for _, domain := range config.Domains {
if domain == parts[1] {
return true
}
for _, domain := range domains {
if emailIsInDomain(email, domain) {
return true
}
}

return false
}
func emailIsInDomain(email string, domain string) bool {
parts := strings.Split(email, "@")
if len(parts) < 2 {
return false
}
if parts[1] == domain {
return true
}
return false
}

Expand Down
127 changes: 113 additions & 14 deletions internal/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,72 @@ func TestAuthValidateEmail(t *testing.T) {
v = ValidateEmail("one@two.com")
assert.True(v, "should allow any domain if email domain is not defined")

// Should allow email specified in a whitelist rule
config.Rules = map[string]*Rule{
"whitelisted-from-rule": {
Rule: "test-whitelist-rule",
Whitelist: []string{"whitelisted-from-rule@example.com"},
},
}

v = ValidateEmail("whitelisted-from-rule@example.com")
assert.True(v, "should allow email from whitelist rule")

// Should allow multiple emails specified in a whitelist rule
config.Rules = map[string]*Rule{
"whitelist-multiple-emails": {
Rule: "whitelist-rule",
Whitelist: []string{"allowed-from-whitelist-multiple-emails1@example.com",
"allowed-from-whitelist-multiple-emails2@example.com"},
},
}

v1 := ValidateEmail("allowed-from-whitelist-multiple-emails@example.com")
v2 := ValidateEmail("allowed-from-whitelist-multiple-emails@example.com")
assert.True((v1 == v2), "should allow emails from a whitelist rule")

// Should allow multiple whitelist rules
config.Rules = map[string]*Rule{
"allowed-from-whitelist-rule1": {
Rule: "whitelist-rule1",
Whitelist: []string{"allowed-from-whitelist-rule1@example.com"},
},
"allowed-from-whitelist-rule2": {
Rule: "whitelist-rule2",
Whitelist: []string{"allowed-from-whitelist-rule2@example.com"},
},
}
v1 = ValidateEmail("allowed-from-whitelist-rule1@example.com")
v2 = ValidateEmail("allowed-from-whitelist-rule2@example.com")
assert.True((v1 == v2), "should allow emails from multiple whitelist rules")

// Should allow domain to be specified in a rule
config.Rules = map[string]*Rule{
"allowed-from-domain-rule": {
Rule: "domain-rule1",
Domains: []string{"example.com"},
},
}

v = ValidateEmail("whitelisted-from-domain@example.com")
assert.True(v, "should allow email from domain rule")

// Should allow multiple domains to be specified in a rule
config.Rules = map[string]*Rule{
"allowed-from-domain-rule-1": {
Rule: "domain-rule1",
Domains: []string{"example.com"},
},
"allowed-from-domain-rule2": {
Rule: "domain-rule2",
Domains: []string{"example.org"},
},
}

v1 = ValidateEmail("allowed-from-domain-rule1@example.org")
v2 = ValidateEmail("allowed-from-domain-rule2@example.com")
assert.True((v1 == v2), "Should allow emails from multiple domain rules")

// Should block non matching domain
config.Domains = []string{"test.com"}
v = ValidateEmail("one@two.com")
Expand Down Expand Up @@ -99,26 +165,59 @@ func TestAuthValidateEmail(t *testing.T) {
config.Domains = []string{"example.com"}
config.Whitelist = []string{"test@test.com"}
config.MatchWhitelistOrDomain = false

v = ValidateEmail("test@test.com")
assert.True(v, "should allow user in whitelist")
assert.True(v, "should allow user from whitelist config if MatchWhitelistOrDomain is disabled")

v = ValidateEmail("test@example.com")
assert.False(v, "should not allow user from valid domain")
v = ValidateEmail("one@two.com")
assert.False(v, "should not allow user not in either")
assert.False(v, "should not allow user from domain config if a whitelist is specified and MatchWhitelistOrDomain is disabled")

// Should allow either matching domain or email address when
// MatchWhitelistOrDomain is enabled
config.Domains = []string{"example.com"}
config.Whitelist = []string{"test@test.com"}
// Should allow only matching email address when
// MatchWhitelistOrDomain is disabled
config.Rules = map[string]*Rule{
"allowed-from-whitelist-rule": {
Rule: "whitelist-rule",
Whitelist: []string{"allowed-from-whitelist-rule@example.com"},
},
"allowed-from-domain-rule": {
Rule: "domain-rule",
Domains: []string{"example.org"},
},
}
config.MatchWhitelistOrDomain = false

v = ValidateEmail("allowed-from-whitelist-rule@example.com")
assert.True(v, "should allow user from whitelist rule if MatchWhitelistOrDomain is disabled")

v = ValidateEmail("not-allowed-from-domain-rule@example.org")
assert.False(v, "should not allow user from domain rule if MatchWhitelistOrDomain is disabled")

v = ValidateEmail("one@two.com")
assert.False(v, "should not allow user not in whitelist or domain")

// Should allow matching email address or domain from a rule
// when MatchWhitelistOrDomain is enabled
config.Rules = map[string]*Rule{
"allowed-from-whitelist-rule": {
Rule: "whitelist-rule",
Whitelist: []string{"allowed-from-whitelist-rule@example.com"},
},
"allowed-from-domain-rule": {
Rule: "domain-rule",
Domains: []string{"example.org"},
},
}
config.MatchWhitelistOrDomain = true
v = ValidateEmail("test@test.com")
assert.True(v, "should allow user in whitelist")
v = ValidateEmail("test@example.com")
assert.True(v, "should allow user from valid domain")

v = ValidateEmail("allowed-from-whitelist-rule@example.com")
assert.True(v, "should allow user from whitelist rule if MatchWhitelistOrDomain is true")

v = ValidateEmail("not-allowed-from-domain-rule@example.org")
assert.True(v, "should allow user from domain rule if MatchWhitelistOrDomain is enabled")

v = ValidateEmail("one@two.com")
assert.False(v, "should not allow user not in either")
assert.False(v, "should not allow user not in whitelist or domain rule")
}

func TestRedirectUri(t *testing.T) {
assert := assert.New(t)

Expand Down
14 changes: 11 additions & 3 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ func (c *Config) parseUnknownFlag(option string, arg flags.SplitArgument, args [
rule.Rule = val
case "provider":
rule.Provider = val
case "whitelist":
list := CommaSeparatedList{}
rule.Whitelist = list
case "domains":
list := CommaSeparatedList{}
rule.Domains = list
default:
return args, fmt.Errorf("invalid route param: %v", option)
}
Expand Down Expand Up @@ -325,9 +331,11 @@ func (c *Config) setupProvider(name string) error {

// Rule holds defined rules
type Rule struct {
Action string
Rule string
Provider string
Action string
Rule string
Provider string
Whitelist []string
Domains []string
}

// NewRule creates a new rule object
Expand Down

0 comments on commit cf2962f

Please sign in to comment.