Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
648cc58
Merge pull request #100 from contentstack/main
harshithad0703 Aug 5, 2025
6746151
Merge pull request #101 from contentstack/staging
harshithad0703 Aug 5, 2025
1bfceee
Feat Adding oauth support in .NET management SDK
cs-raj Sep 8, 2025
6296468
Using Dictonary for storing the token
cs-raj Sep 9, 2025
0a902b3
Oauth Support in Dotnet Management SDK
cs-raj Sep 11, 2025
167ede2
Test Cases for Oauth
cs-raj Sep 11, 2025
3a77a64
Fixed the OAuth Flow
cs-raj Sep 14, 2025
debd7f6
Remove the OAuthToken Valid method
cs-raj Sep 15, 2025
0fcf38a
Version bump
cs-raj Sep 15, 2025
b2c114b
Removed Comments
cs-raj Sep 15, 2025
9c035f1
feat: Add MFA support to login methods and enhance tests for MFA func…
nadeem-cs Sep 15, 2025
6cb68fb
chore: update version to 0.4.0 in changelog and project files
nadeem-cs Sep 15, 2025
5765b06
Add Variants and VariantGroup models with methods
nadeem-cs Sep 16, 2025
5cacd08
Merge pull request #102 from contentstack/feat/DX-1237
cs-raj Sep 17, 2025
8c9064b
Add integration and unit tests for Variants and VariantsModel, and re…
nadeem-cs Sep 17, 2025
59a621f
docs: Refine documentation for Stack and Variants models, correcting …
nadeem-cs Sep 17, 2025
a5c4c63
refactor: Change method access modifiers from virtual to public for V…
nadeem-cs Sep 17, 2025
53cd860
Merge pull request #103 from contentstack/feat/DX-3454-topt
nadeem-cs Sep 17, 2025
905e558
Merge pull request #104 from contentstack/feat/DX-3350-variants
nadeem-cs Sep 17, 2025
e103b6e
Merge pull request #105 from contentstack/development
harshithad0703 Sep 24, 2025
8671e06
Merge branch 'development' into feat/Oauth
cs-raj Sep 25, 2025
d262942
Merge pull request #108 from contentstack/feat/Oauth
cs-raj Sep 25, 2025
c472743
Talisman update
cs-raj Sep 25, 2025
9e1453d
Merge pull request #110 from contentstack/fix/dev
cs-raj Sep 25, 2025
2cc1e5f
Merge pull request #109 from contentstack/development
cs-raj Sep 25, 2025
311d5b5
Merge branch 'main' into staging
cs-raj Sep 25, 2025
f75f1ed
Merge pull request #111 from contentstack/fix/staging
cs-raj Sep 25, 2025
bb8eea8
Revert "Add Variants support "
nadeem-cs Sep 29, 2025
085c33a
Merge pull request #112 from contentstack/revert-104-feat/DX-3350-var…
nadeem-cs Sep 29, 2025
4d72626
Merge pull request #113 from contentstack/development
nadeem-cs Sep 29, 2025
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
4 changes: 4 additions & 0 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ fileignoreconfig:
checksum: 854eb83dcacd62d3bf233c82e5cfd0c69dd20478fa0e7c6af9028f6c6386749d
- filename: Contentstack.Management.Core/Attributes/CSMJsonConverterAttribute.cs
checksum: 774bc2a4cf7f62fb890ba39ba1319769f0ff4e13d94781d394fcac2adf14381e
- filename: Contentstack.Management.Core.Unit.Tests/OAuth/OAuthOptionsTest.cs
checksum: 3d1ed19a9c7d311d9662632f48169d3a9013f605674cbd18b9f45039b0f83ff6
- filename: Contentstack.Management.Core/Models/OAuthOptions.cs
checksum: c328bfd5241e11e6e9d630527ba9084fb5b361abac48e8af2f96379dd6357c6c
version: ""
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# Changelog
## [v0.4.0](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.4.0)
- Feat
- **MFA Support**: Added Multi-Factor Authentication (MFA) support for login operations
- Added `mfaSecret` parameter to `Login` and `LoginAsync` methods for TOTP generation
- Automatic TOTP token generation from Base32-encoded MFA secrets using Otp.NET library
- Comprehensive test coverage for MFA functionality including unit and integration tests
- Supports both explicit token and MFA secret-based authentication flows
- Added Support for OAuth
- Added Comprehensive test coverage for OAuth Functionality in Unit Test cases.
- Supports both Login with and without OAuth Flows

## [v0.3.2](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.3.2)
- Fix
- Added Test cases for the Release
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,123 @@ public void Test007_Should_Return_Loggedin_User_With_Organizations_detail()
Assert.Fail(e.Message);
}
}

[TestMethod]
[DoNotParallelize]
public void Test008_Should_Fail_Login_With_Invalid_MfaSecret()
{
ContentstackClient client = new ContentstackClient();
NetworkCredential credentials = new NetworkCredential("test_user", "test_password");
string invalidMfaSecret = "INVALID_BASE32_SECRET!@#";

try
{
ContentstackResponse contentstackResponse = client.Login(credentials, null, invalidMfaSecret);
Assert.Fail("Expected exception for invalid MFA secret");
}
catch (ArgumentException)
{
// Expected exception for invalid Base32 encoding
Assert.IsTrue(true);
}
catch (Exception e)
{
Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
}
}

[TestMethod]
[DoNotParallelize]
public void Test009_Should_Generate_TOTP_Token_With_Valid_MfaSecret()
{
ContentstackClient client = new ContentstackClient();
NetworkCredential credentials = new NetworkCredential("test_user", "test_password");
string validMfaSecret = "JBSWY3DPEHPK3PXP"; // Valid Base32 test secret

try
{
// This should fail due to invalid credentials, but should succeed in generating TOTP
ContentstackResponse contentstackResponse = client.Login(credentials, null, validMfaSecret);
}
catch (ContentstackErrorException errorException)
{
// Expected to fail due to invalid credentials, but we verify it processed the MFA secret
// The error should be about credentials, not about MFA secret format
Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode);
Assert.IsTrue(errorException.Message.Contains("email or password") ||
errorException.Message.Contains("credentials") ||
errorException.Message.Contains("authentication"));
}
catch (ArgumentException)
{
Assert.Fail("Should not throw ArgumentException for valid MFA secret");
}
catch (Exception e)
{
Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
}
}

[TestMethod]
[DoNotParallelize]
public async System.Threading.Tasks.Task Test010_Should_Generate_TOTP_Token_With_Valid_MfaSecret_Async()
{
ContentstackClient client = new ContentstackClient();
NetworkCredential credentials = new NetworkCredential("test_user", "test_password");
string validMfaSecret = "JBSWY3DPEHPK3PXP"; // Valid Base32 test secret

try
{
// This should fail due to invalid credentials, but should succeed in generating TOTP
ContentstackResponse contentstackResponse = await client.LoginAsync(credentials, null, validMfaSecret);
}
catch (ContentstackErrorException errorException)
{
// Expected to fail due to invalid credentials, but we verify it processed the MFA secret
// The error should be about credentials, not about MFA secret format
Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode);
Assert.IsTrue(errorException.Message.Contains("email or password") ||
errorException.Message.Contains("credentials") ||
errorException.Message.Contains("authentication"));
}
catch (ArgumentException)
{
Assert.Fail("Should not throw ArgumentException for valid MFA secret");
}
catch (Exception e)
{
Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
}
}

[TestMethod]
[DoNotParallelize]
public void Test011_Should_Prefer_Explicit_Token_Over_MfaSecret()
{
ContentstackClient client = new ContentstackClient();
NetworkCredential credentials = new NetworkCredential("test_user", "test_password");
string validMfaSecret = "JBSWY3DPEHPK3PXP";
string explicitToken = "123456";

try
{
// This should fail due to invalid credentials, but should use explicit token
ContentstackResponse contentstackResponse = client.Login(credentials, explicitToken, validMfaSecret);
}
catch (ContentstackErrorException errorException)
{
// Expected to fail due to invalid credentials
// The important thing is that it didn't throw an exception about MFA secret processing
Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode);
}
catch (ArgumentException)
{
Assert.Fail("Should not throw ArgumentException when explicit token is provided");
}
catch (Exception e)
{
Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,107 @@ public void Should_Allow_Credentials_With_Token()
Assert.AreEqual("{\"user\":{\"email\":\"name\",\"password\":\"password\",\"tfa_token\":\"token\"}}", Encoding.Default.GetString(loginService.ByteContent));
}

[TestMethod]
public void Should_Allow_Credentials_With_MfaSecret()
{

string testMfaSecret = "JBSWY3DPEHPK3PXP"; // Base32 encoded "Hello!"
var loginService = new LoginService(serializer, credentials, null, testMfaSecret);
loginService.ContentBody();

Assert.IsNotNull(loginService);
var contentString = Encoding.Default.GetString(loginService.ByteContent);

Assert.IsTrue(contentString.Contains("\"email\":\"name\""));
Assert.IsTrue(contentString.Contains("\"password\":\"password\""));
Assert.IsTrue(contentString.Contains("\"tfa_token\":"));

// Verify the tfa_token is not null or empty in the JSON
Assert.IsFalse(contentString.Contains("\"tfa_token\":null"));
Assert.IsFalse(contentString.Contains("\"tfa_token\":\"\""));
}

[TestMethod]
public void Should_Generate_TOTP_Token_When_MfaSecret_Provided()
{
string testMfaSecret = "JBSWY3DPEHPK3PXP"; // Base32 encoded "Hello!"
var loginService1 = new LoginService(serializer, credentials, null, testMfaSecret);
var loginService2 = new LoginService(serializer, credentials, null, testMfaSecret);

loginService1.ContentBody();
loginService2.ContentBody();

var content1 = Encoding.Default.GetString(loginService1.ByteContent);
var content2 = Encoding.Default.GetString(loginService2.ByteContent);

// Both should contain tfa_token
Assert.IsTrue(content1.Contains("\"tfa_token\":"));
Assert.IsTrue(content2.Contains("\"tfa_token\":"));

// Extract the tokens for comparison (tokens should be 6 digits)
var token1Match = System.Text.RegularExpressions.Regex.Match(content1, "\"tfa_token\":\"(\\d{6})\"");
var token2Match = System.Text.RegularExpressions.Regex.Match(content2, "\"tfa_token\":\"(\\d{6})\"");

Assert.IsTrue(token1Match.Success);
Assert.IsTrue(token2Match.Success);

// Tokens should be valid 6-digit numbers
Assert.AreEqual(6, token1Match.Groups[1].Value.Length);
Assert.AreEqual(6, token2Match.Groups[1].Value.Length);
}

[TestMethod]
public void Should_Prefer_Explicit_Token_Over_MfaSecret()
{
string testMfaSecret = "JBSWY3DPEHPK3PXP";
// file deepcode ignore NoHardcodedCredentials/test: random test token
string explicitToken = "123456";

var loginService = new LoginService(serializer, credentials, explicitToken, testMfaSecret);
loginService.ContentBody();

var contentString = Encoding.Default.GetString(loginService.ByteContent);

// Should use the explicit token, not generate one from MFA secret
Assert.IsTrue(contentString.Contains("\"tfa_token\":\"123456\""));
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Should_Throw_Exception_For_Invalid_Base32_MfaSecret()
{
// Invalid Base32 secret (contains invalid characters)
string invalidMfaSecret = "INVALID_BASE32_123!@#";

var loginService = new LoginService(serializer, credentials, null, invalidMfaSecret);
}

[TestMethod]
public void Should_Not_Generate_Token_When_MfaSecret_Is_Empty()
{
var loginService = new LoginService(serializer, credentials, null, "");
loginService.ContentBody();

var contentString = Encoding.Default.GetString(loginService.ByteContent);

// Should not contain tfa_token when MFA secret is empty
Assert.IsFalse(contentString.Contains("\"tfa_token\":"));
Assert.AreEqual("{\"user\":{\"email\":\"name\",\"password\":\"password\"}}", contentString);
}

[TestMethod]
public void Should_Not_Generate_Token_When_MfaSecret_Is_Null()
{
var loginService = new LoginService(serializer, credentials, null, null);
loginService.ContentBody();

var contentString = Encoding.Default.GetString(loginService.ByteContent);

// Should not contain tfa_token when MFA secret is null
Assert.IsFalse(contentString.Contains("\"tfa_token\":"));
Assert.AreEqual("{\"user\":{\"email\":\"name\",\"password\":\"password\"}}", contentString);
}

[TestMethod]
public void Should_Override_Authtoken_To_ContentstackOptions_On_Success()
{
Expand Down
Loading
Loading