Add support for a simple group filter

This commit is contained in:
Brandon Matthews 2017-01-12 12:15:00 -08:00
parent 5e5717ded7
commit 2fd6f02cd8
6 changed files with 54 additions and 33 deletions

View File

@ -34,7 +34,8 @@ func main() {
flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path") flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path")
flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream") flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream") flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
flagSet.Bool("pass-groups", false, "pass user group information along with basic auth to upstream") flagSet.Bool("pass-groups", false, "pass user group information in the X-Forwarded-Groups header to upstream (Azure only)")
flagSet.String("filter-groups", "", "exclude groups that do not contain this value in its 'displayName' (Azure only)")
flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header") flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header") flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream") flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")

View File

@ -64,6 +64,7 @@ type OAuthProxy struct {
SetXAuthRequest bool SetXAuthRequest bool
PassBasicAuth bool PassBasicAuth bool
PassGroups bool PassGroups bool
FilterGroups string
SkipProviderButton bool SkipProviderButton bool
PassUserHeaders bool PassUserHeaders bool
BasicAuthPassword string BasicAuthPassword string
@ -208,6 +209,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
PassBasicAuth: opts.PassBasicAuth, PassBasicAuth: opts.PassBasicAuth,
PassUserHeaders: opts.PassUserHeaders, PassUserHeaders: opts.PassUserHeaders,
PassGroups: opts.PassGroups, PassGroups: opts.PassGroups,
FilterGroups: opts.FilterGroups,
BasicAuthPassword: opts.BasicAuthPassword, BasicAuthPassword: opts.BasicAuthPassword,
PassAccessToken: opts.PassAccessToken, PassAccessToken: opts.PassAccessToken,
SkipProviderButton: opts.SkipProviderButton, SkipProviderButton: opts.SkipProviderButton,
@ -551,7 +553,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
session.IDToken = req.Form.Get("id_token") session.IDToken = req.Form.Get("id_token")
if p.PassGroups && session.IDToken != "" { if p.PassGroups && session.IDToken != "" {
session.Groups, err = p.provider.GetGroups(session) session.Groups, err = p.provider.GetGroups(session, p.FilterGroups)
if err != nil { if err != nil {
p.ErrorPage(rw, 500, "Internal Error", "Internal Error") p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
return return

View File

@ -52,6 +52,7 @@ type Options struct {
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"` SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"` PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
PassGroups bool `flag:"pass-groups" cfg:"pass_groups"` PassGroups bool `flag:"pass-groups" cfg:"pass_groups"`
FilterGroups string `flag:"filter-groups" cfg:"filter_groups"`
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"` BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"` PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"` PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
@ -103,6 +104,7 @@ func NewOptions() *Options {
PassBasicAuth: true, PassBasicAuth: true,
PassUserHeaders: true, PassUserHeaders: true,
PassGroups: false, PassGroups: false,
FilterGroups: "",
PassAccessToken: false, PassAccessToken: false,
PassHostHeader: true, PassHostHeader: true,
ApprovalPrompt: "force", ApprovalPrompt: "force",

View File

@ -127,7 +127,8 @@ func (p *AzureProvider) GetEmailAddress(s *SessionState) (string, error) {
return email, err return email, err
} }
func (p *AzureProvider) GetGroups(s *SessionState) (string, error) { // Get list of groups user belong to. Filter the desired names of groups (in case of huge group set)
func (p *AzureProvider) GetGroups(s *SessionState, f string) (string, error) {
if s.AccessToken == "" { if s.AccessToken == "" {
return "", errors.New("missing access token") return "", errors.New("missing access token")
} }
@ -136,36 +137,51 @@ func (p *AzureProvider) GetGroups(s *SessionState) (string, error) {
return "", errors.New("missing id token") return "", errors.New("missing id token")
} }
// parse token, check if it has groups // For future use. Right now microsoft graph don't support filter
// if groups, return "|".join(groups_list) // http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part2-url-conventions/odata-v4.0-errata02-os-part2-url-conventions-complete.html#_Toc406398116
// else
// look for claim source
// GET source
// parse response
// return "|".join(response.groups_list)
req, err := http.NewRequest("GET", "https://graph.microsoft.com/v1.0/me/memberOf/", nil) /*
//req, err := http.NewRequest("POST", "https://graph.microsoft.com/v1.0/me/getMemberGroups", strings.NewReader("{\"securityEnabledOnly\":true}")) var request string = "https://graph.microsoft.com/v1.0/me/memberOf?$select=id,displayName,groupTypes,securityEnabled,description,mailEnabled&$top=999"
if err != nil { if f != "" {
return "", err request += "?$filter=contains(displayName, '"+f+"')"
}
req.Header = getAzureHeader(s.AccessToken)
req.Header.Add("Content-Type", "application/json")
groupData, err := api.Request(req)
groups := make([]string, 0)
for _, groupInfo := range groupData.Get("value").MustArray() {
//v, ok := groupInfo.(string)
v, ok := groupInfo.(map[string]interface{})
if !ok {
continue
} }
dname := v["displayName"].(string) */
secen := v["securityEnabled"].(bool) //
mailen := v["mailEnabled"].(bool) // Filters that will be possible to use:
if secen == true && mailen == false { // contains - unknown function | "https://graph.microsoft.com/v1.0/me/memberOf?$filter=contains(displayName,%27olm%27)"
groups = append(groups, dname) // startswith - not supported | "https://graph.microsoft.com/v1.0/me/memberOf?$filter=startswith(displayName,%27olm%27)"
// substring - not supported | "https://graph.microsoft.com/v1.0/me/memberOf?$filter=substring(displayName,0,2)%20eq%20%27olm%27"
requestUrl := "https://graph.microsoft.com/v1.0/me/memberOf?$select=displayName"
groups := make([]string, 0)
for {
req, err := http.NewRequest("GET", requestUrl, nil)
if err != nil {
return "", err
}
req.Header = getAzureHeader(s.AccessToken)
req.Header.Add("Content-Type", "application/json")
groupData, err := api.Request(req)
for _, groupInfo := range groupData.Get("value").MustArray() {
v, ok := groupInfo.(map[string]interface{})
if !ok {
continue
}
dname := v["displayName"].(string)
if strings.Contains(dname, f) {
groups = append(groups, dname)
}
}
if nextlink := groupData.Get("@odata.nextLink").MustString(); nextlink != "" {
requestUrl = nextlink
} else {
break
} }
} }

View File

@ -111,7 +111,7 @@ func (p *ProviderData) GetEmailAddress(s *SessionState) (string, error) {
return "", errors.New("not implemented") return "", errors.New("not implemented")
} }
func (p *ProviderData) GetGroups(s *SessionState) (string, error) { func (p *ProviderData) GetGroups(s *SessionState, f string) (string, error) {
return "", errors.New("not implemented") return "", errors.New("not implemented")
} }

View File

@ -7,7 +7,7 @@ import (
type Provider interface { type Provider interface {
Data() *ProviderData Data() *ProviderData
GetEmailAddress(*SessionState) (string, error) GetEmailAddress(*SessionState) (string, error)
GetGroups(*SessionState) (string, error) GetGroups(*SessionState, string) (string, error)
Redeem(string, string) (*SessionState, error) Redeem(string, string) (*SessionState, error)
ValidateGroup(string) bool ValidateGroup(string) bool
ValidateSessionState(*SessionState) bool ValidateSessionState(*SessionState) bool