diff --git a/connector/google/google.go b/connector/google/google.go index 1b515b8e42..4299771333 100644 --- a/connector/google/google.go +++ b/connector/google/google.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "os" + "regexp" "time" "github.com/coreos/go-oidc/v3/oidc" @@ -40,6 +41,9 @@ type Config struct { // If this field is nonempty, only users from a listed group will be allowed to log in Groups []string `json:"groups"` + // Optional field to filter groups by regexp + GroupsFilter string `json:"groupsFilter"` + // Optional path to service account json // If nonempty, and groups claim is made, will use authentication from file to // check groups with the admin directory api @@ -77,6 +81,18 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e return nil, fmt.Errorf("could not create directory service: %v", err) } + var groupsFilter *regexp.Regexp + if c.GroupsFilter != "" { + var err error + groupsFilter, err = regexp.Compile(c.GroupsFilter) + if err != nil { + cancel() + return nil, fmt.Errorf("unable to compile the groupsFilter regexp. "+ + "Input string: %q; error: %v", c.GroupsFilter, err, + ) + } + } + clientID := c.ClientID return &googleConnector{ redirectURI: c.RedirectURI, @@ -94,6 +110,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e cancel: cancel, hostedDomains: c.HostedDomains, groups: c.Groups, + groupsFilter: groupsFilter, serviceAccountFilePath: c.ServiceAccountFilePath, adminEmail: c.AdminEmail, fetchTransitiveGroupMembership: c.FetchTransitiveGroupMembership, @@ -114,6 +131,7 @@ type googleConnector struct { logger log.Logger hostedDomains []string groups []string + groupsFilter *regexp.Regexp serviceAccountFilePath string adminEmail string fetchTransitiveGroupMembership bool @@ -257,8 +275,10 @@ func (c *googleConnector) getGroups(email string, fetchTransitiveGroupMembership } for _, group := range groupsList.Groups { - // TODO (joelspeed): Make desired group key configurable - userGroups = append(userGroups, group.Email) + if c.groupsFilter == nil || c.groupsFilter.MatchString(group.Email) { + // TODO (joelspeed): Make desired group key configurable + userGroups = append(userGroups, group.Email) + } // getGroups takes a user's email/alias as well as a group's email/alias if fetchTransitiveGroupMembership { diff --git a/server/handlers.go b/server/handlers.go index acbf4ecb73..6578a12e47 100755 --- a/server/handlers.go +++ b/server/handlers.go @@ -765,6 +765,8 @@ func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) { s.withClientFromStorage(w, r, s.handleRefreshToken) case grantTypePassword: s.withClientFromStorage(w, r, s.handlePasswordGrant) + case grantTypeClientCredentials: + s.withClientFromStorage(w, r, s.handleClientCredentialsGrant) default: s.tokenErrHelper(w, errUnsupportedGrantType, "", http.StatusBadRequest) } @@ -1015,6 +1017,29 @@ func (s *Server) handleUserInfo(w http.ResponseWriter, r *http.Request) { w.Write(claims) } +func (s *Server) handleClientCredentialsGrant(w http.ResponseWriter, r *http.Request, client storage.Client) { + if err := r.ParseForm(); err != nil { + s.tokenErrHelper(w, errInvalidRequest, "Couldn't parse data", http.StatusBadRequest) + return + } + q := r.Form + + nonce := q.Get("nonce") + scopes := strings.Fields(q.Get("scope")) + + claims := storage.Claims{UserID: client.ID} + + accessToken := storage.NewID() + idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, nonce, accessToken, "", "client") + if err != nil { + s.tokenErrHelper(w, errServerError, fmt.Sprintf("failed to create ID token: %v", err), http.StatusInternalServerError) + return + } + + resp := s.toAccessTokenResponse(idToken, accessToken, "", expiry) + s.writeAccessToken(w, resp) +} + func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, client storage.Client) { // Parse the fields if err := r.ParseForm(); err != nil { diff --git a/server/oauth2.go b/server/oauth2.go index 23f06b8258..088aecf5ad 100644 --- a/server/oauth2.go +++ b/server/oauth2.go @@ -130,6 +130,7 @@ const ( grantTypeRefreshToken = "refresh_token" grantTypePassword = "password" grantTypeDeviceCode = "urn:ietf:params:oauth:grant-type:device_code" + grantTypeClientCredentials = "client_credentials" ) const (