2019-04-24 21:52:08 +00:00
|
|
|
package authorization
|
|
|
|
|
|
|
|
import (
|
2020-02-18 22:15:09 +00:00
|
|
|
"fmt"
|
2019-04-24 21:52:08 +00:00
|
|
|
"net"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
|
2019-12-24 02:14:52 +00:00
|
|
|
"github.com/authelia/authelia/internal/configuration/schema"
|
2020-02-18 22:15:09 +00:00
|
|
|
"github.com/authelia/authelia/internal/logging"
|
2019-04-24 21:52:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const userPrefix = "user:"
|
|
|
|
const groupPrefix = "group:"
|
|
|
|
|
|
|
|
// Authorizer the component in charge of checking whether a user can access a given resource.
|
|
|
|
type Authorizer struct {
|
|
|
|
configuration schema.AccessControlConfiguration
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewAuthorizer create an instance of authorizer with a given access control configuration.
|
|
|
|
func NewAuthorizer(configuration schema.AccessControlConfiguration) *Authorizer {
|
|
|
|
return &Authorizer{
|
|
|
|
configuration: configuration,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subject subject who to check access control for.
|
|
|
|
type Subject struct {
|
|
|
|
Username string
|
|
|
|
Groups []string
|
|
|
|
IP net.IP
|
|
|
|
}
|
|
|
|
|
2020-02-18 22:15:09 +00:00
|
|
|
func (s Subject) String() string {
|
|
|
|
return fmt.Sprintf("username=%s groups=%s ip=%s", s.Username, strings.Join(s.Groups, ","), s.IP.String())
|
|
|
|
}
|
|
|
|
|
2020-05-02 05:06:39 +00:00
|
|
|
// Object object to check access control for.
|
2019-04-24 21:52:08 +00:00
|
|
|
type Object struct {
|
|
|
|
Domain string
|
|
|
|
Path string
|
|
|
|
}
|
|
|
|
|
|
|
|
// selectMatchingSubjectRules take a set of rules and select only the rules matching the subject constraints.
|
|
|
|
func selectMatchingSubjectRules(rules []schema.ACLRule, subject Subject) []schema.ACLRule {
|
|
|
|
selectedRules := []schema.ACLRule{}
|
|
|
|
|
|
|
|
for _, rule := range rules {
|
2020-04-16 00:18:11 +00:00
|
|
|
if len(rule.Subjects) > 0 {
|
|
|
|
for _, subjectRule := range rule.Subjects {
|
|
|
|
if isSubjectMatching(subject, subjectRule) && isIPMatching(subject.IP, rule.Networks) {
|
|
|
|
selectedRules = append(selectedRules, rule)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if isIPMatching(subject.IP, rule.Networks) {
|
|
|
|
selectedRules = append(selectedRules, rule)
|
|
|
|
}
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return selectedRules
|
|
|
|
}
|
|
|
|
|
|
|
|
func selectMatchingObjectRules(rules []schema.ACLRule, object Object) []schema.ACLRule {
|
|
|
|
selectedRules := []schema.ACLRule{}
|
|
|
|
|
|
|
|
for _, rule := range rules {
|
2020-05-04 19:39:25 +00:00
|
|
|
if isDomainMatching(object.Domain, rule.Domains) && isPathMatching(object.Path, rule.Resources) {
|
|
|
|
selectedRules = append(selectedRules, rule)
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return selectedRules
|
|
|
|
}
|
|
|
|
|
|
|
|
func selectMatchingRules(rules []schema.ACLRule, subject Subject, object Object) []schema.ACLRule {
|
|
|
|
matchingRules := selectMatchingSubjectRules(rules, subject)
|
|
|
|
return selectMatchingObjectRules(matchingRules, object)
|
|
|
|
}
|
|
|
|
|
2020-04-20 21:03:38 +00:00
|
|
|
// PolicyToLevel converts a string policy to int authorization level.
|
2020-02-04 21:18:02 +00:00
|
|
|
func PolicyToLevel(policy string) Level {
|
2019-04-24 21:52:08 +00:00
|
|
|
switch policy {
|
|
|
|
case "bypass":
|
|
|
|
return Bypass
|
|
|
|
case "one_factor":
|
|
|
|
return OneFactor
|
|
|
|
case "two_factor":
|
|
|
|
return TwoFactor
|
|
|
|
case "deny":
|
|
|
|
return Denied
|
|
|
|
}
|
|
|
|
// By default the deny policy applies.
|
|
|
|
return Denied
|
|
|
|
}
|
|
|
|
|
2020-03-06 00:31:09 +00:00
|
|
|
// IsSecondFactorEnabled return true if at least one policy is set to second factor.
|
|
|
|
func (p *Authorizer) IsSecondFactorEnabled() bool {
|
|
|
|
if PolicyToLevel(p.configuration.DefaultPolicy) == TwoFactor {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, r := range p.configuration.Rules {
|
|
|
|
if PolicyToLevel(r.Policy) == TwoFactor {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
// GetRequiredLevel retrieve the required level of authorization to access the object.
|
|
|
|
func (p *Authorizer) GetRequiredLevel(subject Subject, requestURL url.URL) Level {
|
2020-02-18 22:15:09 +00:00
|
|
|
logging.Logger().Tracef("Check authorization of subject %s and url %s.",
|
|
|
|
subject.String(), requestURL.String())
|
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
matchingRules := selectMatchingRules(p.configuration.Rules, subject, Object{
|
|
|
|
Domain: requestURL.Hostname(),
|
|
|
|
Path: requestURL.Path,
|
|
|
|
})
|
|
|
|
|
|
|
|
if len(matchingRules) > 0 {
|
2020-02-04 21:18:02 +00:00
|
|
|
return PolicyToLevel(matchingRules[0].Policy)
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
2020-02-18 22:15:09 +00:00
|
|
|
logging.Logger().Tracef("No matching rule for subject %s and url %s... Applying default policy.",
|
|
|
|
subject.String(), requestURL.String())
|
|
|
|
|
2020-02-04 21:18:02 +00:00
|
|
|
return PolicyToLevel(p.configuration.DefaultPolicy)
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
2020-05-04 19:39:25 +00:00
|
|
|
|
|
|
|
// IsURLMatchingRuleWithGroupSubjects returns true if the request has at least one
|
|
|
|
// matching ACL with a subject of type group attached to it, otherwise false.
|
|
|
|
func (p *Authorizer) IsURLMatchingRuleWithGroupSubjects(requestURL url.URL) (hasGroupSubjects bool) {
|
|
|
|
for _, rule := range p.configuration.Rules {
|
|
|
|
if isDomainMatching(requestURL.Hostname(), rule.Domains) && isPathMatching(requestURL.Path, rule.Resources) {
|
|
|
|
for _, subjectRule := range rule.Subjects {
|
|
|
|
if strings.HasPrefix(subjectRule, groupPrefix) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|