From 96a36b334d92a9dda7060742ad83852eab660ed2 Mon Sep 17 00:00:00 2001 From: John Stoltenborg Date: Wed, 1 Jun 2016 22:17:30 -0400 Subject: [PATCH 1/9] Configure switch for roles header option. Passing in arbitrary string for now; next up is grab roles from provider. --- README.md | 1 + contrib/oauth2_proxy.cfg.example | 3 +++ main.go | 1 + oauthproxy.go | 8 ++++++++ options.go | 2 ++ 5 files changed, 15 insertions(+) diff --git a/README.md b/README.md index 135f573f6..1c5288ffe 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ Usage of oauth2_proxy: -pass-access-token=false: pass OAuth access_token to upstream via X-Forwarded-Access-Token header -pass-basic-auth=true: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream -pass-host-header=true: pass the request Host Header to upstream + -pass-roles-header=true: pass user's roles upstream via X-Forwarded-Roles header -profile-url="": Profile access endpoint -provider="google": OAuth provider -proxy-prefix="/oauth2": the url root path that this proxy should be nested under (e.g. //sign_in) diff --git a/contrib/oauth2_proxy.cfg.example b/contrib/oauth2_proxy.cfg.example index 4006850a4..8518a3d5b 100644 --- a/contrib/oauth2_proxy.cfg.example +++ b/contrib/oauth2_proxy.cfg.example @@ -41,6 +41,9 @@ ## Pass OAuth Access token to upstream via "X-Forwarded-Access-Token" # pass_access_token = false +## Pass roles the user has via X-Forwarded-Roles +# pass_roles_header = true + ## Authenticated Email Addresses File (one email per line) # authenticated_emails_file = "" diff --git a/main.go b/main.go index dd9a100e8..3c9fe2dd7 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ func main() { 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-host-header", true, "pass the request Host Header to upstream") + flagSet.Bool("pass-roles-header", false, "pass user's teams upstream via X-Forwarded-Roles header") flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)") flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") diff --git a/oauthproxy.go b/oauthproxy.go index 16adf2249..396c66d36 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -30,6 +30,7 @@ var SignatureHeaders []string = []string{ "X-Forwarded-User", "X-Forwarded-Email", "X-Forwarded-Access-Token", + "X-Forwarded-Roles", "Cookie", "Gap-Auth", } @@ -61,6 +62,7 @@ type OAuthProxy struct { PassBasicAuth bool BasicAuthPassword string PassAccessToken bool + PassRolesHeader bool CookieCipher *cookie.Cipher skipAuthRegex []string compiledRegex []*regexp.Regexp @@ -195,6 +197,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { PassBasicAuth: opts.PassBasicAuth, BasicAuthPassword: opts.BasicAuthPassword, PassAccessToken: opts.PassAccessToken, + PassRolesHeader: opts.PassRolesHeader, CookieCipher: cipher, templates: loadTemplates(opts.CustomTemplatesDir), } @@ -596,6 +599,11 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int if p.PassAccessToken && session.AccessToken != "" { req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken} } + + if p.PassRolesHeader { + req.Header["X-Forwarded-Roles"] = []string{"our_roles"} + } + if session.Email == "" { rw.Header().Set("GAP-Auth", session.User) } else { diff --git a/options.go b/options.go index 37e66b4c4..8c63b5305 100644 --- a/options.go +++ b/options.go @@ -50,6 +50,7 @@ type Options struct { BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"` PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"` PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"` + PassRolesHeader bool `flag:"pass-roles-header" cfg:"pass_roles_header"` // These options allow for other providers besides Google, with // potential overrides. @@ -93,6 +94,7 @@ func NewOptions() *Options { PassBasicAuth: true, PassAccessToken: false, PassHostHeader: true, + PassRolesHeader: true, ApprovalPrompt: "force", RequestLogging: true, } From eaa55ef089c3a303915ad07913364744b47ee262 Mon Sep 17 00:00:00 2001 From: John Stoltenborg Date: Thu, 2 Jun 2016 22:24:34 -0400 Subject: [PATCH 2/9] Work towards getting Github teams into a roles header --- oauthproxy.go | 5 ++++- providers/github.go | 37 ++++++++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 396c66d36..c2ba3bd63 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -601,7 +601,10 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } if p.PassRolesHeader { - req.Header["X-Forwarded-Roles"] = []string{"our_roles"} + + // Todo - pull team list + log.Printf("Provider data - %v", p.provider.Data()) + req.Header["X-Forwarded-Roles"] = []string{"admin,kindly"} } if session.Email == "" { diff --git a/providers/github.go b/providers/github.go index cc5460b4e..c3b0e8681 100644 --- a/providers/github.go +++ b/providers/github.go @@ -14,6 +14,13 @@ type GitHubProvider struct { *ProviderData Org string Team string + UserTeams []struct{ + Name string `json:"name"` + Slug string `json:"slug"` + Org struct { + Login string `json:"login"` + } `json:"organization"` + } } func NewGitHubProvider(p *ProviderData) *GitHubProvider { @@ -44,6 +51,7 @@ func NewGitHubProvider(p *ProviderData) *GitHubProvider { } return &GitHubProvider{ProviderData: p} } + func (p *GitHubProvider) SetOrgTeam(org, team string) { p.Org = org p.Team = team @@ -98,22 +106,17 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) { return false, nil } -func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) { - // https://developer.github.com/v3/orgs/teams/#list-user-teams +func(p *GitHubProvider) setUserTeams(accessToken string) (bool, error) { - var teams []struct { - Name string `json:"name"` - Slug string `json:"slug"` - Org struct { - Login string `json:"login"` - } `json:"organization"` - } + // https://developer.github.com/v3/orgs/teams/#list-user-teams params := url.Values{ "access_token": {accessToken}, "limit": {"100"}, } + log.Printf("Getting team list"); + endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/teams?" + params.Encode() req, _ := http.NewRequest("GET", endpoint, nil) req.Header.Set("Accept", "application/vnd.github.v3+json") @@ -131,14 +134,22 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) { return false, fmt.Errorf("got %d from %q %s", resp.StatusCode, endpoint, body) } - if err := json.Unmarshal(body, &teams); err != nil { + if err := json.Unmarshal(body, &p.UserTeams); err != nil { return false, fmt.Errorf("%s unmarshaling %s", err, body) } + // Todo - Filter by organization we care about + log.Printf("Returned teams - %v", p.UserTeams) + + return true, nil +} + +func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) { + var hasOrg bool presentOrgs := make(map[string]bool) var presentTeams []string - for _, team := range teams { + for _, team := range p.UserTeams { presentOrgs[team.Org.Login] = true if p.Org == team.Org.Login { hasOrg = true @@ -171,6 +182,10 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) { Primary bool `json:"primary"` } + if ok, err := p.setUserTeams(s.AccessToken); err != nil || !ok { + return "", err + } + // if we require an Org or Team, check that first if p.Org != "" { if p.Team != "" { From aaa438ed0e7907c3f7ddcb1890d26fb8be09df40 Mon Sep 17 00:00:00 2001 From: John Stoltenborg Date: Fri, 3 Jun 2016 15:24:38 -0400 Subject: [PATCH 3/9] Sending roles as header --- oauthproxy.go | 11 +++++++---- options.go | 14 ++++++++++++++ providers/github.go | 34 ++++++++++++++++++++++++++++------ 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index c2ba3bd63..53937730a 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -601,10 +601,13 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } if p.PassRolesHeader { - - // Todo - pull team list - log.Printf("Provider data - %v", p.provider.Data()) - req.Header["X-Forwarded-Roles"] = []string{"admin,kindly"} + type RoleProvider interface { + GetUserRoles() string + } + rp := p.provider.(RoleProvider); + rp.GetUserRoles(); + log.Printf("User role data - %v", rp.GetUserRoles()) + req.Header["X-Forwarded-Roles"] = []string{rp.GetUserRoles()} } if session.Email == "" { diff --git a/options.go b/options.go index 8c63b5305..f4b2acd9c 100644 --- a/options.go +++ b/options.go @@ -177,6 +177,20 @@ func (o *Options) Validate() error { o.CookieExpire.String())) } + // Todo - Read up on Go syntax and clean this up + // Confirm the provider type supports sending user roles + if o.PassRolesHeader { + type RoleProvider interface { + GetUserRoles() string + } + if rp, ok := o.provider.(RoleProvider); ok{ + rp.GetUserRoles(); + } else { + msgs = append(msgs, "Provider '" + o.provider.Data().ProviderName + "' does not support sending a roles header.") + } + } + + if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" { if len(o.GoogleGroups) < 1 { msgs = append(msgs, "missing setting: google-group") diff --git a/providers/github.go b/providers/github.go index c3b0e8681..b860c49d4 100644 --- a/providers/github.go +++ b/providers/github.go @@ -14,7 +14,7 @@ type GitHubProvider struct { *ProviderData Org string Team string - UserTeams []struct{ + userTeams []struct{ Name string `json:"name"` Slug string `json:"slug"` Org struct { @@ -109,7 +109,6 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) { func(p *GitHubProvider) setUserTeams(accessToken string) (bool, error) { // https://developer.github.com/v3/orgs/teams/#list-user-teams - params := url.Values{ "access_token": {accessToken}, "limit": {"100"}, @@ -134,12 +133,11 @@ func(p *GitHubProvider) setUserTeams(accessToken string) (bool, error) { return false, fmt.Errorf("got %d from %q %s", resp.StatusCode, endpoint, body) } - if err := json.Unmarshal(body, &p.UserTeams); err != nil { + if err := json.Unmarshal(body, &p.userTeams); err != nil { return false, fmt.Errorf("%s unmarshaling %s", err, body) } - // Todo - Filter by organization we care about - log.Printf("Returned teams - %v", p.UserTeams) + log.Printf("Returned teams - %v", p.userTeams) return true, nil } @@ -149,7 +147,7 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) { var hasOrg bool presentOrgs := make(map[string]bool) var presentTeams []string - for _, team := range p.UserTeams { + for _, team := range p.userTeams { presentOrgs[team.Org.Login] = true if p.Org == team.Org.Login { hasOrg = true @@ -231,3 +229,27 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) { return "", nil } + + + +// Return a filtered list of all teams assigned to a user by the organization defined in the configuration +func (p *GitHubProvider) GetUserRoles()(string) { + + // Todo - could abstract this filtering and refactor hasOrgAndTeam() + presentOrgs := make(map[string]bool) + var presentTeams []string + for _, team := range p.userTeams { + presentOrgs[team.Org.Login] = true + if p.Org == team.Org.Login { + ts := strings.Split(p.Team, ",") + for _, t := range ts { + if t == team.Slug { + log.Printf("Found Github Organization:%q Team:%q (Name:%q)", team.Org.Login, team.Slug, team.Name) + } + } + presentTeams = append(presentTeams, team.Slug) + } + } + + return strings.Join(presentTeams, ",") +} From edc6d32af362969601639497c5cf267be9bdda3a Mon Sep 17 00:00:00 2001 From: Elliot Murphy Date: Mon, 6 Jun 2016 10:25:45 -0400 Subject: [PATCH 4/9] Fix test failures. This cleans up the optional RoleProvider interface and fixes some test crashes where PassRolesHeader was defaulting to true in the test suite. --- oauthproxy.go | 11 +++++------ options.go | 13 ++++--------- providers/providers.go | 10 ++++++++++ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 53937730a..56a0b27a3 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -601,13 +601,12 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } if p.PassRolesHeader { - type RoleProvider interface { - GetUserRoles() string + switch rp := p.provider.(type) { + case providers.RoleProvider: + roles := rp.GetUserRoles() + log.Printf("User role data - %v", roles) + req.Header["X-Forwarded-Roles"] = []string{roles} } - rp := p.provider.(RoleProvider); - rp.GetUserRoles(); - log.Printf("User role data - %v", rp.GetUserRoles()) - req.Header["X-Forwarded-Roles"] = []string{rp.GetUserRoles()} } if session.Email == "" { diff --git a/options.go b/options.go index f4b2acd9c..f7bcdf751 100644 --- a/options.go +++ b/options.go @@ -94,7 +94,7 @@ func NewOptions() *Options { PassBasicAuth: true, PassAccessToken: false, PassHostHeader: true, - PassRolesHeader: true, + PassRolesHeader: false, ApprovalPrompt: "force", RequestLogging: true, } @@ -177,20 +177,15 @@ func (o *Options) Validate() error { o.CookieExpire.String())) } - // Todo - Read up on Go syntax and clean this up // Confirm the provider type supports sending user roles if o.PassRolesHeader { - type RoleProvider interface { - GetUserRoles() string - } - if rp, ok := o.provider.(RoleProvider); ok{ - rp.GetUserRoles(); + if rp, ok := o.provider.(providers.RoleProvider); ok { + rp.GetUserRoles() } else { - msgs = append(msgs, "Provider '" + o.provider.Data().ProviderName + "' does not support sending a roles header.") + msgs = append(msgs, "Provider '"+o.provider.Data().ProviderName+"' does not support sending a roles header.") } } - if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" { if len(o.GoogleGroups) < 1 { msgs = append(msgs, "missing setting: google-group") diff --git a/providers/providers.go b/providers/providers.go index 010e633bf..96a3ca731 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -4,6 +4,8 @@ import ( "github.com/bitly/oauth2_proxy/cookie" ) +// Provider is the primary interface for an authentication provider +// all provider type Provider interface { Data() *ProviderData GetEmailAddress(*SessionState) (string, error) @@ -16,6 +18,14 @@ type Provider interface { CookieForSession(*SessionState, *cookie.Cipher) (string, error) } +// RoleProvider is an optional interface that exposes a list of roles +// for a user. For Providers like GitHub this would be the teams the user +// is a member of. +type RoleProvider interface { + GetUserRoles() string +} + +// New gives you an instance of the given provider func New(provider string, p *ProviderData) Provider { switch provider { case "myusa": From 423a79a1f16c503713c6a06507a6f83c41959fe7 Mon Sep 17 00:00:00 2001 From: Elliot Murphy Date: Mon, 6 Jun 2016 11:03:05 -0400 Subject: [PATCH 5/9] Cleanup RoleHeader call from code review --- oauthproxy.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 56a0b27a3..caab79d86 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -601,12 +601,10 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } if p.PassRolesHeader { - switch rp := p.provider.(type) { - case providers.RoleProvider: - roles := rp.GetUserRoles() - log.Printf("User role data - %v", roles) - req.Header["X-Forwarded-Roles"] = []string{roles} - } + rp := p.provider.(providers.RoleProvider) + roles := rp.GetUserRoles() + log.Printf("User role data - %v", roles) + req.Header["X-Forwarded-Roles"] = []string{roles} } if session.Email == "" { From 24588436b8549598e59e17517b1087a8955e9afe Mon Sep 17 00:00:00 2001 From: John Stoltenborg Date: Mon, 6 Jun 2016 12:32:22 -0400 Subject: [PATCH 6/9] Update default to false for pass-roles-header --- README.md | 2 +- contrib/oauth2_proxy.cfg.example | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c5288ffe..8b31619b0 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ Usage of oauth2_proxy: -pass-access-token=false: pass OAuth access_token to upstream via X-Forwarded-Access-Token header -pass-basic-auth=true: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream -pass-host-header=true: pass the request Host Header to upstream - -pass-roles-header=true: pass user's roles upstream via X-Forwarded-Roles header + -pass-roles-header=false: pass user's roles upstream via X-Forwarded-Roles header -profile-url="": Profile access endpoint -provider="google": OAuth provider -proxy-prefix="/oauth2": the url root path that this proxy should be nested under (e.g. //sign_in) diff --git a/contrib/oauth2_proxy.cfg.example b/contrib/oauth2_proxy.cfg.example index 8518a3d5b..49261347e 100644 --- a/contrib/oauth2_proxy.cfg.example +++ b/contrib/oauth2_proxy.cfg.example @@ -42,7 +42,7 @@ # pass_access_token = false ## Pass roles the user has via X-Forwarded-Roles -# pass_roles_header = true +# pass_roles_header = false ## Authenticated Email Addresses File (one email per line) # authenticated_emails_file = "" From 616d4e4017cccb1c3d9b276f0cf05e863a48d560 Mon Sep 17 00:00:00 2001 From: Seth Klein Date: Mon, 6 Jun 2016 13:03:46 -0400 Subject: [PATCH 7/9] Remove unneeded call to GetUserRoles during initialization --- options.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/options.go b/options.go index f7bcdf751..14ee43c93 100644 --- a/options.go +++ b/options.go @@ -179,9 +179,7 @@ func (o *Options) Validate() error { // Confirm the provider type supports sending user roles if o.PassRolesHeader { - if rp, ok := o.provider.(providers.RoleProvider); ok { - rp.GetUserRoles() - } else { + if _, ok := o.provider.(providers.RoleProvider); !ok { msgs = append(msgs, "Provider '"+o.provider.Data().ProviderName+"' does not support sending a roles header.") } } From 383c5747d44ac0f4fbadccd353cb8f317c051348 Mon Sep 17 00:00:00 2001 From: John Stoltenborg Date: Wed, 29 Jun 2016 11:11:31 -0400 Subject: [PATCH 8/9] Implement session refresh for Github provider Also some fmt --- oauthproxy.go | 4 ++-- providers/github.go | 35 +++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 53937730a..0f5937ca7 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -604,8 +604,8 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int type RoleProvider interface { GetUserRoles() string } - rp := p.provider.(RoleProvider); - rp.GetUserRoles(); + rp := p.provider.(RoleProvider) + rp.GetUserRoles() log.Printf("User role data - %v", rp.GetUserRoles()) req.Header["X-Forwarded-Roles"] = []string{rp.GetUserRoles()} } diff --git a/providers/github.go b/providers/github.go index b860c49d4..12b884849 100644 --- a/providers/github.go +++ b/providers/github.go @@ -3,23 +3,24 @@ package providers import ( "encoding/json" "fmt" - "strings" "io/ioutil" "log" "net/http" "net/url" + "strings" + "time" ) type GitHubProvider struct { *ProviderData - Org string - Team string - userTeams []struct{ + Org string + Team string + userTeams []struct { Name string `json:"name"` Slug string `json:"slug"` Org struct { - Login string `json:"login"` - } `json:"organization"` + Login string `json:"login"` + } `json:"organization"` } } @@ -72,7 +73,7 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) { "limit": {"100"}, } - endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/orgs?" + params.Encode() + endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/orgs?" + params.Encode() req, _ := http.NewRequest("GET", endpoint, nil) req.Header.Set("Accept", "application/vnd.github.v3+json") resp, err := http.DefaultClient.Do(req) @@ -106,7 +107,7 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) { return false, nil } -func(p *GitHubProvider) setUserTeams(accessToken string) (bool, error) { +func (p *GitHubProvider) setUserTeams(accessToken string) (bool, error) { // https://developer.github.com/v3/orgs/teams/#list-user-teams params := url.Values{ @@ -114,7 +115,7 @@ func(p *GitHubProvider) setUserTeams(accessToken string) (bool, error) { "limit": {"100"}, } - log.Printf("Getting team list"); + log.Printf("Getting team list") endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/teams?" + params.Encode() req, _ := http.NewRequest("GET", endpoint, nil) @@ -230,10 +231,24 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) { return "", nil } +func (p *GitHubProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) { + if s == nil || s.ExpiresOn.After(time.Now()) { + return false, nil + } + + if !p.ValidateGroup(s.Email) { + return false, fmt.Errorf("%s is no longer in the group(s)", s.Email) + } + + log.Printf("Refreshing roles for for Github provider.") + + p.setUserTeams(s.AccessToken) + return true, nil +} // Return a filtered list of all teams assigned to a user by the organization defined in the configuration -func (p *GitHubProvider) GetUserRoles()(string) { +func (p *GitHubProvider) GetUserRoles() string { // Todo - could abstract this filtering and refactor hasOrgAndTeam() presentOrgs := make(map[string]bool) From 64705b6b25ea1a1cbdcef8e4170827ffd45d8fc4 Mon Sep 17 00:00:00 2001 From: John Stoltenborg Date: Fri, 8 Jul 2016 14:40:58 -0400 Subject: [PATCH 9/9] Role data enhancements Fix case of restarted proxy causing need to refetch roles, enable metered role updates via cookie refresh configuration --- oauthproxy.go | 21 ++++++++++++++++-- providers/github.go | 41 ++++++++++------------------------- providers/provider_default.go | 3 +-- providers/providers.go | 1 + 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index caab79d86..39a2553aa 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -530,6 +530,9 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } if session != nil && sessionAge > p.CookieRefresh && p.CookieRefresh != time.Duration(0) { log.Printf("%s refreshing %s old session cookie for %s (refresh after %s)", remoteAddr, sessionAge, session, p.CookieRefresh) + log.Printf("Refreshing role permissions for user") + rp := p.provider.(providers.RoleProvider) + rp.SetUserRoles(session.AccessToken) saveSession = true } @@ -603,8 +606,22 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int if p.PassRolesHeader { rp := p.provider.(providers.RoleProvider) roles := rp.GetUserRoles() - log.Printf("User role data - %v", roles) - req.Header["X-Forwarded-Roles"] = []string{roles} + + // Upon restarting the proxy, if there is an existing cookie, we need to re-fetch roles from provider + // Project preference is to avoid cookie bloat, so we aren't storing roles in the cookie + // https://github.com/bitly/oauth2_proxy/issues/174#issuecomment-1578273584 + var i = 0 + if len(roles) < 1 && i < 1 { + i++ + rp.SetUserRoles(session.AccessToken) + refreshedRoles := rp.GetUserRoles() + req.Header["X-Forwarded-Roles"] = []string{refreshedRoles} + log.Printf("Refreshed user role data - %v", refreshedRoles) + + } else { + req.Header["X-Forwarded-Roles"] = []string{roles} + log.Printf("User role data - %v", roles) + } } if session.Email == "" { diff --git a/providers/github.go b/providers/github.go index 12b884849..bd29e68c3 100644 --- a/providers/github.go +++ b/providers/github.go @@ -8,14 +8,13 @@ import ( "net/http" "net/url" "strings" - "time" ) type GitHubProvider struct { *ProviderData Org string Team string - userTeams []struct { + userRoles []struct { Name string `json:"name"` Slug string `json:"slug"` Org struct { @@ -107,16 +106,14 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) { return false, nil } -func (p *GitHubProvider) setUserTeams(accessToken string) (bool, error) { +func (p *GitHubProvider) SetUserRoles(accessToken string) (bool, error) { // https://developer.github.com/v3/orgs/teams/#list-user-teams params := url.Values{ "access_token": {accessToken}, "limit": {"100"}, } - - log.Printf("Getting team list") - + endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/teams?" + params.Encode() req, _ := http.NewRequest("GET", endpoint, nil) req.Header.Set("Accept", "application/vnd.github.v3+json") @@ -134,11 +131,11 @@ func (p *GitHubProvider) setUserTeams(accessToken string) (bool, error) { return false, fmt.Errorf("got %d from %q %s", resp.StatusCode, endpoint, body) } - if err := json.Unmarshal(body, &p.userTeams); err != nil { + if err := json.Unmarshal(body, &p.userRoles); err != nil { return false, fmt.Errorf("%s unmarshaling %s", err, body) } - log.Printf("Returned teams - %v", p.userTeams) + log.Printf("Returned roles - %v", p.userRoles) return true, nil } @@ -148,7 +145,7 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) { var hasOrg bool presentOrgs := make(map[string]bool) var presentTeams []string - for _, team := range p.userTeams { + for _, team := range p.userRoles { presentOrgs[team.Org.Login] = true if p.Org == team.Org.Login { hasOrg = true @@ -181,7 +178,7 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) { Primary bool `json:"primary"` } - if ok, err := p.setUserTeams(s.AccessToken); err != nil || !ok { + if ok, err := p.SetUserRoles(s.AccessToken); err != nil || !ok { return "", err } @@ -231,29 +228,13 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) { return "", nil } -func (p *GitHubProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) { - - if s == nil || s.ExpiresOn.After(time.Now()) { - return false, nil - } - - if !p.ValidateGroup(s.Email) { - return false, fmt.Errorf("%s is no longer in the group(s)", s.Email) - } - - log.Printf("Refreshing roles for for Github provider.") - - p.setUserTeams(s.AccessToken) - return true, nil -} - // Return a filtered list of all teams assigned to a user by the organization defined in the configuration func (p *GitHubProvider) GetUserRoles() string { // Todo - could abstract this filtering and refactor hasOrgAndTeam() presentOrgs := make(map[string]bool) - var presentTeams []string - for _, team := range p.userTeams { + var presentRoles []string + for _, team := range p.userRoles { presentOrgs[team.Org.Login] = true if p.Org == team.Org.Login { ts := strings.Split(p.Team, ",") @@ -262,9 +243,9 @@ func (p *GitHubProvider) GetUserRoles() string { log.Printf("Found Github Organization:%q Team:%q (Name:%q)", team.Org.Login, team.Slug, team.Name) } } - presentTeams = append(presentTeams, team.Slug) + presentRoles = append(presentRoles, team.Slug) } } - return strings.Join(presentTeams, ",") + return strings.Join(presentRoles, ",") } diff --git a/providers/provider_default.go b/providers/provider_default.go index 82b73ec3d..637c54944 100644 --- a/providers/provider_default.go +++ b/providers/provider_default.go @@ -9,7 +9,6 @@ import ( "net/http" "net/url" "strings" - "github.com/bitly/oauth2_proxy/cookie" ) @@ -122,4 +121,4 @@ func (p *ProviderData) ValidateSessionState(s *SessionState) bool { // RefreshSessionIfNeeded func (p *ProviderData) RefreshSessionIfNeeded(s *SessionState) (bool, error) { return false, nil -} +} \ No newline at end of file diff --git a/providers/providers.go b/providers/providers.go index 96a3ca731..000a74278 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -23,6 +23,7 @@ type Provider interface { // is a member of. type RoleProvider interface { GetUserRoles() string + SetUserRoles(string) (bool, error) } // New gives you an instance of the given provider