@@ -6,17 +6,25 @@ import (
66 "encoding/json"
77 "errors"
88 "fmt"
9+ "io"
910 "io/ioutil"
1011 "log"
1112 "net/http"
1213 "net/url"
1314 "strings"
1415 "time"
16+
17+ "golang.org/x/oauth2"
18+ "golang.org/x/oauth2/google"
19+ "google.golang.org/api/admin/directory/v1"
1520)
1621
1722type GoogleProvider struct {
1823 *ProviderData
1924 RedeemRefreshUrl *url.URL
25+ // GroupValidator is a function that determines if the passed email is in
26+ // the configured Google group.
27+ GroupValidator func(string) bool
2028}
2129
2230func NewGoogleProvider(p *ProviderData) *GoogleProvider {
@@ -42,7 +50,15 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider {
4250 if p.Scope == "" {
4351 p.Scope = "profile email"
4452 }
45− return &GoogleProvider{ProviderData: p}
53+
54+ return &GoogleProvider{
55+ ProviderData: p,
56+ // Set a default GroupValidator to just always return valid (true), it will
57+ // be overwritten if we configured a Google group restriction.
58+ GroupValidator: func(email string) bool {
59+ return true
60+ },
61+ }
4662}
4763
4864func emailFromIdToken(idToken string) (string, error) {
@@ -139,6 +155,102 @@ func (p *GoogleProvider) Redeem(redirectUrl, code string) (s *SessionState, err
139155 return
140156}
141157
158+// SetGroupRestriction configures the GoogleProvider to restrict access to the
159+// specified group(s). AdminEmail has to be an administrative email on the domain that is
160+// checked. CredentialsFile is the path to a json file containing a Google service
161+// account credentials.
162+func (p *GoogleProvider) SetGroupRestriction(groups []string, adminEmail string, credentialsReader io.Reader) {
163+ adminService := getAdminService(adminEmail, credentialsReader)
164+ p.GroupValidator = func(email string) bool {
165+ return userInGroup(adminService, groups, email)
166+ }
167+}
168+
169+func getAdminService(adminEmail string, credentialsReader io.Reader) *admin.Service {
170+ data, err := ioutil.ReadAll(credentialsReader)
171+ if err != nil {
172+ log.Fatal("can't read Google credentials file:", err)
173+ }
174+ conf, err := google.JWTConfigFromJSON(data, admin.AdminDirectoryUserReadonlyScope, admin.AdminDirectoryGroupReadonlyScope)
175+ if err != nil {
176+ log.Fatal("can't load Google credentials file:", err)
177+ }
178+ conf.Subject = adminEmail
179+
180+ client := conf.Client(oauth2.NoContext)
181+ adminService, err := admin.New(client)
182+ if err != nil {
183+ log.Fatal(err)
184+ }
185+ return adminService
186+}
187+
188+func userInGroup(service *admin.Service, groups []string, email string) bool {
189+ user, err := fetchUser(service, email)
190+ if err != nil {
191+ log.Printf("error fetching user: %v", err)
192+ return false
193+ }
194+ id := user.Id
195+ custID := user.CustomerId
196+
197+ for _, group := range groups {
198+ members, err := fetchGroupMembers(service, group)
199+ if err != nil {
200+ log.Printf("error fetching group members: %v", err)
201+ return false
202+ }
203+
204+ for _, member := range members {
205+ switch member.Type {
206+ case "CUSTOMER":
207+ if member.Id == custID {
208+ return true
209+ }
210+ case "USER":
211+ if member.Id == id {
212+ return true
213+ }
214+ }
215+ }
216+ }
217+ return false
218+}
219+
220+func fetchUser(service *admin.Service, email string) (*admin.User, error) {
221+ user, err := service.Users.Get(email).Do()
222+ return user, err
223+}
224+
225+func fetchGroupMembers(service *admin.Service, group string) ([]*admin.Member, error) {
226+ members := []*admin.Member{}
227+ pageToken := ""
228+ for {
229+ req := service.Members.List(group)
230+ if pageToken != "" {
231+ req.PageToken(pageToken)
232+ }
233+ r, err := req.Do()
234+ if err != nil {
235+ return nil, err
236+ }
237+ for _, member := range r.Members {
238+ members = append(members, member)
239+ }
240+ if r.NextPageToken == "" {
241+ break
242+ }
243+ pageToken = r.NextPageToken
244+ }
245+ return members, nil
246+}
247+
248+// ValidateGroup validates that the provided email exists in the configured Google
249+// group(s).
250+func (p *GoogleProvider) ValidateGroup(email string) bool {
251+ return p.GroupValidator(email)
252+}
253+
142254func (p *GoogleProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
143255 if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
144256 return false, nil
@@ -148,6 +260,12 @@ func (p *GoogleProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
148260 if err != nil {
149261 return false, err
150262 }
263+
264+ // re-check that the user is in the proper google group(s)
265+ if !p.ValidateGroup(s.Email) {
266+ return false, fmt.Errorf("%s is no longer in the group(s)", s.Email)
267+ }
268+
151269 origExpiration := s.ExpiresOn
152270 s.AccessToken = newToken
153271 s.ExpiresOn = time.Now().Add(duration).Truncate(time.Second)