diff --git a/handlers/auth.go b/handlers/auth.go index dcfd69ef..83467a2c 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -139,26 +139,29 @@ func verifyUser(u interface{}) (bool, error) { return true, nil // WhiteList - case len(cfg.Cfg.WhiteList) != 0: - for _, wl := range cfg.Cfg.WhiteList { - if user.Username == wl { - log.Debugf("verifyUser: Success! found user.Username in WhiteList: %s", user.Username) - return true, nil + case len(cfg.Cfg.WhiteList) != 0 || len(cfg.Cfg.TeamWhiteList) != 0: + if len(cfg.Cfg.WhiteList) != 0 { + for _, wl := range cfg.Cfg.WhiteList { + if user.Username == wl { + log.Debugf("verifyUser: Success! found user.Username in WhiteList: %s", user.Username) + return true, nil + } } } - return false, fmt.Errorf("verifyUser: user.Username not found in WhiteList: %s", user.Username) - - // TeamWhiteList - case len(cfg.Cfg.TeamWhiteList) != 0: - for _, team := range user.TeamMemberships { - for _, wl := range cfg.Cfg.TeamWhiteList { - if team == wl { - log.Debugf("verifyUser: Success! found user.TeamWhiteList in TeamWhiteList: %s for user %s", wl, user.Username) - return true, nil + log.Debug("User not in userwhitelist, checking in teamWhiteList") + // TeamWhiteList - first match between user.teammembership and teamwhitelist in config authenticates the user + if len(cfg.Cfg.TeamWhiteList) != 0 { + for _, team := range user.TeamMemberships { + for _, wl := range cfg.Cfg.TeamWhiteList { + if team == wl { + log.Debugf("verifyUser: Success! found user.TeamWhiteList in TeamWhiteList: %s for user %s", wl, user.Username) + return true, nil + } } } + log.Warnf("verifyUser: user.TeamMemberships %s not found in TeamWhiteList: %s for user %s", user.TeamMemberships, cfg.Cfg.TeamWhiteList, user.Username) } - return false, fmt.Errorf("verifyUser: user.TeamMemberships %s not found in TeamWhiteList: %s for user %s", user.TeamMemberships, cfg.Cfg.TeamWhiteList, user.Username) + return false, fmt.Errorf("verifyUser: user.Username not found in WhiteList or TeamWhiteList: %s", user.Username) // Domains case len(cfg.Cfg.Domains) != 0: diff --git a/pkg/cfg/cfg.go b/pkg/cfg/cfg.go index 9aedba64..93311321 100644 --- a/pkg/cfg/cfg.go +++ b/pkg/cfg/cfg.go @@ -43,21 +43,22 @@ import ( // though most of the time envconfig will use the struct key's name: VOUCH_PORT VOUCH_JWT_MAXAGE // default values should be set in .defaults.yml type Config struct { - LogLevel string `mapstructure:"logLevel"` - Listen string `mapstructure:"listen"` - Port int `mapstructure:"port"` - SocketMode int `mapstructure:"socket_mode"` - SocketGroup string `mapstructure:"socket_group"` - DocumentRoot string `mapstructure:"document_root" envconfig:"document_root"` - WriteTimeout int `mapstructure:"writeTimeout"` - ReadTimeout int `mapstructure:"readTimeout"` - IdleTimeout int `mapstructure:"idleTimeout"` - Domains []string `mapstructure:"domains"` - WhiteList []string `mapstructure:"whitelist"` - TeamWhiteList []string `mapstructure:"teamWhitelist"` - AllowAllUsers bool `mapstructure:"allowAllUsers"` - PublicAccess bool `mapstructure:"publicAccess"` - TLS struct { + LogLevel string `mapstructure:"logLevel"` + Listen string `mapstructure:"listen"` + Port int `mapstructure:"port"` + SocketMode int `mapstructure:"socket_mode"` + SocketGroup string `mapstructure:"socket_group"` + DocumentRoot string `mapstructure:"document_root" envconfig:"document_root"` + WriteTimeout int `mapstructure:"writeTimeout"` + ReadTimeout int `mapstructure:"readTimeout"` + IdleTimeout int `mapstructure:"idleTimeout"` + Domains []string `mapstructure:"domains"` + WhiteList []string `mapstructure:"whitelist"` + TeamWhiteList []string `mapstructure:"teamWhitelist"` + TeamWhiteListClaim string `mapstructure:"teamWhitelistclaim"` // claim in UserInfo body that is used for TeamWhitelisting. If the value for TeamWhiteListClaim matches TeamWhiteList, auth is successful. + AllowAllUsers bool `mapstructure:"allowAllUsers"` + PublicAccess bool `mapstructure:"publicAccess"` + TLS struct { Cert string `mapstructure:"cert"` Key string `mapstructure:"key"` Profile string `mapstructure:"profile"` diff --git a/pkg/providers/openid/openid.go b/pkg/providers/openid/openid.go index bdc6bb7f..f4cfc65e 100644 --- a/pkg/providers/openid/openid.go +++ b/pkg/providers/openid/openid.go @@ -12,14 +12,13 @@ package openid import ( "encoding/json" - "golang.org/x/oauth2" - "io/ioutil" - "net/http" - "github.com/vouch/vouch-proxy/pkg/cfg" "github.com/vouch/vouch-proxy/pkg/providers/common" "github.com/vouch/vouch-proxy/pkg/structs" "go.uber.org/zap" + "golang.org/x/oauth2" + "io/ioutil" + "net/http" ) // Provider provider specific functions @@ -39,6 +38,7 @@ func (Provider) GetUserInfo(r *http.Request, user *structs.User, customClaims *s return err } userinfo, err := client.Get(cfg.GenOAuth.UserInfoURL) + if err != nil { return err } @@ -57,6 +57,41 @@ func (Provider) GetUserInfo(r *http.Request, user *structs.User, customClaims *s log.Error(err) return err } + if err = appendTeamMembershipsFromCustomClaim(data, user); err != nil { + log.Error(err) + return err + } + user.PrepareUserData() return nil } + +// appendTeamMembershipsFromCustomClaim appends teammembership values in user. If +// any TeamWhiteListClaim is mentioned in the config file, userinfo body is +// checked for the claim and the claim values are appended to user +// teammemberships. Later, this user data is used for teamwhitelist check in auth. +func appendTeamMembershipsFromCustomClaim(data []byte, user *structs.User) error { + var f interface{} + err := json.Unmarshal(data, &f) + if err != nil { + log.Error(err) + return err + } + if cfg.Cfg.TeamWhiteListClaim != "" { + m := f.(map[string]interface{}) + for k := range m { + if k == cfg.Cfg.TeamWhiteListClaim { + claimval, ok := m[k].(string) + if !ok { + log.Error("TeamWhiteList claim sent in openID user body cannot be casted as string") + continue // continue auth with existing teammemberships for user + } + + user.TeamMemberships = append(user.TeamMemberships, claimval) + break + } + } + log.Debugf("team memberships present in user: %+v", user.TeamMemberships) + } + return nil +} diff --git a/pkg/providers/openid/openid_test.go b/pkg/providers/openid/openid_test.go new file mode 100644 index 00000000..b90a9290 --- /dev/null +++ b/pkg/providers/openid/openid_test.go @@ -0,0 +1,52 @@ +package openid + +import ( + "github.com/stretchr/testify/assert" + "testing" + + "github.com/vouch/vouch-proxy/pkg/cfg" + "github.com/vouch/vouch-proxy/pkg/structs" +) + +func TestGetUserInfo(t *testing.T) { + setUp() + + user := structs.User{ + Username: "test", + CreatedOn: 123, + Email: "email@example.com", + ID: 1, + LastUpdate: 123, + Name: "name", + TeamMemberships: []string{"team1"}, + } + + // test1 + userinfobody := "{\"sub\":\"xx\",\"email\":\"email@example.com\",\"email_address\":\"email@example.com\",\"full_name\":\"ABC DEF\",\"last_name\":\"ABC\",\"CustomClaim1\":\"team2\"}" + data := []byte(userinfobody) + err := appendTeamMembershipsFromCustomClaim(data, &user) + assert.ElementsMatchf(t, err, nil, "Expected error to be nil") + assert.ElementsMatchf(t, user.TeamMemberships, []string{"team1", "team2"}, "Expected team memberships to be appended") + + //test2 + user.TeamMemberships = nil + userinfobody = "{\"sub\":\"xx\",\"email\":\"email@example.com\",\"email_address\":\"email@example.com\",\"full_name\":\"ABC DEF\",\"last_name\":\"ABC\",\"CustomClaim1\":\"team2\"}" + data = []byte(userinfobody) + err = appendTeamMembershipsFromCustomClaim(data, &user) + assert.ElementsMatchf(t, err, nil, "Expected error to be nil") + assert.ElementsMatchf(t, user.TeamMemberships, []string{"team2"}, "Expected team memberships to be appended") + + //test3 + user.TeamMemberships = nil + userinfobody = "{\"sub\":\"xx\",\"email\":\"email@example.com\",\"email_address\":\"email@example.com\",\"full_name\":\"ABC DEF\",\"last_name\":\"ABC\",\"CustomClaim1\":[\"team2\",\"team3\"]}" + data = []byte(userinfobody) + err = appendTeamMembershipsFromCustomClaim(data, &user) + assert.ElementsMatchf(t, err, nil, "Expected error to be nil") + assert.ElementsMatchf(t, user.TeamMemberships, nil, "Expected team memberships to be empty due to casting error") + +} + +func setUp() { + log = cfg.Logging.Logger + cfg.Cfg.TeamWhiteListClaim = "CustomClaim1" +}