diff --git a/FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/AbstractOidcProviderConfigTest.cs b/FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/AbstractOidcProviderConfigTest.cs index f124bf92..caf15058 100644 --- a/FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/AbstractOidcProviderConfigTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/AbstractOidcProviderConfigTest.cs @@ -46,6 +46,9 @@ public void CreateProviderConfig() Assert.True(config.Enabled); Assert.Equal("OIDC_CLIENT_ID", config.ClientId); Assert.Equal("https://oidc.com/issuer", config.Issuer); + Assert.Equal("OIDC_CLIENT_SECRET", config.ClientSecret); + Assert.True(config.CodeResponseType); + Assert.False(config.IdTokenResponseType); } [Fact] @@ -59,6 +62,9 @@ public async Task GetProviderConfig() Assert.True(config.Enabled); Assert.Equal("OIDC_CLIENT_ID", config.ClientId); Assert.Equal("https://oidc.com/issuer", config.Issuer); + Assert.Equal("OIDC_CLIENT_SECRET", config.ClientSecret); + Assert.True(config.CodeResponseType); + Assert.False(config.IdTokenResponseType); } [Fact] @@ -84,6 +90,9 @@ public async Task ListProviderConfig() Assert.True(config.Enabled); Assert.Equal("OIDC_CLIENT_ID", config.ClientId); Assert.Equal("https://oidc.com/issuer", config.Issuer); + Assert.Equal("OIDC_CLIENT_SECRET", config.ClientSecret); + Assert.True(config.CodeResponseType); + Assert.False(config.IdTokenResponseType); } [Fact] @@ -97,6 +106,9 @@ public async Task UpdateProviderConfig() Enabled = false, ClientId = "UPDATED_OIDC_CLIENT_ID", Issuer = "https://oidc.com/updated-issuer", + ClientSecret = "UPDATED_OIDC_CLIENT_SECRET", + CodeResponseType = false, + IdTokenResponseType = true, }; var config = await this.auth.UpdateProviderConfigAsync(args); @@ -106,6 +118,9 @@ public async Task UpdateProviderConfig() Assert.False(config.Enabled); Assert.Equal("UPDATED_OIDC_CLIENT_ID", config.ClientId); Assert.Equal("https://oidc.com/updated-issuer", config.Issuer); + Assert.Equal("UPDATED_OIDC_CLIENT_SECRET", config.ClientSecret); + Assert.False(config.CodeResponseType); + Assert.True(config.IdTokenResponseType); } [Fact] diff --git a/FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/OidcProviderConfigFixture.cs b/FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/OidcProviderConfigFixture.cs index 2b3b0d25..3d4aecc4 100644 --- a/FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/OidcProviderConfigFixture.cs +++ b/FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/OidcProviderConfigFixture.cs @@ -31,6 +31,9 @@ public OidcProviderConfigFixture() Enabled = true, ClientId = "OIDC_CLIENT_ID", Issuer = "https://oidc.com/issuer", + ClientSecret = "OIDC_CLIENT_SECRET", + CodeResponseType = true, + IdTokenResponseType = false, }; this.ProviderConfig = this.Auth.CreateProviderConfigAsync(args).Result; } diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs index 3286ce3e..d68094f5 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs @@ -33,7 +33,12 @@ public class OidcProviderConfigTest ""clientId"": ""CLIENT_ID"", ""issuer"": ""https://oidc.com/issuer"", ""displayName"": ""oidcProviderName"", - ""enabled"": true + ""enabled"": true, + ""clientSecret"": ""CLIENT_SECRET"", + ""responseType"": { + ""code"": true, + ""idToken"": true + } }"; private static readonly IList ListConfigsResponses = new List() @@ -135,6 +140,9 @@ public async Task CreateConfig(ProviderTestConfig config) Enabled = true, ClientId = "CLIENT_ID", Issuer = "https://oidc.com/issuer", + ClientSecret = "CLIENT_SECRET", + CodeResponseType = true, + IdTokenResponseType = true, }; var provider = await auth.CreateProviderConfigAsync(args); @@ -147,11 +155,14 @@ public async Task CreateConfig(ProviderTestConfig config) var body = NewtonsoftJsonSerializer.Instance.Deserialize( handler.LastRequestBody); - Assert.Equal(4, body.Count); + Assert.Equal(6, body.Count); Assert.Equal("oidcProviderName", body["displayName"]); Assert.True((bool)body["enabled"]); Assert.Equal("CLIENT_ID", body["clientId"]); Assert.Equal("https://oidc.com/issuer", body["issuer"]); + Assert.Equal("CLIENT_SECRET", body["clientSecret"]); + Assert.True((bool)body["responseType"]["code"]); + Assert.True((bool)body["responseType"]["idToken"]); } [Theory] @@ -252,6 +263,9 @@ public async Task UpdateConfig(ProviderTestConfig config) Enabled = true, ClientId = "CLIENT_ID", Issuer = "https://oidc.com/issuer", + ClientSecret = "CLIENT_SECRET", + CodeResponseType = true, + IdTokenResponseType = true, }; var provider = await auth.UpdateProviderConfigAsync(args); @@ -260,16 +274,19 @@ public async Task UpdateConfig(ProviderTestConfig config) Assert.Equal(1, handler.Requests.Count); var request = handler.Requests[0]; Assert.Equal(ProviderTestConfig.PatchMethod, request.Method); - var mask = "clientId,displayName,enabled,issuer"; + var mask = "clientId,clientSecret,displayName,enabled,issuer,responseType.code,responseType.idToken"; config.AssertRequest($"oauthIdpConfigs/oidc.provider?updateMask={mask}", request); var body = NewtonsoftJsonSerializer.Instance.Deserialize( handler.LastRequestBody); - Assert.Equal(4, body.Count); + Assert.Equal(6, body.Count); Assert.Equal("oidcProviderName", body["displayName"]); Assert.True((bool)body["enabled"]); Assert.Equal("CLIENT_ID", body["clientId"]); Assert.Equal("https://oidc.com/issuer", body["issuer"]); + Assert.Equal("CLIENT_SECRET", body["clientSecret"]); + Assert.True((bool)body["responseType"]["code"]); + Assert.True((bool)body["responseType"]["idToken"]); } [Theory] @@ -621,6 +638,9 @@ private void AssertOidcProviderConfig(OidcProviderConfig provider) Assert.True(provider.Enabled); Assert.Equal("CLIENT_ID", provider.ClientId); Assert.Equal("https://oidc.com/issuer", provider.Issuer); + Assert.Equal("CLIENT_SECRET", provider.ClientSecret); + Assert.True(provider.CodeResponseType); + Assert.True(provider.IdTokenResponseType); } public class InvalidCreateArgs : IEnumerable @@ -682,6 +702,29 @@ public IEnumerator MakeEnumerator() }, "Malformed issuer string: not a url", }; + yield return new object[] + { + new OidcProviderConfigArgs() + { + ProviderId = "oidc.provider", + ClientId = "CLIENT_ID", + Issuer = "https://oidc.com/issuer", + CodeResponseType = true, + }, + "Client secret must not be null or empty for code response type.", + }; + yield return new object[] + { + new OidcProviderConfigArgs() + { + ProviderId = "oidc.provider", + ClientId = "CLIENT_ID", + Issuer = "https://oidc.com/issuer", + CodeResponseType = false, + IdTokenResponseType = false, + }, + "At least one response type must be returned.", + }; } IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); @@ -754,6 +797,27 @@ public IEnumerator MakeEnumerator() }, "Malformed issuer string: not a url", }; + yield return new object[] + { + new OidcProviderConfigArgs() + { + ProviderId = "oidc.provider", + Issuer = "https://oidc.com/issuer", + CodeResponseType = true, + }, + "Client secret must not be null or empty for code response type.", + }; + yield return new object[] + { + new OidcProviderConfigArgs() + { + ProviderId = "oidc.provider", + Issuer = "https://oidc.com/issuer", + CodeResponseType = false, + IdTokenResponseType = false, + }, + "At least one response type must be returned.", + }; } IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); diff --git a/FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfig.cs b/FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfig.cs index d72f4342..7fcc04e5 100644 --- a/FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfig.cs +++ b/FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfig.cs @@ -27,6 +27,9 @@ internal OidcProviderConfig(Request request) { this.ClientId = request.ClientId; this.Issuer = request.Issuer; + this.ClientSecret = request.ClientSecret; + this.IdTokenResponseType = request.ResponseType.IdToken == true; + this.CodeResponseType = request.ResponseType.Code == true; } /// @@ -63,6 +66,30 @@ internal OidcProviderConfig(Request request) /// public string Issuer { get; } + /// + /// Gets the client secret, which is used to verify Code response types. + /// + public string ClientSecret { get; } + + /// + /// Gets a value indicating whether an ID Token response type will be provided. + /// + public bool IdTokenResponseType { get; } + + /// + /// Gets a value indicating whether an Code type response type will be provided. + /// + public bool CodeResponseType { get; } + + internal sealed class ResponseTypeInfo + { + [JsonProperty("code")] + internal bool? Code { get; set; } + + [JsonProperty("idToken")] + internal bool? IdToken { get; set; } + } + internal sealed new class Request : AuthProviderConfig.Request { [JsonProperty("clientId")] @@ -70,6 +97,12 @@ internal OidcProviderConfig(Request request) [JsonProperty("issuer")] internal string Issuer { get; set; } + + [JsonProperty("clientSecret")] + internal string ClientSecret { get; set; } + + [JsonProperty("responseType")] + internal ResponseTypeInfo ResponseType { get; set; } } } } diff --git a/FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs b/FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs index 0d756e83..e1c8e28a 100644 --- a/FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs +++ b/FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs @@ -56,6 +56,21 @@ public sealed class OidcProviderConfigArgs : AuthProviderConfigArgs public string Issuer { get; set; } + /// + /// Gets or sets the client secret, which is used to verify Code response types. + /// + public string ClientSecret { get; set; } + + /// + /// Gets or sets a value indicating whether this OIDC provider uses an ID Token response type. + /// + public bool? IdTokenResponseType { get; set; } + + /// + /// Gets or sets a value indicating whether this OIDC provider uses a Code response type. + /// + public bool? CodeResponseType { get; set; } + internal override AuthProviderConfig.Request ToCreateRequest() { var req = new OidcProviderConfig.Request() @@ -64,7 +79,17 @@ internal override AuthProviderConfig.Request ToCreateRequest() Enabled = this.Enabled, ClientId = this.ClientId, Issuer = this.Issuer, + ClientSecret = this.ClientSecret, }; + if (this.CodeResponseType != null || this.IdTokenResponseType != null) + { + req.ResponseType = new OidcProviderConfig.ResponseTypeInfo() + { + Code = this.CodeResponseType, + IdToken = this.IdTokenResponseType, + }; + } + if (string.IsNullOrEmpty(req.ClientId)) { throw new ArgumentException("Client ID must not be null or empty."); @@ -79,6 +104,16 @@ internal override AuthProviderConfig.Request ToCreateRequest() throw new ArgumentException($"Malformed issuer string: {req.Issuer}"); } + if (req.ResponseType?.Code == true && string.IsNullOrEmpty(req.ClientSecret)) + { + throw new ArgumentException("Client secret must not be null or empty for code response type."); + } + + if (req.ResponseType?.Code == false && req.ResponseType?.IdToken == false) + { + throw new ArgumentException("At least one response type must be returned."); + } + return req; } @@ -90,7 +125,16 @@ internal override AuthProviderConfig.Request ToUpdateRequest() Enabled = this.Enabled, ClientId = this.ClientId, Issuer = this.Issuer, + ClientSecret = this.ClientSecret, }; + if (this.CodeResponseType != null || this.IdTokenResponseType != null) + { + req.ResponseType = new OidcProviderConfig.ResponseTypeInfo() + { + Code = this.CodeResponseType, + IdToken = this.IdTokenResponseType, + }; + } if (req.ClientId == string.Empty) { @@ -106,6 +150,16 @@ internal override AuthProviderConfig.Request ToUpdateRequest() throw new ArgumentException($"Malformed issuer string: {req.Issuer}"); } + if (req.ResponseType?.Code == true && string.IsNullOrEmpty(req.ClientSecret)) + { + throw new ArgumentException("Client secret must not be null or empty for code response type."); + } + + if (req.ResponseType?.Code == false && req.ResponseType?.IdToken == false) + { + throw new ArgumentException("At least one response type must be returned."); + } + return req; }