Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/net-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ on:
push:
branches:
- master
- main
- feature/*
pull_request:
branches:
- master


jobs:
# Build and test on .NET Core
dotnet-core-ci:
Expand All @@ -18,9 +18,9 @@ jobs:
- uses: actions/checkout@v2

- name: Set up .NET
uses: actions/setup-dotnet@v1.7.2
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ matrix.dotnet-version }}
dotnet-version: 6.0.x

- name: Install dependencies
run: nuget restore
Expand Down Expand Up @@ -55,4 +55,4 @@ jobs:
run: msbuild.exe duo_api_csharp.sln

- name: Run Tests dll
run: vstest.console.exe .\test\bin\Debug\DuoApiTest.dll
run: vstest.console.exe .\DuoApi.Tests\bin\Debug\net6.0\DuoApi.Tests.dll
14 changes: 14 additions & 0 deletions DuoApi.Examples/DuoApi.Examples.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\DuoApi\DuoApi.csproj" />
</ItemGroup>

</Project>
20 changes: 7 additions & 13 deletions examples/Program.cs → DuoApi.Examples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ static int Main(string[] args)
var r = client.JSONApiCall<Dictionary<string, object>>(
"GET", "/admin/v1/info/authentication_attempts", parameters);
var attempts = r["authentication_attempts"] as Dictionary<string, object>;
foreach (KeyValuePair<string, object> info in attempts)
{
foreach (KeyValuePair<string, object> info in attempts) {
var s = String.Format("{0} authentication(s) ended with {1}.",
info.Value,
info.Key);
Expand All @@ -35,28 +34,23 @@ static int Main(string[] args)
var users = client.JSONApiCall<System.Collections.ArrayList>(
"GET", "/admin/v1/users", parameters);
System.Console.WriteLine(String.Format("{0} users.", users.Count));
foreach (Dictionary<string, object> user in users)
{
foreach (Dictionary<string, object> user in users) {
System.Console.WriteLine(
"\t" + "Username: " + (user["username"] as string));
}

// paging call
int? offset = 0;
while (offset != null)
{
var jsonResponse = client.JSONPagingApiCall("GET", "/admin/v1/users", parameters, (int)offset, 10);
var pagedUsers = jsonResponse["response"] as System.Collections.ArrayList;
while (offset != null) {
var pagedUsers = client.JSONPagingApiCall<System.Collections.ArrayList>("GET", "/admin/v1/users", parameters, (int)offset, 10, out var metadata);
System.Console.WriteLine(String.Format("{0} users at offset {1}", pagedUsers.Count, offset));
foreach (Dictionary<string, object> user in pagedUsers)
{
foreach (Dictionary<string, object> user in pagedUsers) {
System.Console.WriteLine(
"\t" + "Username: " + (user["username"] as string));
}
var metadata = jsonResponse["metadata"] as Dictionary<string, object>;
if (metadata.ContainsKey("next_offset"))
if (metadata.next_offset.HasValue)
{
offset = metadata["next_offset"] as int?;
offset = metadata.next_offset.Value;
}
else
{
Expand Down
20 changes: 10 additions & 10 deletions test/ApiCallTest.cs → DuoApi.Tests/ApiCallTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -391,10 +391,10 @@ public void TestValidJsonPagingResponseNoParameters()
return "{\"stat\": \"OK\", \"response\": \"hello, world!\", \"metadata\": {\"next_offset\":10}}";
};
var parameters = new Dictionary<string, string>();
var jsonResponse = api.JSONPagingApiCall("GET", "/json_ok", parameters, 0, 10);
Assert.Equal("hello, world!", jsonResponse["response"]);
var metadata = jsonResponse["metadata"] as Dictionary<string, object>;
Assert.Equal(10, metadata["next_offset"]);
var jsonResponse = api.JSONPagingApiCall<string>("GET", "/json_ok", parameters, 0, 10, out var metadata);
Assert.Equal("hello, world!", jsonResponse);

Assert.Equal(10, metadata.next_offset.Value);
// make sure parameters was not changed as a side-effect
Assert.Empty(parameters);
}
Expand All @@ -411,10 +411,10 @@ public void TestValidJsonPagingResponseExistingParameters()
{"offset", "0"},
{"limit", "10"}
};
var jsonResponse = api.JSONPagingApiCall("GET", "/json_ok", parameters, 10, 20);
Assert.Equal("hello, world!", jsonResponse["response"]);
var metadata = jsonResponse["metadata"] as Dictionary<string, object>;
Assert.False(metadata.ContainsKey("next_offset"));
var jsonResponse = api.JSONPagingApiCall<string>("GET", "/json_ok", parameters, 10, 20, out var metadata);
Assert.Equal("hello, world!", jsonResponse);

Assert.NotNull(metadata);
// make sure parameters was not changed as a side-effect
Assert.Equal(2, parameters.Count);
Assert.Equal("0", parameters["offset"]);
Expand Down Expand Up @@ -461,7 +461,7 @@ public void TestJsonResponseMissingField()
});

Assert.NotNull(ex);
var e = Assert.IsType<BadResponseException>(ex);
var e = Assert.IsType<ApiException>(ex);

Assert.Equal(400, e.HttpStatus);

Expand Down Expand Up @@ -527,7 +527,7 @@ public void TestRateLimitedCompletely()
HttpStatusCode code;
string response = api.ApiCall("GET", "/hello", new Dictionary<string, string>(), 10000, out code);

Assert.Equal(code, (HttpStatusCode)429);
Assert.Equal((HttpStatusCode)429, code);
Assert.Equal(7, callCount);
Assert.Equal(6, api.sleeper.sleepCalls.Count);
Assert.Equal(1123, api.sleeper.sleepCalls[0]);
Expand Down
File renamed without changes.
31 changes: 31 additions & 0 deletions DuoApi.Tests/DuoApi.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>

<IsPackable>false</IsPackable>

<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>

<EnableNETAnalyzers>True</EnableNETAnalyzers>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DuoApi\DuoApi.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,4 @@ public void NextOffsetTest()
var expected = "foo=1&next_offset=fjaewoifjew&next_offset=473891274832917498";
Assert.Equal(expected, DuoApi.CanonicalizeParams(parameters));
}
}
}
2 changes: 1 addition & 1 deletion test/SigningTest.cs → DuoApi.Tests/SigningTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ public void HmacSha512()
var expected = "Basic dGVzdF9pa2V5OjA1MDgwNjUwMzVhMDNiMmExZGUyZjQ1M2U2MjllNzkxZDE4MDMyOWUxNTdmNjVkZjZiM2UwZjA4Mjk5ZDQzMjFlMWM1YzdhN2M3ZWU2YjllNWZjODBkMWZiNmZiZjNhZDVlYjdjNDRkZDNiMzk4NWEwMmMzN2FjYTUzZWMzNjk4";
Assert.Equal(expected, actual);
}
}
}
103 changes: 103 additions & 0 deletions DuoApi.Tests/TestRealAPICall.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Duo;
using System;
using System.Linq;
using System.Text.Json;
using Xunit;

public class TestRealAPICall
{
/// <summary>
/// Secrets should not be committed, a true integration test cannot be be performed without these.
/// </summary>
private const string test_ikey = "INTEGRATION KEY";
private const string test_skey = "SECRET KEY";
private const string test_host = "api-.duosecurity.com";

private DuoApi api;

/// <summary>
///
/// </summary>
public TestRealAPICall()
{
api = new DuoApi(test_ikey, test_skey, test_host);
}

[SkippableTheory]
[InlineData(1)]
[InlineData(1, 1)]
[InlineData(400)]
public void GetUsers(ushort pagesize, ushort offset = 0)
{
Skip.If(test_ikey == "INTEGRATION KEY", "The keys are not configure");
Skip.If(test_skey == "SECRET KEY", "The keys are not configure");

//arrange
//act
var users = api.GetUsers(pagesize, out var pagingInfo, offset);

//assert
Console.WriteLine($"{users.Length:n0} users");
Console.WriteLine(JsonSerializer.Serialize(users, new JsonSerializerOptions() { WriteIndented = true }));

Assert.True(users.Length <= pagesize);
}

[SkippableFact]
public void GetAllTest()
{
Skip.If(test_ikey == "INTEGRATION KEY", "The keys are not configure");
Skip.If(test_skey == "SECRET KEY", "The keys are not configure");

//arrange
ushort pagesize = 2;
ushort offset = 0;

//act
var users = api.GetUsers(pagesize, out var pagingInfo, offset).ToList();

while (pagingInfo.next_offset.HasValue)
{
offset = pagingInfo.next_offset.Value;

users.AddRange(api.GetUsers(pagesize, out pagingInfo, offset));
}

//assert
Console.WriteLine($"{users.Count:n0} users");
Console.WriteLine(JsonSerializer.Serialize(users, new JsonSerializerOptions()
{
WriteIndented = true,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault
}));

Assert.Equal(users.Count, pagingInfo.total_objects);
}

[SkippableFact]
public void GetSingleTest()
{
Skip.If(test_ikey == "INTEGRATION KEY", "The keys are not configure");
Skip.If(test_skey == "SECRET KEY", "The keys are not configure");

//arrange
var user = api.GetUsers(100, out _).LastOrDefault();

Skip.If(user is null);


//act
var actual = api.GetUser(user.UserName);

//assert
Assert.NotNull(actual);
Assert.Equal(actual.User_Id, user.User_Id);

Console.WriteLine(JsonSerializer.Serialize(actual, new JsonSerializerOptions()
{
WriteIndented = true,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault
}));
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* Copyright (c) 2022 Cisco Systems, Inc. and/or its affiliates
* All rights reserved
*/
Expand All @@ -13,10 +13,16 @@

namespace Duo
{
/// <summary>
///
/// </summary>
public class CertificatePinnerFactory
{
private readonly X509CertificateCollection _rootCerts;

/// <summary>
/// Certificate Pinner Factory
/// </summary>
public CertificatePinnerFactory(X509CertificateCollection rootCerts)
{
_rootCerts = rootCerts;
Expand Down Expand Up @@ -130,7 +136,7 @@ internal static X509CertificateCollection GetDuoCertCollection()
internal static string[] ReadCertsFromFile()
{
var certs = "";
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("duo_api_csharp.ca_certs.pem"))
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("DuoApi.ca_certs.pem"))
using (StreamReader reader = new StreamReader(stream))
{
certs = reader.ReadToEnd();
Expand Down
45 changes: 45 additions & 0 deletions DuoApi/DataEnvelope.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.ComponentModel.DataAnnotations;

namespace Duo
{

/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
internal class DataEnvelope<T>
{
/// <summary>
///
/// </summary>
[Required]
public DuoApiResponseStatus Stat { get; set; }

/// <summary>
///
/// </summary>
public int? Code { get; set; }

/// <summary>
///
/// </summary>
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public T Response { get; set; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

/// <summary>
/// Upon error, basic error information
/// </summary>
public string? Message { get; set; }

/// <summary>
/// Upon error, detailed error information
/// </summary>
public string? Message_detail { get; set; }

/// <summary>
///
/// </summary>
public PagingInfo? Metadata { get; set; }
}
}
Loading