1
0
Fork 0
mirror of https://github.com/Luzifer/nginx-sso.git synced 2024-12-30 01:31:18 +00:00
nginx-sso/acl.go

190 lines
3.9 KiB
Go
Raw Normal View History

package main
import (
"fmt"
"net/http"
"regexp"
"strings"
"github.com/Luzifer/go_helpers/str"
)
type aclRule struct {
Field string `yaml:"field"`
Invert bool `yaml:"invert"`
IsPresent *bool `yaml:"present"`
MatchRegex *string `yaml:"regexp"`
MatchString *string `yaml:"equals"`
}
func (a aclRule) Validate() error {
if a.Field == "" {
return fmt.Errorf("Field is not set")
}
if a.IsPresent == nil && a.MatchRegex == nil && a.MatchString == nil {
return fmt.Errorf("No matcher (present, regexp, equals) is set")
}
if a.MatchRegex != nil {
if _, err := regexp.Compile(*a.MatchRegex); err != nil {
return fmt.Errorf("Regexp is invalid: %s", err)
}
}
return nil
}
func (a aclRule) AppliesToFields(fields map[string]string) bool {
var field, value string
for f, v := range fields {
if strings.ToLower(a.Field) == f {
field = f
value = v
break
}
}
if a.IsPresent != nil {
if !a.Invert && *a.IsPresent && field == "" {
// Field is expected to be present but isn't, rule does not apply
return false
}
if !a.Invert && !*a.IsPresent && field != "" {
// Field is expected not to be present but is, rule does not apply
return false
}
if a.Invert && *a.IsPresent && field != "" {
// Field is expected not to be present but is, rule does not apply
return false
}
if a.Invert && !*a.IsPresent && field == "" {
// Field is expected to be present but isn't, rule does not apply
return false
}
return true
}
if field == "" {
// We found a rule which has no matching field, rule does not apply
return false
}
if a.MatchString != nil {
if (*a.MatchString != value) == !a.Invert {
// Value does not match expected string, rule does not apply
return false
}
}
if a.MatchRegex != nil {
if regexp.MustCompile(*a.MatchRegex).MatchString(value) == a.Invert {
// Value does not match expected regexp, rule does not apply
return false
}
}
return true
}
type aclAccessResult uint
const (
accessDunno aclAccessResult = iota
accessAllow
accessDeny
)
type aclRuleSet struct {
Rules []aclRule `yaml:"rules"`
Allow []string `yaml:"allow"`
Deny []string `yaml:"deny"`
}
func (a aclRuleSet) buildFieldSet(r *http.Request) map[string]string {
result := map[string]string{}
for k := range r.Header {
result[strings.ToLower(k)] = r.Header.Get(k)
}
return result
}
func (a aclRuleSet) HasAccess(user string, groups []string, r *http.Request) aclAccessResult {
fields := a.buildFieldSet(r)
for _, rule := range a.Rules {
if !rule.AppliesToFields(fields) {
// At least one rule does not match the request
return accessDunno
}
}
// All rules do apply to this request, we can judge
if str.StringInSlice(user, a.Deny) {
// Explicit deny, final result
return accessDeny
}
if str.StringInSlice(user, a.Allow) {
// Explicit allow, final result
return accessAllow
}
for _, group := range groups {
if str.StringInSlice("@"+group, a.Deny) {
// Deny through group, final result
return accessDeny
}
if str.StringInSlice("@"+group, a.Allow) {
// Allow through group, final result
return accessAllow
}
}
// Neither user nor group are handled
return accessDunno
}
func (a aclRuleSet) Validate() error {
for i, r := range a.Rules {
if err := r.Validate(); err != nil {
return fmt.Errorf("Rule on position %d is invalid: %s", i+1, err)
}
}
return nil
}
type acl struct {
RuleSets []aclRuleSet `yaml:"rule_sets"`
}
func (a acl) Validate() error {
for i, r := range a.RuleSets {
if err := r.Validate(); err != nil {
return fmt.Errorf("RuleSet on position %d is invalid: %s", i+1, err)
}
}
return nil
}
func (a acl) HasAccess(user string, groups []string, r *http.Request) bool {
result := accessDunno
for _, rs := range a.RuleSets {
if intermediateResult := rs.HasAccess(user, groups, r); intermediateResult > result {
result = intermediateResult
}
}
return result == accessAllow
}