1
0
Fork 0
mirror of https://github.com/Luzifer/nginx-sso.git synced 2024-12-20 12:51:17 +00:00
nginx-sso/acl.go
Knut Ahlers 87d719367d
Initial version (#1)
* Initial draft
* HCL does not support int64
* Add http stubs
* Login does not need to return user details
* Fields should have a label
* Add example configuration
* Add stub for "Simple" authenticator
* Add debug logging
* Implement configuration loading
* Implement user detection
* Fix error names in doc strings
* Implement session store
* Implement "Token" provider
* Add login frontend
* Implement login and logout
* Do not show tabs when there is no choice
* Fix multi-tab errors, sorting
* Implement "Yubikey" authenticator
* Lint: Rename error to naming convention
* Apply cookie security
* Prevent double-login
* Adjust parameters for crowd
* Implement ACL
* Replace HCL config with YAML config
* Remove config debug output
* Remove crowd config

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2018-01-28 15:16:52 +01:00

189 lines
3.9 KiB
Go

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
}