Skip to content

Commit

Permalink
feat(auth): enables OIDC auth code flow (#299)
Browse files Browse the repository at this point in the history
* Enable OIDC auth code flow

* Changes requested by hiranya911

* More changes requested by hiranya911

* Update FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Update FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Update FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Update FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfig.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs

Co-authored-by: Kevin Cheung <[email protected]>

* Fixing tests

* Fixing tests

Co-authored-by: Kevin Cheung <[email protected]>
  • Loading branch information
ScruffyProdigy and kevinthecheung authored Jun 16, 2021
1 parent d422935 commit d3a290a
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand All @@ -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);
Expand All @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> ListConfigsResponses = new List<string>()
Expand Down Expand Up @@ -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);
Expand All @@ -147,11 +155,14 @@ public async Task CreateConfig(ProviderTestConfig config)

var body = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(
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]
Expand Down Expand Up @@ -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);
Expand All @@ -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<JObject>(
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]
Expand Down Expand Up @@ -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<object[]>
Expand Down Expand Up @@ -682,6 +702,29 @@ public IEnumerator<object[]> 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();
Expand Down Expand Up @@ -754,6 +797,27 @@ public IEnumerator<object[]> 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();
Expand Down
33 changes: 33 additions & 0 deletions FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/// <summary>
Expand Down Expand Up @@ -63,13 +66,43 @@ internal OidcProviderConfig(Request request)
/// </summary>
public string Issuer { get; }

/// <summary>
/// Gets the client secret, which is used to verify Code response types.
/// </summary>
public string ClientSecret { get; }

/// <summary>
/// Gets a value indicating whether an ID Token response type will be provided.
/// </summary>
public bool IdTokenResponseType { get; }

/// <summary>
/// Gets a value indicating whether an Code type response type will be provided.
/// </summary>
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")]
internal string ClientId { get; set; }

[JsonProperty("issuer")]
internal string Issuer { get; set; }

[JsonProperty("clientSecret")]
internal string ClientSecret { get; set; }

[JsonProperty("responseType")]
internal ResponseTypeInfo ResponseType { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ public sealed class OidcProviderConfigArgs : AuthProviderConfigArgs<OidcProvider
/// </summary>
public string Issuer { get; set; }

/// <summary>
/// Gets or sets the client secret, which is used to verify Code response types.
/// </summary>
public string ClientSecret { get; set; }

/// <summary>
/// Gets or sets a value indicating whether this OIDC provider uses an ID Token response type.
/// </summary>
public bool? IdTokenResponseType { get; set; }

/// <summary>
/// Gets or sets a value indicating whether this OIDC provider uses a Code response type.
/// </summary>
public bool? CodeResponseType { get; set; }

internal override AuthProviderConfig.Request ToCreateRequest()
{
var req = new OidcProviderConfig.Request()
Expand All @@ -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.");
Expand All @@ -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;
}

Expand All @@ -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)
{
Expand All @@ -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;
}

Expand Down

0 comments on commit d3a290a

Please sign in to comment.