authelia/internal/authorization/util.go
James Elliott 3c1bb3ec19
feat(authorization): domain regex match with named groups (#2789)
This adds an option to match domains by regex including two special named matching groups. User matches the username of the user, and Group matches the groups a user is a member of. These are both case-insensitive and you can see examples in the docs.
2022-04-01 22:38:49 +11:00

208 lines
5.1 KiB
Go

package authorization
import (
"net"
"regexp"
"strings"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
// PolicyToLevel converts a string policy to int authorization level.
func PolicyToLevel(policy string) Level {
switch policy {
case bypass:
return Bypass
case oneFactor:
return OneFactor
case twoFactor:
return TwoFactor
case deny:
return Denied
}
// By default the deny policy applies.
return Denied
}
// LevelToPolicy converts a int authorization level to string policy.
func LevelToPolicy(level Level) (policy string) {
switch level {
case Bypass:
return bypass
case OneFactor:
return oneFactor
case TwoFactor:
return twoFactor
case Denied:
return deny
}
return deny
}
func stringSliceToRegexpSlice(strings []string) (regexps []regexp.Regexp, err error) {
for _, str := range strings {
pattern, err := regexp.Compile(str)
if err != nil {
return nil, err
}
regexps = append(regexps, *pattern)
}
return regexps, nil
}
func schemaSubjectToACLSubject(subjectRule string) (subject SubjectMatcher) {
if strings.HasPrefix(subjectRule, prefixUser) {
user := strings.Trim(subjectRule[len(prefixUser):], " ")
return AccessControlUser{Name: user}
}
if strings.HasPrefix(subjectRule, prefixGroup) {
group := strings.Trim(subjectRule[len(prefixGroup):], " ")
return AccessControlGroup{Name: group}
}
return nil
}
func schemaDomainsToACL(domainRules []string, domainRegexRules []regexp.Regexp) (domains []SubjectObjectMatcher) {
for _, domainRule := range domainRules {
domains = append(domains, NewAccessControlDomain(domainRule))
}
for _, domainRegexRule := range domainRegexRules {
domains = append(domains, NewAccessControlDomainRegex(domainRegexRule))
}
return domains
}
func schemaResourcesToACL(resourceRules []regexp.Regexp) (resources []AccessControlResource) {
for _, resourceRule := range resourceRules {
resources = append(resources, AccessControlResource{Pattern: resourceRule})
}
return resources
}
func schemaMethodsToACL(methodRules []string) (methods []string) {
for _, method := range methodRules {
methods = append(methods, strings.ToUpper(method))
}
return methods
}
func schemaNetworksToACL(networkRules []string, networksMap map[string][]*net.IPNet, networksCacheMap map[string]*net.IPNet) (networks []*net.IPNet) {
for _, network := range networkRules {
if _, ok := networksMap[network]; !ok {
if _, ok := networksCacheMap[network]; ok {
networks = append(networks, networksCacheMap[network])
} else {
cidr, err := parseNetwork(network)
if err == nil {
networks = append(networks, cidr)
networksCacheMap[cidr.String()] = cidr
if cidr.String() != network {
networksCacheMap[network] = cidr
}
}
}
} else {
networks = append(networks, networksMap[network]...)
}
}
return networks
}
func parseSchemaNetworks(schemaNetworks []schema.ACLNetwork) (networksMap map[string][]*net.IPNet, networksCacheMap map[string]*net.IPNet) {
// These maps store pointers to the net.IPNet values so we can reuse them efficiently.
// The networksMap contains the named networks as keys, the networksCacheMap contains the CIDR notations as keys.
networksMap = map[string][]*net.IPNet{}
networksCacheMap = map[string]*net.IPNet{}
for _, aclNetwork := range schemaNetworks {
var networks []*net.IPNet
for _, networkRule := range aclNetwork.Networks {
cidr, err := parseNetwork(networkRule)
if err == nil {
networks = append(networks, cidr)
networksCacheMap[cidr.String()] = cidr
if cidr.String() != networkRule {
networksCacheMap[networkRule] = cidr
}
}
}
if _, ok := networksMap[aclNetwork.Name]; len(networks) != 0 && !ok {
networksMap[aclNetwork.Name] = networks
}
}
return networksMap, networksCacheMap
}
func parseNetwork(networkRule string) (cidr *net.IPNet, err error) {
if !strings.Contains(networkRule, "/") {
ip := net.ParseIP(networkRule)
if ip.To4() != nil {
_, cidr, err = net.ParseCIDR(networkRule + "/32")
} else {
_, cidr, err = net.ParseCIDR(networkRule + "/128")
}
} else {
_, cidr, err = net.ParseCIDR(networkRule)
}
return cidr, err
}
func schemaSubjectsToACL(subjectRules [][]string) (subjects []AccessControlSubjects) {
for _, subjectRule := range subjectRules {
subject := AccessControlSubjects{}
for _, subjectRuleItem := range subjectRule {
subject.AddSubject(subjectRuleItem)
}
if len(subject.Subjects) != 0 {
subjects = append(subjects, subject)
}
}
return subjects
}
func domainToPrefixSuffix(domain string) (prefix, suffix string) {
parts := strings.Split(domain, ".")
if len(parts) == 1 {
return "", parts[0]
}
return parts[0], strings.Join(parts[1:], ".")
}
// IsAuthLevelSufficient returns true if the current authenticationLevel is above the authorizationLevel.
func IsAuthLevelSufficient(authenticationLevel authentication.Level, authorizationLevel Level) bool {
switch authorizationLevel {
case Denied:
return false
case OneFactor:
return authenticationLevel >= authentication.OneFactor
case TwoFactor:
return authenticationLevel >= authentication.TwoFactor
}
return true
}