From 5ff0e1df65765e17927422a955b85d4583aef324 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Fri, 5 Nov 2021 13:37:59 -0400 Subject: [PATCH 1/4] fix(fcm): Fix typo in max number of tokens (#467) The API supports up to 500 tokens. Changing the comments in code to reflect that. b/197867415 --- snippets/messaging.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/snippets/messaging.go b/snippets/messaging.go index 129502ef..eec36504 100644 --- a/snippets/messaging.go +++ b/snippets/messaging.go @@ -111,7 +111,7 @@ func sendAll(ctx context.Context, client *messaging.Client) { registrationToken := "YOUR_REGISTRATION_TOKEN" // [START send_all] - // Create a list containing up to 100 messages. + // Create a list containing up to 500 messages. messages := []*messaging.Message{ { Notification: &messaging.Notification{ @@ -142,7 +142,7 @@ func sendAll(ctx context.Context, client *messaging.Client) { func sendMulticast(ctx context.Context, client *messaging.Client) { // [START send_multicast] - // Create a list containing up to 100 registration tokens. + // Create a list containing up to 500 registration tokens. // This registration tokens come from the client FCM SDKs. registrationTokens := []string{ "YOUR_REGISTRATION_TOKEN_1", @@ -170,7 +170,7 @@ func sendMulticast(ctx context.Context, client *messaging.Client) { func sendMulticastAndHandleErrors(ctx context.Context, client *messaging.Client) { // [START send_multicast_error] - // Create a list containing up to 100 registration tokens. + // Create a list containing up to 500 registration tokens. // This registration tokens come from the client FCM SDKs. registrationTokens := []string{ "YOUR_REGISTRATION_TOKEN_1", From 52dada8fe5634c1e2f9bf8b4531084e8ecc15d98 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 14 Dec 2021 04:47:21 -0800 Subject: [PATCH 2/4] feat(auth): enables OIDC Auth code flow (#468) Provides an option for developers to specify the OAuth response type for their OIDC provider (either one of these can be set:): id_token code (if set, must also set the client secret) RELEASE NOTES: Added support for configuring the authorization code flow for OIDC providers. --- auth/provider_config.go | 119 ++++++++++++++++++++++++----- auth/provider_config_test.go | 143 +++++++++++++++++++++++++++++------ 2 files changed, 223 insertions(+), 39 deletions(-) diff --git a/auth/provider_config.go b/auth/provider_config.go index 17a941d3..312fa349 100644 --- a/auth/provider_config.go +++ b/auth/provider_config.go @@ -38,11 +38,15 @@ const ( spEntityIDKey = "spConfig.spEntityId" callbackURIKey = "spConfig.callbackUri" - clientIDKey = "clientId" - issuerKey = "issuer" + clientIDKey = "clientId" + clientSecretKey = "clientSecret" + issuerKey = "issuer" displayNameKey = "displayName" enabledKey = "enabled" + + idTokenResponseTypeKey = "responseType.idToken" + codeResponseTypeKey = "responseType.code" ) type nestedMap map[string]interface{} @@ -113,11 +117,14 @@ func buildMask(data map[string]interface{}) []string { // OIDCProviderConfig is the OIDC auth provider configuration. // See https://openid.net/specs/openid-connect-core-1_0-final.html. type OIDCProviderConfig struct { - ID string - DisplayName string - Enabled bool - ClientID string - Issuer string + ID string + DisplayName string + Enabled bool + ClientID string + Issuer string + ClientSecret string + CodeResponseType bool + IDTokenResponseType bool } // OIDCProviderConfigToCreate represents the options used to create a new OIDCProviderConfig. @@ -152,6 +159,27 @@ func (config *OIDCProviderConfigToCreate) Enabled(enabled bool) *OIDCProviderCon return config.set(enabledKey, enabled) } +// ClientSecret sets the client secret for the new provider. +// This is required for the code flow. +func (config *OIDCProviderConfigToCreate) ClientSecret(secret string) *OIDCProviderConfigToCreate { + return config.set(clientSecretKey, secret) +} + +// IDTokenResponseType sets whether to enable the ID token response flow for the new provider. +// By default, this is enabled if no response type is specified. +// Having both the code and ID token response flows is currently not supported. +func (config *OIDCProviderConfigToCreate) IDTokenResponseType(enabled bool) *OIDCProviderConfigToCreate { + return config.set(idTokenResponseTypeKey, enabled) +} + +// CodeResponseType sets whether to enable the code response flow for the new provider. +// By default, this is not enabled if no response type is specified. +// A client secret must be set for this response type. +// Having both the code and ID token response flows is currently not supported. +func (config *OIDCProviderConfigToCreate) CodeResponseType(enabled bool) *OIDCProviderConfigToCreate { + return config.set(codeResponseTypeKey, enabled) +} + func (config *OIDCProviderConfigToCreate) set(key string, value interface{}) *OIDCProviderConfigToCreate { if config.params == nil { config.params = make(nestedMap) @@ -180,6 +208,19 @@ func (config *OIDCProviderConfigToCreate) buildRequest() (nestedMap, string, err return nil, "", fmt.Errorf("failed to parse Issuer: %v", err) } + if val, ok := config.params.Get(codeResponseTypeKey); ok && val.(bool) { + if val, ok := config.params.GetString(clientSecretKey); !ok || val == "" { + return nil, "", errors.New("Client Secret must not be empty for Code Response Type") + } + if val, ok := config.params.Get(idTokenResponseTypeKey); ok && val.(bool) { + return nil, "", errors.New("Only one response type may be chosen") + } + } else if ok && !val.(bool) { + if val, ok := config.params.Get(idTokenResponseTypeKey); ok && !val.(bool) { + return nil, "", errors.New("At least one response type must be returned") + } + } + return config.params, config.id, nil } @@ -213,6 +254,27 @@ func (config *OIDCProviderConfigToUpdate) Enabled(enabled bool) *OIDCProviderCon return config.set(enabledKey, enabled) } +// ClientSecret sets the client secret for the provider. +// This is required for the code flow. +func (config *OIDCProviderConfigToUpdate) ClientSecret(secret string) *OIDCProviderConfigToUpdate { + return config.set(clientSecretKey, secret) +} + +// IDTokenResponseType sets whether to enable the ID token response flow for the provider. +// By default, this is enabled if no response type is specified. +// Having both the code and ID token response flows is currently not supported. +func (config *OIDCProviderConfigToUpdate) IDTokenResponseType(enabled bool) *OIDCProviderConfigToUpdate { + return config.set(idTokenResponseTypeKey, enabled) +} + +// CodeResponseType sets whether to enable the code response flow for the new provider. +// By default, this is not enabled if no response type is specified. +// A client secret must be set for this response type. +// Having both the code and ID token response flows is currently not supported. +func (config *OIDCProviderConfigToUpdate) CodeResponseType(enabled bool) *OIDCProviderConfigToUpdate { + return config.set(codeResponseTypeKey, enabled) +} + func (config *OIDCProviderConfigToUpdate) set(key string, value interface{}) *OIDCProviderConfigToUpdate { if config.params == nil { config.params = make(nestedMap) @@ -240,6 +302,19 @@ func (config *OIDCProviderConfigToUpdate) buildRequest() (nestedMap, error) { } } + if val, ok := config.params.Get(codeResponseTypeKey); ok && val.(bool) { + if val, ok := config.params.GetString(clientSecretKey); !ok || val == "" { + return nil, errors.New("Client Secret must not be empty for Code Response Type") + } + if val, ok := config.params.Get(idTokenResponseTypeKey); ok && val.(bool) { + return nil, errors.New("Only one response type may be chosen") + } + } else if ok && !val.(bool) { + if val, ok := config.params.Get(idTokenResponseTypeKey); ok && !val.(bool) { + return nil, errors.New("At least one response type must be returned") + } + } + return config.params, nil } @@ -826,20 +901,30 @@ func (c *baseClient) makeRequest( } type oidcProviderConfigDAO struct { - Name string `json:"name"` - ClientID string `json:"clientId"` - Issuer string `json:"issuer"` - DisplayName string `json:"displayName"` - Enabled bool `json:"enabled"` + Name string `json:"name"` + ClientID string `json:"clientId"` + Issuer string `json:"issuer"` + DisplayName string `json:"displayName"` + Enabled bool `json:"enabled"` + ClientSecret string `json:"clientSecret"` + ResponseType oidcProviderResponseType `json:"responseType"` +} + +type oidcProviderResponseType struct { + Code bool `json:"code"` + IDToken bool `json:"idToken"` } func (dao *oidcProviderConfigDAO) toOIDCProviderConfig() *OIDCProviderConfig { return &OIDCProviderConfig{ - ID: extractResourceID(dao.Name), - DisplayName: dao.DisplayName, - Enabled: dao.Enabled, - ClientID: dao.ClientID, - Issuer: dao.Issuer, + ID: extractResourceID(dao.Name), + DisplayName: dao.DisplayName, + Enabled: dao.Enabled, + ClientID: dao.ClientID, + Issuer: dao.Issuer, + ClientSecret: dao.ClientSecret, + CodeResponseType: dao.ResponseType.Code, + IDTokenResponseType: dao.ResponseType.IDToken, } } diff --git a/auth/provider_config_test.go b/auth/provider_config_test.go index 7884375c..0defec12 100644 --- a/auth/provider_config_test.go +++ b/auth/provider_config_test.go @@ -33,7 +33,12 @@ const oidcConfigResponse = `{ "clientId": "CLIENT_ID", "issuer": "https://oidc.com/issuer", "displayName": "oidcProviderName", - "enabled": true + "enabled": true, + "clientSecret": "CLIENT_SECRET", + "responseType": { + "code": true, + "idToken": true + } }` const samlConfigResponse = `{ @@ -67,11 +72,14 @@ var idpCertsMap = []interface{}{ } var oidcProviderConfig = &OIDCProviderConfig{ - ID: "oidc.provider", - DisplayName: "oidcProviderName", - Enabled: true, - ClientID: "CLIENT_ID", - Issuer: "https://oidc.com/issuer", + ID: "oidc.provider", + DisplayName: "oidcProviderName", + Enabled: true, + ClientID: "CLIENT_ID", + Issuer: "https://oidc.com/issuer", + ClientSecret: "CLIENT_SECRET", + CodeResponseType: true, + IDTokenResponseType: true, } var samlProviderConfig = &SAMLProviderConfig{ @@ -157,7 +165,11 @@ func TestCreateOIDCProviderConfig(t *testing.T) { DisplayName(oidcProviderConfig.DisplayName). Enabled(oidcProviderConfig.Enabled). ClientID(oidcProviderConfig.ClientID). - Issuer(oidcProviderConfig.Issuer) + Issuer(oidcProviderConfig.Issuer). + ClientSecret(oidcProviderConfig.ClientSecret). + CodeResponseType(true). + IDTokenResponseType(false) + oidc, err := client.CreateOIDCProviderConfig(context.Background(), options) if err != nil { t.Fatal(err) @@ -168,10 +180,15 @@ func TestCreateOIDCProviderConfig(t *testing.T) { } wantBody := map[string]interface{}{ - "displayName": oidcProviderConfig.DisplayName, - "enabled": oidcProviderConfig.Enabled, - "clientId": oidcProviderConfig.ClientID, - "issuer": oidcProviderConfig.Issuer, + "displayName": oidcProviderConfig.DisplayName, + "enabled": oidcProviderConfig.Enabled, + "clientId": oidcProviderConfig.ClientID, + "issuer": oidcProviderConfig.Issuer, + "clientSecret": oidcProviderConfig.ClientSecret, + "responseType": map[string]interface{}{ + "code": true, + "idToken": false, + }, } if err := checkCreateOIDCConfigRequest(s, wantBody); err != nil { t.Fatal(err) @@ -186,7 +203,8 @@ func TestCreateOIDCProviderConfigMinimal(t *testing.T) { options := (&OIDCProviderConfigToCreate{}). ID(oidcProviderConfig.ID). ClientID(oidcProviderConfig.ClientID). - Issuer(oidcProviderConfig.Issuer) + Issuer(oidcProviderConfig.Issuer). + IDTokenResponseType(true) oidc, err := client.CreateOIDCProviderConfig(context.Background(), options) if err != nil { t.Fatal(err) @@ -197,8 +215,9 @@ func TestCreateOIDCProviderConfigMinimal(t *testing.T) { } wantBody := map[string]interface{}{ - "clientId": oidcProviderConfig.ClientID, - "issuer": oidcProviderConfig.Issuer, + "clientId": oidcProviderConfig.ClientID, + "issuer": oidcProviderConfig.Issuer, + "responseType": map[string]interface{}{"idToken": true}, } if err := checkCreateOIDCConfigRequest(s, wantBody); err != nil { t.Fatal(err) @@ -215,7 +234,9 @@ func TestCreateOIDCProviderConfigZeroValues(t *testing.T) { DisplayName(""). Enabled(false). ClientID(oidcProviderConfig.ClientID). - Issuer(oidcProviderConfig.Issuer) + Issuer(oidcProviderConfig.Issuer). + CodeResponseType(false). + IDTokenResponseType(true) oidc, err := client.CreateOIDCProviderConfig(context.Background(), options) if err != nil { t.Fatal(err) @@ -230,6 +251,10 @@ func TestCreateOIDCProviderConfigZeroValues(t *testing.T) { "enabled": false, "clientId": oidcProviderConfig.ClientID, "issuer": oidcProviderConfig.Issuer, + "responseType": map[string]interface{}{ + "code": false, + "idToken": true, + }, } if err := checkCreateOIDCConfigRequest(s, wantBody); err != nil { t.Fatal(err) @@ -246,7 +271,8 @@ func TestCreateOIDCProviderConfigError(t *testing.T) { options := (&OIDCProviderConfigToCreate{}). ID(oidcProviderConfig.ID). ClientID(oidcProviderConfig.ClientID). - Issuer(oidcProviderConfig.Issuer) + Issuer(oidcProviderConfig.Issuer). + IDTokenResponseType(true) oidc, err := client.CreateOIDCProviderConfig(context.Background(), options) if oidc != nil || !errorutils.IsInternal(err) { t.Errorf("CreateOIDCProviderConfig() = (%v, %v); want = (nil, %q)", oidc, err, "internal-error") @@ -303,6 +329,36 @@ func TestCreateOIDCProviderConfigInvalidInput(t *testing.T) { ClientID("CLIENT_ID"). Issuer("not a url"), }, + { + name: "MissingClientSecret", + want: "Client Secret must not be empty for Code Response Type", + conf: (&OIDCProviderConfigToCreate{}). + ID("oidc.provider"). + ClientID("CLIENT_ID"). + Issuer("https://oidc.com/issuer"). + CodeResponseType(true), + }, + { + name: "TwoResponseTypes", + want: "Only one response type may be chosen", + conf: (&OIDCProviderConfigToCreate{}). + ID("oidc.provider"). + ClientID("CLIENT_ID"). + Issuer("https://oidc.com/issuer"). + IDTokenResponseType(true). + CodeResponseType(true). + ClientSecret("secret"), + }, + { + name: "ZeroResponseTypes", + want: "At least one response type must be returned", + conf: (&OIDCProviderConfigToCreate{}). + ID("oidc.provider"). + ClientID("CLIENT_ID"). + Issuer("https://oidc.com/issuer"). + IDTokenResponseType(false). + CodeResponseType(false), + }, } client := &baseClient{} @@ -323,7 +379,10 @@ func TestUpdateOIDCProviderConfig(t *testing.T) { DisplayName(oidcProviderConfig.DisplayName). Enabled(oidcProviderConfig.Enabled). ClientID(oidcProviderConfig.ClientID). - Issuer(oidcProviderConfig.Issuer) + Issuer(oidcProviderConfig.Issuer). + ClientSecret(oidcProviderConfig.ClientSecret). + CodeResponseType(true). + IDTokenResponseType(false) oidc, err := client.UpdateOIDCProviderConfig(context.Background(), "oidc.provider", options) if err != nil { t.Fatal(err) @@ -334,16 +393,24 @@ func TestUpdateOIDCProviderConfig(t *testing.T) { } wantBody := map[string]interface{}{ - "displayName": oidcProviderConfig.DisplayName, - "enabled": oidcProviderConfig.Enabled, - "clientId": oidcProviderConfig.ClientID, - "issuer": oidcProviderConfig.Issuer, + "displayName": oidcProviderConfig.DisplayName, + "enabled": oidcProviderConfig.Enabled, + "clientId": oidcProviderConfig.ClientID, + "issuer": oidcProviderConfig.Issuer, + "clientSecret": oidcProviderConfig.ClientSecret, + "responseType": map[string]interface{}{ + "code": true, + "idToken": false, + }, } wantMask := []string{ "clientId", + "clientSecret", "displayName", "enabled", "issuer", + "responseType.code", + "responseType.idToken", } if err := checkUpdateOIDCConfigRequest(s, wantBody, wantMask); err != nil { t.Fatal(err) @@ -384,7 +451,9 @@ func TestUpdateOIDCProviderConfigZeroValues(t *testing.T) { client := s.Client options := (&OIDCProviderConfigToUpdate{}). DisplayName(""). - Enabled(false) + Enabled(false). + CodeResponseType(false). + IDTokenResponseType(true) oidc, err := client.UpdateOIDCProviderConfig(context.Background(), "oidc.provider", options) if err != nil { t.Fatal(err) @@ -397,10 +466,16 @@ func TestUpdateOIDCProviderConfigZeroValues(t *testing.T) { wantBody := map[string]interface{}{ "displayName": nil, "enabled": false, + "responseType": map[string]interface{}{ + "code": false, + "idToken": true, + }, } wantMask := []string{ "displayName", "enabled", + "responseType.code", + "responseType.idToken", } if err := checkUpdateOIDCConfigRequest(s, wantBody, wantMask); err != nil { t.Fatal(err) @@ -455,6 +530,30 @@ func TestUpdateOIDCProviderConfigInvalidInput(t *testing.T) { conf: (&OIDCProviderConfigToUpdate{}). Issuer("not a url"), }, + { + name: "MissingClientSecret", + want: "Client Secret must not be empty for Code Response Type", + conf: (&OIDCProviderConfigToUpdate{}). + Issuer("https://oidc.com/issuer"). + CodeResponseType(true), + }, + { + name: "TwoResponseTypes", + want: "Only one response type may be chosen", + conf: (&OIDCProviderConfigToUpdate{}). + Issuer("https://oidc.com/issuer"). + IDTokenResponseType(true). + CodeResponseType(true). + ClientSecret("secret"), + }, + { + name: "ZeroResponseTypes", + want: "At least one response type must be returned", + conf: (&OIDCProviderConfigToUpdate{}). + Issuer("https://oidc.com/issuer"). + IDTokenResponseType(false). + CodeResponseType(false), + }, } client := &baseClient{} From 56ce7a4a20b4b88c2893c880fdf56d2166b21283 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 15 Dec 2021 15:36:42 -0500 Subject: [PATCH 3/4] Fix integration tests for OIDC code flow (#472) --- .gitignore | 1 + integration/auth/provider_config_test.go | 63 ++++++++++++++++++++---- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 87b8a431..c50ffc81 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ testdata/integration_* .vscode/* *~ \#*\# +.DS_Store diff --git a/integration/auth/provider_config_test.go b/integration/auth/provider_config_test.go index 4d830de3..f0c9f285 100644 --- a/integration/auth/provider_config_test.go +++ b/integration/auth/provider_config_test.go @@ -45,11 +45,12 @@ type oidcProviderClient interface { func testOIDCProviderConfig(t *testing.T, client oidcProviderClient) { id := randomOIDCProviderID() want := &auth.OIDCProviderConfig{ - ID: id, - DisplayName: "OIDC_DISPLAY_NAME", - Enabled: true, - ClientID: "OIDC_CLIENT_ID", - Issuer: "https://oidc.com/issuer", + ID: id, + DisplayName: "OIDC_DISPLAY_NAME", + Enabled: true, + ClientID: "OIDC_CLIENT_ID", + Issuer: "https://oidc.com/issuer", + IDTokenResponseType: true, } req := (&auth.OIDCProviderConfigToCreate{}). @@ -117,10 +118,11 @@ func testOIDCProviderConfig(t *testing.T, client oidcProviderClient) { t.Run("UpdateOIDCProviderConfig()", func(t *testing.T) { want = &auth.OIDCProviderConfig{ - ID: id, - DisplayName: "UPDATED_OIDC_DISPLAY_NAME", - ClientID: "UPDATED_OIDC_CLIENT_ID", - Issuer: "https://oidc.com/updated_issuer", + ID: id, + DisplayName: "UPDATED_OIDC_DISPLAY_NAME", + ClientID: "UPDATED_OIDC_CLIENT_ID", + Issuer: "https://oidc.com/updated_issuer", + IDTokenResponseType: true, } req := (&auth.OIDCProviderConfigToUpdate{}). DisplayName("UPDATED_OIDC_DISPLAY_NAME"). @@ -137,6 +139,49 @@ func testOIDCProviderConfig(t *testing.T, client oidcProviderClient) { } }) + t.Run("UpdateOIDCProviderConfig() should be rejected with invalid oauth response type", func(t *testing.T) { + req := (&auth.OIDCProviderConfigToUpdate{}). + DisplayName("UPDATED_OIDC_DISPLAY_NAME"). + Enabled(false). + ClientID("UPDATED_OIDC_CLIENT_ID"). + Issuer("https://oidc.com/updated_issuer"). + IDTokenResponseType(false). + CodeResponseType(false). + ClientSecret("CLIENT_SECRET") + _, err := client.UpdateOIDCProviderConfig(context.Background(), id, req) + if err == nil { + t.Fatalf("UpdateOIDCProviderConfig(invalid_oauth_response_type) error nil; want not nil") + } + + if err.Error() != "At least one response type must be returned" { + t.Errorf( + "UpdateOIDCProviderConfig(invalid_oauth_response_type) returned an error of '%s'; "+ + "expected 'At least one response type must be returned'", + err.Error()) + } + }) + + t.Run("UpdateOIDCProviderConfig() should be rejected code flow with no client secret", func(t *testing.T) { + req := (&auth.OIDCProviderConfigToUpdate{}). + DisplayName("UPDATED_OIDC_DISPLAY_NAME"). + Enabled(false). + ClientID("UPDATED_OIDC_CLIENT_ID"). + Issuer("https://oidc.com/updated_issuer"). + IDTokenResponseType(false). + CodeResponseType(true) + _, err := client.UpdateOIDCProviderConfig(context.Background(), id, req) + if err == nil { + t.Fatalf("UpdateOIDCProviderConfig(code_flow_with_no_client_secret) error nil; want not nil") + } + + if err.Error() != "Client Secret must not be empty for Code Response Type" { + t.Errorf( + "UpdateOIDCProviderConfig(code_flow_with_no_client_secret) returned an error of '%s'; "+ + "expected 'Client Secret must not be empty for Code Response Type'", + err.Error()) + } + }) + t.Run("DeleteOIDCProviderConfig()", func(t *testing.T) { if err := client.DeleteOIDCProviderConfig(context.Background(), id); err != nil { t.Fatalf("DeleteOIDCProviderConfig() = %v", err) From ffc7617c2b30d92459794079212df5e11ecf4660 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 15 Dec 2021 16:50:49 -0500 Subject: [PATCH 4/4] [chore] Release 4.7.0 (#473) Bumped version to 4.7.0v --- firebase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase.go b/firebase.go index 0c46ba50..c4fa02e9 100644 --- a/firebase.go +++ b/firebase.go @@ -38,7 +38,7 @@ import ( var defaultAuthOverrides = make(map[string]interface{}) // Version of the Firebase Go Admin SDK. -const Version = "4.6.1" +const Version = "4.7.0" // firebaseEnvName is the name of the environment variable with the Config. const firebaseEnvName = "FIREBASE_CONFIG"