Merge pull request #139 from jburnham/google_group_auth
[RDY] google: Support restricting access to a specific group(s)
This commit is contained in:
		
						commit
						2a784ae0d0
					
				
							
								
								
									
										3
									
								
								Godeps
								
								
								
								
							
							
						
						
									
										3
									
								
								Godeps
								
								
								
								
							|  | @ -3,3 +3,6 @@ github.com/bitly/go-simplejson          3378bdcb5cebedcbf8b5750edee28010f128fe24 | ||||||
| github.com/mreiferson/go-options         ee94b57f2fbf116075426f853e5abbcdfeca8b3d | github.com/mreiferson/go-options         ee94b57f2fbf116075426f853e5abbcdfeca8b3d | ||||||
| github.com/bmizerany/assert              e17e99893cb6509f428e1728281c2ad60a6b31e3 | github.com/bmizerany/assert              e17e99893cb6509f428e1728281c2ad60a6b31e3 | ||||||
| gopkg.in/fsnotify.v1                     v1.2.0 | gopkg.in/fsnotify.v1                     v1.2.0 | ||||||
|  | golang.org/x/oauth2                      397fe7649477ff2e8ced8fc0b2696f781e53745a | ||||||
|  | golang.org/x/oauth2/google               397fe7649477ff2e8ced8fc0b2696f781e53745a | ||||||
|  | google.golang.org/api/admin/directory/v1 a5c3e2a4792aff40e59840d9ecdff0542a202a80 | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								README.md
								
								
								
								
							
							
						
						
									
										25
									
								
								README.md
								
								
								
								
							|  | @ -51,6 +51,26 @@ For Google, the registration steps are: | ||||||
| 
 | 
 | ||||||
| It's recommended to refresh sessions on a short interval (1h) with `cookie-refresh` setting which validates that the account is still authorized. | It's recommended to refresh sessions on a short interval (1h) with `cookie-refresh` setting which validates that the account is still authorized. | ||||||
| 
 | 
 | ||||||
|  | #### Restrict auth to specific Google groups on your domain. (optional) | ||||||
|  | 
 | ||||||
|  | 1. Create a service account: https://developers.google.com/identity/protocols/OAuth2ServiceAccount and make sure to download the json file. | ||||||
|  | 2. Make note of the Client ID for a future step. | ||||||
|  | 3. Under "APIs & Auth", choose APIs. | ||||||
|  | 4. Click on Admin SDK and then Enable API. | ||||||
|  | 5. Follow the steps on https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account and give the client id from step 2 the following oauth scopes: | ||||||
|  | ``` | ||||||
|  | https://www.googleapis.com/auth/admin.directory.group.readonly | ||||||
|  | https://www.googleapis.com/auth/admin.directory.user.readonly | ||||||
|  | ``` | ||||||
|  | 6. Follow the steps on https://support.google.com/a/answer/60757 to enable Admin API access. | ||||||
|  | 7. Create or choose an existing administrative email address on the Gmail domain to assign to the ```google-admin-email``` flag. This email will be impersonated by this client to make calls to the Admin SDK. See the note on the link from step 5 for the reason why. | ||||||
|  | 8. Create or choose an existing email group and set that email to the ```google-group``` flag. You can pass multiple instances of this flag with different groups | ||||||
|  | and the user will be checked against all the provided groups. | ||||||
|  | 9. Lock down the permissions on the json file downloaded from step 1 so only oauth2_proxy is able to read the file and set the path to the file in the ```google-service-account-json``` flag. | ||||||
|  | 10. Restart oauth2_proxy. | ||||||
|  | 
 | ||||||
|  | Note: The user is checked against the group members list on initial authentication and every time the token is refreshed ( about once an hour ). | ||||||
|  | 
 | ||||||
| ### GitHub Auth Provider | ### GitHub Auth Provider | ||||||
| 
 | 
 | ||||||
| 1. Create a new project: https://github.com/settings/developers | 1. Create a new project: https://github.com/settings/developers | ||||||
|  | @ -110,6 +130,10 @@ Usage of oauth2_proxy: | ||||||
|   -email-domain=: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email |   -email-domain=: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email | ||||||
|   -github-org="": restrict logins to members of this organisation |   -github-org="": restrict logins to members of this organisation | ||||||
|   -github-team="": restrict logins to members of this team |   -github-team="": restrict logins to members of this team | ||||||
|  |   -google-group="": restrict logins to members of this google group | ||||||
|  |   -google-admin-email="": the google admin to impersonate for api calls | ||||||
|  |   -google-service-account-json="": the path to the service account json credentials | ||||||
|  | 
 | ||||||
|   -htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption |   -htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption | ||||||
|   -http-address="127.0.0.1:4180": [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients |   -http-address="127.0.0.1:4180": [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients | ||||||
|   -https-address=":443": <addr>:<port> to listen on for HTTPS clients |   -https-address=":443": <addr>:<port> to listen on for HTTPS clients | ||||||
|  | @ -233,4 +257,3 @@ Follow the examples in the [`providers` package](providers/) to define a new | ||||||
| `Provider` instance. Add a new `case` to | `Provider` instance. Add a new `case` to | ||||||
| [`providers.New()`](providers/providers.go) to allow `oauth2_proxy` to use the | [`providers.New()`](providers/providers.go) to allow `oauth2_proxy` to use the | ||||||
| new `Provider`. | new `Provider`. | ||||||
| 
 |  | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								main.go
								
								
								
								
							
							
						
						
									
										4
									
								
								main.go
								
								
								
								
							|  | @ -20,6 +20,7 @@ func main() { | ||||||
| 	emailDomains := StringArray{} | 	emailDomains := StringArray{} | ||||||
| 	upstreams := StringArray{} | 	upstreams := StringArray{} | ||||||
| 	skipAuthRegex := StringArray{} | 	skipAuthRegex := StringArray{} | ||||||
|  | 	googleGroups := StringArray{} | ||||||
| 
 | 
 | ||||||
| 	config := flagSet.String("config", "", "path to config file") | 	config := flagSet.String("config", "", "path to config file") | ||||||
| 	showVersion := flagSet.Bool("version", false, "print version string") | 	showVersion := flagSet.Bool("version", false, "print version string") | ||||||
|  | @ -39,6 +40,9 @@ func main() { | ||||||
| 	flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") | 	flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") | ||||||
| 	flagSet.String("github-org", "", "restrict logins to members of this organisation") | 	flagSet.String("github-org", "", "restrict logins to members of this organisation") | ||||||
| 	flagSet.String("github-team", "", "restrict logins to members of this team") | 	flagSet.String("github-team", "", "restrict logins to members of this team") | ||||||
|  | 	flagSet.Var(&googleGroups, "google-group", "restrict logins to members of this google group (may be given multiple times).") | ||||||
|  | 	flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls") | ||||||
|  | 	flagSet.String("google-service-account-json", "", "the path to the service account json credentials") | ||||||
| 	flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"") | 	flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"") | ||||||
| 	flagSet.String("client-secret", "", "the OAuth Client Secret") | 	flagSet.String("client-secret", "", "the OAuth Client Secret") | ||||||
| 	flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)") | 	flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)") | ||||||
|  |  | ||||||
|  | @ -433,7 +433,7 @@ func (p *OauthProxy) OauthCallback(rw http.ResponseWriter, req *http.Request) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// set cookie, or deny
 | 	// set cookie, or deny
 | ||||||
| 	if p.Validator(session.Email) { | 	if p.Validator(session.Email) && p.provider.ValidateGroup(session.Email) { | ||||||
| 		log.Printf("%s authentication complete %s", remoteAddr, session) | 		log.Printf("%s authentication complete %s", remoteAddr, session) | ||||||
| 		err := p.SaveSession(rw, req, session) | 		err := p.SaveSession(rw, req, session) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -477,7 +477,7 @@ func (p *OauthProxy) Proxy(rw http.ResponseWriter, req *http.Request) { | ||||||
| 		clearSession = true | 		clearSession = true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if saveSession && !revalidated && session.AccessToken != "" { | 	if saveSession && !revalidated && session != nil && session.AccessToken != "" { | ||||||
| 		if !p.provider.ValidateSessionState(session) { | 		if !p.provider.ValidateSessionState(session) { | ||||||
| 			log.Printf("%s removing session. error validating %s", remoteAddr, session) | 			log.Printf("%s removing session. error validating %s", remoteAddr, session) | ||||||
| 			saveSession = false | 			saveSession = false | ||||||
|  | @ -493,7 +493,7 @@ func (p *OauthProxy) Proxy(rw http.ResponseWriter, req *http.Request) { | ||||||
| 		clearSession = true | 		clearSession = true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if saveSession { | 	if saveSession && session != nil { | ||||||
| 		err := p.SaveSession(rw, req, session) | 		err := p.SaveSession(rw, req, session) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Printf("%s %s", remoteAddr, err) | 			log.Printf("%s %s", remoteAddr, err) | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								options.go
								
								
								
								
							
							
						
						
									
										25
									
								
								options.go
								
								
								
								
							|  | @ -3,6 +3,7 @@ package main | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | 	"os" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  | @ -25,6 +26,9 @@ type Options struct { | ||||||
| 	EmailDomains             []string `flag:"email-domain" cfg:"email_domains"` | 	EmailDomains             []string `flag:"email-domain" cfg:"email_domains"` | ||||||
| 	GitHubOrg                string   `flag:"github-org" cfg:"github_org"` | 	GitHubOrg                string   `flag:"github-org" cfg:"github_org"` | ||||||
| 	GitHubTeam               string   `flag:"github-team" cfg:"github_team"` | 	GitHubTeam               string   `flag:"github-team" cfg:"github_team"` | ||||||
|  | 	GoogleGroups             []string `flag:"google-group" cfg:"google_group"` | ||||||
|  | 	GoogleAdminEmail         string   `flag:"google-admin-email" cfg:"google_admin_email"` | ||||||
|  | 	GoogleServiceAccountJSON string   `flag:"google-service-account-json" cfg:"google_service_account_json"` | ||||||
| 	HtpasswdFile             string   `flag:"htpasswd-file" cfg:"htpasswd_file"` | 	HtpasswdFile             string   `flag:"htpasswd-file" cfg:"htpasswd_file"` | ||||||
| 	DisplayHtpasswdForm      bool     `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"` | 	DisplayHtpasswdForm      bool     `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"` | ||||||
| 	CustomTemplatesDir       string   `flag:"custom-templates-dir" cfg:"custom_templates_dir"` | 	CustomTemplatesDir       string   `flag:"custom-templates-dir" cfg:"custom_templates_dir"` | ||||||
|  | @ -159,6 +163,18 @@ func (o *Options) Validate() error { | ||||||
| 			o.CookieExpire.String())) | 			o.CookieExpire.String())) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" { | ||||||
|  | 		if len(o.GoogleGroups) < 1 { | ||||||
|  | 			msgs = append(msgs, "missing setting: google-group") | ||||||
|  | 		} | ||||||
|  | 		if o.GoogleAdminEmail == "" { | ||||||
|  | 			msgs = append(msgs, "missing setting: google-admin-email") | ||||||
|  | 		} | ||||||
|  | 		if o.GoogleServiceAccountJSON == "" { | ||||||
|  | 			msgs = append(msgs, "missing setting: google-service-account-json") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if len(msgs) != 0 { | 	if len(msgs) != 0 { | ||||||
| 		return fmt.Errorf("Invalid configuration:\n  %s", | 		return fmt.Errorf("Invalid configuration:\n  %s", | ||||||
| 			strings.Join(msgs, "\n  ")) | 			strings.Join(msgs, "\n  ")) | ||||||
|  | @ -182,6 +198,15 @@ func parseProviderInfo(o *Options, msgs []string) []string { | ||||||
| 	switch p := o.provider.(type) { | 	switch p := o.provider.(type) { | ||||||
| 	case *providers.GitHubProvider: | 	case *providers.GitHubProvider: | ||||||
| 		p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam) | 		p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam) | ||||||
|  | 	case *providers.GoogleProvider: | ||||||
|  | 		if o.GoogleServiceAccountJSON != "" { | ||||||
|  | 			file, err := os.Open(o.GoogleServiceAccountJSON) | ||||||
|  | 			if err != nil { | ||||||
|  | 				msgs = append(msgs, "invalid Google credentials file: "+o.GoogleServiceAccountJSON) | ||||||
|  | 			} else { | ||||||
|  | 				p.SetGroupRestriction(o.GoogleGroups, o.GoogleAdminEmail, file) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return msgs | 	return msgs | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -40,6 +40,32 @@ func TestNewOptions(t *testing.T) { | ||||||
| 	assert.Equal(t, expected, err.Error()) | 	assert.Equal(t, expected, err.Error()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestGoogleGroupOptions(t *testing.T) { | ||||||
|  | 	o := testOptions() | ||||||
|  | 	o.GoogleGroups = []string{"googlegroup"} | ||||||
|  | 	err := o.Validate() | ||||||
|  | 	assert.NotEqual(t, nil, err) | ||||||
|  | 
 | ||||||
|  | 	expected := errorMsg([]string{ | ||||||
|  | 		"missing setting: google-admin-email", | ||||||
|  | 		"missing setting: google-service-account-json"}) | ||||||
|  | 	assert.Equal(t, expected, err.Error()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGoogleGroupInvalidFile(t *testing.T) { | ||||||
|  | 	o := testOptions() | ||||||
|  | 	o.GoogleGroups = []string{"test_group"} | ||||||
|  | 	o.GoogleAdminEmail = "admin@example.com" | ||||||
|  | 	o.GoogleServiceAccountJSON = "file_doesnt_exist.json" | ||||||
|  | 	err := o.Validate() | ||||||
|  | 	assert.NotEqual(t, nil, err) | ||||||
|  | 
 | ||||||
|  | 	expected := errorMsg([]string{ | ||||||
|  | 		"invalid Google credentials file: file_doesnt_exist.json", | ||||||
|  | 	}) | ||||||
|  | 	assert.Equal(t, expected, err.Error()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestInitializedOptions(t *testing.T) { | func TestInitializedOptions(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, o.Validate()) | ||||||
|  |  | ||||||
|  | @ -6,17 +6,25 @@ import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/oauth2" | ||||||
|  | 	"golang.org/x/oauth2/google" | ||||||
|  | 	"google.golang.org/api/admin/directory/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type GoogleProvider struct { | type GoogleProvider struct { | ||||||
| 	*ProviderData | 	*ProviderData | ||||||
| 	RedeemRefreshUrl *url.URL | 	RedeemRefreshUrl *url.URL | ||||||
|  | 	// GroupValidator is a function that determines if the passed email is in
 | ||||||
|  | 	// the configured Google group.
 | ||||||
|  | 	GroupValidator func(string) bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewGoogleProvider(p *ProviderData) *GoogleProvider { | func NewGoogleProvider(p *ProviderData) *GoogleProvider { | ||||||
|  | @ -42,7 +50,15 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider { | ||||||
| 	if p.Scope == "" { | 	if p.Scope == "" { | ||||||
| 		p.Scope = "profile email" | 		p.Scope = "profile email" | ||||||
| 	} | 	} | ||||||
| 	return &GoogleProvider{ProviderData: p} | 
 | ||||||
|  | 	return &GoogleProvider{ | ||||||
|  | 		ProviderData: p, | ||||||
|  | 		// Set a default GroupValidator to just always return valid (true), it will
 | ||||||
|  | 		// be overwritten if we configured a Google group restriction.
 | ||||||
|  | 		GroupValidator: func(email string) bool { | ||||||
|  | 			return true | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func emailFromIdToken(idToken string) (string, error) { | func emailFromIdToken(idToken string) (string, error) { | ||||||
|  | @ -139,6 +155,102 @@ func (p *GoogleProvider) Redeem(redirectUrl, code string) (s *SessionState, err | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // SetGroupRestriction configures the GoogleProvider to restrict access to the
 | ||||||
|  | // specified group(s). AdminEmail has to be an administrative email on the domain that is
 | ||||||
|  | // checked. CredentialsFile is the path to a json file containing a Google service
 | ||||||
|  | // account credentials.
 | ||||||
|  | func (p *GoogleProvider) SetGroupRestriction(groups []string, adminEmail string, credentialsReader io.Reader) { | ||||||
|  | 	adminService := getAdminService(adminEmail, credentialsReader) | ||||||
|  | 	p.GroupValidator = func(email string) bool { | ||||||
|  | 		return userInGroup(adminService, groups, email) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getAdminService(adminEmail string, credentialsReader io.Reader) *admin.Service { | ||||||
|  | 	data, err := ioutil.ReadAll(credentialsReader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal("can't read Google credentials file:", err) | ||||||
|  | 	} | ||||||
|  | 	conf, err := google.JWTConfigFromJSON(data, admin.AdminDirectoryUserReadonlyScope, admin.AdminDirectoryGroupReadonlyScope) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal("can't load Google credentials file:", err) | ||||||
|  | 	} | ||||||
|  | 	conf.Subject = adminEmail | ||||||
|  | 
 | ||||||
|  | 	client := conf.Client(oauth2.NoContext) | ||||||
|  | 	adminService, err := admin.New(client) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	return adminService | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func userInGroup(service *admin.Service, groups []string, email string) bool { | ||||||
|  | 	user, err := fetchUser(service, email) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("error fetching user: %v", err) | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	id := user.Id | ||||||
|  | 	custID := user.CustomerId | ||||||
|  | 
 | ||||||
|  | 	for _, group := range groups { | ||||||
|  | 		members, err := fetchGroupMembers(service, group) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Printf("error fetching group members: %v", err) | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, member := range members { | ||||||
|  | 			switch member.Type { | ||||||
|  | 			case "CUSTOMER": | ||||||
|  | 				if member.Id == custID { | ||||||
|  | 					return true | ||||||
|  | 				} | ||||||
|  | 			case "USER": | ||||||
|  | 				if member.Id == id { | ||||||
|  | 					return true | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func fetchUser(service *admin.Service, email string) (*admin.User, error) { | ||||||
|  | 	user, err := service.Users.Get(email).Do() | ||||||
|  | 	return user, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func fetchGroupMembers(service *admin.Service, group string) ([]*admin.Member, error) { | ||||||
|  | 	members := []*admin.Member{} | ||||||
|  | 	pageToken := "" | ||||||
|  | 	for { | ||||||
|  | 		req := service.Members.List(group) | ||||||
|  | 		if pageToken != "" { | ||||||
|  | 			req.PageToken(pageToken) | ||||||
|  | 		} | ||||||
|  | 		r, err := req.Do() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		for _, member := range r.Members { | ||||||
|  | 			members = append(members, member) | ||||||
|  | 		} | ||||||
|  | 		if r.NextPageToken == "" { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		pageToken = r.NextPageToken | ||||||
|  | 	} | ||||||
|  | 	return members, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ValidateGroup validates that the provided email exists in the configured Google
 | ||||||
|  | // group(s).
 | ||||||
|  | func (p *GoogleProvider) ValidateGroup(email string) bool { | ||||||
|  | 	return p.GroupValidator(email) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (p *GoogleProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) { | func (p *GoogleProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) { | ||||||
| 	if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" { | 	if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" { | ||||||
| 		return false, nil | 		return false, nil | ||||||
|  | @ -148,6 +260,12 @@ func (p *GoogleProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	// re-check that the user is in the proper google group(s)
 | ||||||
|  | 	if !p.ValidateGroup(s.Email) { | ||||||
|  | 		return false, fmt.Errorf("%s is no longer in the group(s)", s.Email) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	origExpiration := s.ExpiresOn | 	origExpiration := s.ExpiresOn | ||||||
| 	s.AccessToken = newToken | 	s.AccessToken = newToken | ||||||
| 	s.ExpiresOn = time.Now().Add(duration).Truncate(time.Second) | 	s.ExpiresOn = time.Now().Add(duration).Truncate(time.Second) | ||||||
|  |  | ||||||
|  | @ -105,6 +105,23 @@ func TestGoogleProviderGetEmailAddress(t *testing.T) { | ||||||
| 	assert.Equal(t, "refresh12345", session.RefreshToken) | 	assert.Equal(t, "refresh12345", session.RefreshToken) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestGoogleProviderValidateGroup(t *testing.T) { | ||||||
|  | 	p := newGoogleProvider() | ||||||
|  | 	p.GroupValidator = func(email string) bool { | ||||||
|  | 		return email == "michael.bland@gsa.gov" | ||||||
|  | 	} | ||||||
|  | 	assert.Equal(t, true, p.ValidateGroup("michael.bland@gsa.gov")) | ||||||
|  | 	p.GroupValidator = func(email string) bool { | ||||||
|  | 		return email != "michael.bland@gsa.gov" | ||||||
|  | 	} | ||||||
|  | 	assert.Equal(t, false, p.ValidateGroup("michael.bland@gsa.gov")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGoogleProviderWithoutValidateGroup(t *testing.T) { | ||||||
|  | 	p := newGoogleProvider() | ||||||
|  | 	assert.Equal(t, true, p.ValidateGroup("michael.bland@gsa.gov")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| //
 | //
 | ||||||
| func TestGoogleProviderGetEmailAddressInvalidEncoding(t *testing.T) { | func TestGoogleProviderGetEmailAddressInvalidEncoding(t *testing.T) { | ||||||
| 	p := newGoogleProvider() | 	p := newGoogleProvider() | ||||||
|  |  | ||||||
|  | @ -105,6 +105,12 @@ func (p *ProviderData) GetEmailAddress(s *SessionState) (string, error) { | ||||||
| 	return "", errors.New("not implemented") | 	return "", errors.New("not implemented") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ValidateGroup validates that the provided email exists in the configured provider
 | ||||||
|  | // email group(s).
 | ||||||
|  | func (p *ProviderData) ValidateGroup(email string) bool { | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (p *ProviderData) ValidateSessionState(s *SessionState) bool { | func (p *ProviderData) ValidateSessionState(s *SessionState) bool { | ||||||
| 	return validateToken(p, s.AccessToken, nil) | 	return validateToken(p, s.AccessToken, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ type Provider interface { | ||||||
| 	Data() *ProviderData | 	Data() *ProviderData | ||||||
| 	GetEmailAddress(*SessionState) (string, error) | 	GetEmailAddress(*SessionState) (string, error) | ||||||
| 	Redeem(string, string) (*SessionState, error) | 	Redeem(string, string) (*SessionState, error) | ||||||
|  | 	ValidateGroup(string) bool | ||||||
| 	ValidateSessionState(*SessionState) bool | 	ValidateSessionState(*SessionState) bool | ||||||
| 	GetLoginURL(redirectURI, finalRedirect string) string | 	GetLoginURL(redirectURI, finalRedirect string) string | ||||||
| 	RefreshSessionIfNeeded(*SessionState) (bool, error) | 	RefreshSessionIfNeeded(*SessionState) (bool, error) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue