Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issuer parameter added to the GenerateAuthUri method #40

Merged
merged 10 commits into from
Apr 29, 2024
29 changes: 29 additions & 0 deletions DuoUniversal.Tests/TestGenerateAuthUrl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,35 @@ public void TestSuccess(string username)
Assert.True(authUri.StartsWith($"https://{API_HOST}"));
}

[Test]
[TestCase(USERNAME)]
[TestCase("I iz a user")]
[TestCase("[email protected]")]
public void TestSuccessWithIssuer(string username)
{
Client clientWithIssuer = new ClientBuilder(CLIENT_ID, CLIENT_SECRET, API_HOST, REDIRECT_URI).UseAudienceIssuer("http://issuer").Build();
string authUri = clientWithIssuer.GenerateAuthUri(username, STATE);
Assert.True(Uri.IsWellFormedUriString(authUri, UriKind.Absolute));
Assert.True(authUri.StartsWith($"https://{API_HOST}"));
}

[Test]
[TestCase(" ")]
public void TestInvalidIssuer(string issuer)
{
Client clientWithIssuer = new ClientBuilder(CLIENT_ID, CLIENT_SECRET, API_HOST, REDIRECT_URI).UseAudienceIssuer(issuer).Build();
Assert.Throws<DuoException>(() => clientWithIssuer.GenerateAuthUri("username", STATE));
}

[Test]
[TestCase(null)]
public void TestNullIssuer(string issuer)
{
Client clientWithIssuer = new ClientBuilder(CLIENT_ID, CLIENT_SECRET, API_HOST, REDIRECT_URI).UseAudienceIssuer(issuer).Build();
string authUri = clientWithIssuer.GenerateAuthUri("username", STATE);
Assert.True(Uri.IsWellFormedUriString(authUri, UriKind.Absolute));
}

[Test]
[TestCase(null)]
[TestCase("")]
Expand Down
32 changes: 29 additions & 3 deletions DuoUniversal/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public class Client

internal bool UseDuoCodeAttribute { get; set; } = false;

internal string AudienceIssuer { get; set; } = null;

internal Client()
{
}
Expand Down Expand Up @@ -83,7 +85,7 @@ public async Task<bool> DoHealthCheck(bool handleException = true)
/// <returns>A URL to redirect the user's browser to</returns>
public string GenerateAuthUri(string username, string state)
{
ValidateAuthUriInputs(username, state);
ValidateAuthUriInputs(username, state, AudienceIssuer);

string authEndpoint = CustomizeApiUri(AUTH_ENDPOINT);

Expand Down Expand Up @@ -163,7 +165,7 @@ private string CustomizeApiUri(string baseUrl)
/// </summary>
/// <param name="username">The username to check</param>
/// <param name="state">The state value to check</param>
private void ValidateAuthUriInputs(string username, string state)
private void ValidateAuthUriInputs(string username, string state, string issuer)
{
if (string.IsNullOrWhiteSpace(username))
{
Expand All @@ -174,6 +176,10 @@ private void ValidateAuthUriInputs(string username, string state)
{
throw new DuoException($"state must be a non-empty string between {MINIMUM_STATE_LENGTH} and {MAXIMUM_STATE_LENGTH}.");
}
if (issuer != null && issuer.Trim().Length == 0)
{
throw new DuoException("issuer can be null, but cannot be an empty string");
}
}

/// <summary>
Expand All @@ -196,6 +202,12 @@ private string GenerateAuthJwt(string username, string state, string authEndpoin
// TODO support nonce
};

// issuer parameter is used for the Epic Hyperdrive integration only
if (AudienceIssuer != null)
{
additionalClaims[Labels.AUDIENCE_ISSUER] = AudienceIssuer;
}

if (UseDuoCodeAttribute)
{
additionalClaims[Labels.USE_DUO_CODE_ATTRIBUTE] = "true";
Expand Down Expand Up @@ -299,6 +311,7 @@ public class ClientBuilder
private bool _sslCertValidation = true;
private X509Certificate2Collection _customRoots = null;
private IWebProxy proxy = null;
private string _audienceIssuer = null;


// For testing only
Expand Down Expand Up @@ -411,6 +424,18 @@ public ClientBuilder UseHttpProxy(IWebProxy proxy)
return this;
}

/// <summary>
/// Set an audienceIssuer value to generate a SAML response for the Epic integration
/// </summary>
/// <param name="audienceIssuer">Specific parameter for the Epic integration for the SAML response generation</param>
/// <returns>The ClientBuilder</returns>
public ClientBuilder UseAudienceIssuer(string audienceIssuer)
{
_audienceIssuer = audienceIssuer;

return this;
}

/// <summary>
/// Build the Client based on the settings provided to the Builder
/// </summary>
Expand All @@ -425,7 +450,8 @@ public Client Build()
ClientSecret = _clientSecret,
ApiHost = _apiHost,
RedirectUri = _redirectUri,
UseDuoCodeAttribute = _useDuoCodeAttribute
UseDuoCodeAttribute = _useDuoCodeAttribute,
AudienceIssuer = _audienceIssuer
};

var httpClient = BuildHttpClient();
Expand Down
2 changes: 1 addition & 1 deletion DuoUniversal/DuoUniversal.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net471</TargetFrameworks>
<PackageId>DuoUniversal</PackageId>
<Version>1.2.3</Version>
<Version>1.2.4</Version>
<Authors>Duo Security</Authors>
<Company>Duo Security</Company>
<Copyright>Cisco Systems, Inc. and/or its affiliates</Copyright>
Expand Down
1 change: 1 addition & 0 deletions DuoUniversal/Labels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ internal class Labels
public const string DUO_UNAME = "duo_uname";
public const string PREFERRED_USERNAME = "preferred_username";
public const string USE_DUO_CODE_ATTRIBUTE = "use_duo_code_attribute";
public const string AUDIENCE_ISSUER = "issuer";
}
}
Loading