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

Implement Attack Protection endpoints #547

Merged
merged 1 commit into from
Feb 14, 2022
Merged
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
103 changes: 103 additions & 0 deletions src/Auth0.ManagementApi/Clients/AttackProtectionClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Auth0.ManagementApi.Models.AttackProtection;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Auth0.ManagementApi.Clients
{
public class AttackProtectionClient : BaseClient
{
private const string AttackProtectionBasePath = "attack-protection";
private const string SuspiciousIpThrottlingPath = "suspicious-ip-throttling";
private const string BreachedPasswordDetection = "breached-password-detection";
private const string BruteForceProtection = "brute-force-protection";

public AttackProtectionClient(IManagementConnection connection, Uri baseUri,
IDictionary<string, string> defaultHeaders) : base(connection, baseUri, defaultHeaders)
{
}

/// <summary>
/// Get the suspicious IP throttling configuration.
/// </summary>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="SuspiciousIpThrottling"/> containing the configuration.</returns>
public Task<SuspiciousIpThrottling> GetSuspiciousIpThrottlingAsync(
CancellationToken cancellationToken = default)
{
return Connection.GetAsync<SuspiciousIpThrottling>(
BuildUri($"{AttackProtectionBasePath}/{SuspiciousIpThrottlingPath}"), DefaultHeaders,
cancellationToken: cancellationToken);
}

/// <summary>
/// Update the suspicious IP throttling configuration.
/// </summary>
/// <param name="request">Specifies criteria to use when updating the configuration.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The <see cref="SuspiciousIpThrottling"/> that was updated.</returns>
public Task<SuspiciousIpThrottling> UpdateSuspiciousIpThrottlingAsync(SuspiciousIpThrottling request,
CancellationToken cancellationToken = default)
{
return Connection.SendAsync<SuspiciousIpThrottling>(new HttpMethod("PATCH"),
BuildUri($"{AttackProtectionBasePath}/{SuspiciousIpThrottlingPath}"), request, DefaultHeaders,
cancellationToken: cancellationToken);
}


/// <summary>
/// Get breached password detection settings.
/// </summary>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="BreachedPasswordDetection"/> containing the configuration.</returns>
public Task<BreachedPasswordDetection> GetBreachedPasswordDetectionAsync(
CancellationToken cancellationToken = default)
{
return Connection.GetAsync<BreachedPasswordDetection>(
BuildUri($"{AttackProtectionBasePath}/{BreachedPasswordDetection}"), DefaultHeaders,
cancellationToken: cancellationToken);
}

/// <summary>
/// Update breached password detection settings.
/// </summary>
/// <param name="request">Specifies criteria to use when updating the configuration.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The <see cref="BreachedPasswordDetection"/> that was updated.</returns>
public Task<BreachedPasswordDetection> UpdateBreachedPasswordDetectionAsync(BreachedPasswordDetection request,
CancellationToken cancellationToken = default)
{
return Connection.SendAsync<BreachedPasswordDetection>(new HttpMethod("PATCH"),
BuildUri($"{AttackProtectionBasePath}/{BreachedPasswordDetection}"), request, DefaultHeaders,
cancellationToken: cancellationToken);
}

/// <summary>
/// Get the brute force configuration.
/// </summary>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="BruteForceProtection"/> containing the configuration.</returns>
public Task<BruteForceProtection> GetBruteForceProtectionAsync(CancellationToken cancellationToken = default)
{
return Connection.GetAsync<BruteForceProtection>(
BuildUri($"{AttackProtectionBasePath}/{BruteForceProtection}"), DefaultHeaders,
cancellationToken: cancellationToken);
}

/// <summary>
/// Update the brute force configuration.
/// </summary>
/// <param name="request">Specifies criteria to use when updating the configuration.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The <see cref="BruteForceProtection"/> that was updated.</returns>
public Task<BruteForceProtection> UpdateBruteForceProtectionAsync(BruteForceProtection request,
CancellationToken cancellationToken = default)
{
return Connection.SendAsync<BruteForceProtection>(new HttpMethod("PATCH"),
BuildUri($"{AttackProtectionBasePath}/{BruteForceProtection}"), request, DefaultHeaders,
cancellationToken: cancellationToken);
}
}
}
6 changes: 6 additions & 0 deletions src/Auth0.ManagementApi/ManagementApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public class ManagementApiClient : IDisposable
/// </summary>
public ActionsClient Actions { get; }

/// <summary>
/// Contains all the methods to call the /attack-protection endpoints.
/// </summary>
public AttackProtectionClient AttackProtection { get; }

/// <summary>
/// Contains all the methods to call the /blacklists/tokens endpoints.
/// </summary>
Expand Down Expand Up @@ -170,6 +175,7 @@ public ManagementApiClient(string token, Uri baseUri, IManagementConnection mana
DefaultHeaders = CreateDefaultHeaders(token);

Actions = new ActionsClient(managementConnection, baseUri, DefaultHeaders);
AttackProtection = new AttackProtectionClient(managementConnection, baseUri, DefaultHeaders);
BlacklistedTokens = new BlacklistedTokensClient(managementConnection, baseUri, DefaultHeaders);
Branding = new BrandingClient(managementConnection, baseUri, DefaultHeaders);
ClientGrants = new ClientGrantsClient(managementConnection, baseUri, DefaultHeaders);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Auth0.ManagementApi.Models.AttackProtection
{
public class BreachedPasswordDetection
{
/// <summary>
/// Whether or not breached password detection is active.
/// </summary>
[JsonProperty("enabled")]
public bool Enabled { get; set; }

/// <summary>
/// Action to take when a breached password is detected. Possible values: "block", "user_notification", "admin_notification".
/// </summary>
[JsonProperty("shields")]
public IList<string> Shields { get; set; }

/// <summary>
/// When "admin_notification" is enabled, determines how often email notifications are sent. Possible values: "immediately", "daily", "weekly", "monthly".
/// </summary>
[JsonProperty("admin_notification_frequency")]
public IList<string> AdminNotificationFrequency { get; set; }

/// <summary>
/// The subscription level for breached password detection methods. Use "enhanced" to enable Credential Guard. Possible values: "standard", "enhanced".
/// </summary>
[JsonProperty("method")]
public string Method { get; set; }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Auth0.ManagementApi.Models.AttackProtection
{
public class BruteForceProtection
{
/// <summary>
/// Whether or not brute force attack protections are active.
/// </summary>
[JsonProperty("enabled")]
public bool Enabled { get; set; }

/// <summary>
/// Action to take when a brute force protection threshold is violated. Possible values: "block", "user_notification".
/// </summary>
[JsonProperty("shields")]
public IList<string> Shields { get; set; }

/// <summary>
/// List of trusted IP addresses that will not have attack protection enforced against them.
/// </summary>
[JsonProperty("allowlist")]
public IList<string> Allowlist { get; set; }

/// <summary>
/// Account Lockout: Determines whether or not IP address is used when counting failed attempts. Possible values: "count_per_identifier_and_ip", "count_per_identifier".
/// </summary>
[JsonProperty("mode")]
public string Mode { get; set; }

/// <summary>
/// Maximum number of unsuccessful attempts.
/// </summary>
[JsonProperty("max_attempts")]
public int MaxAttempts { get; set; }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Auth0.ManagementApi.Models.AttackProtection
{
public class Stage
{
public class StageEntry
{
/// <summary>
/// Total number of attempts allowed per day.
/// </summary>
[JsonProperty("max_attempts")]
public int MaxAttempts { get; set; }

/// <summary>
/// Interval of time, given in milliseconds, at which new attempts are granted.
/// </summary>
[JsonProperty("rate")]
public int Rate { get; set; }
}

/// <summary>
/// Configuration options that apply before every login attempt.
/// </summary>
[JsonProperty("pre-login")]
public StageEntry PreLogin { get; set; }

/// <summary>
/// Configuration options that apply before every user registration attempt.
/// </summary>
[JsonProperty("pre-user-registration")]
public StageEntry PreUserRegistration { get; set; }
}

public class SuspiciousIpThrottling
{
/// <summary>
/// Whether or not suspicious IP throttling attack protections are active.
/// </summary>
[JsonProperty("enabled")]
public bool Enabled { get; set; }

/// <summary>
/// Action to take when a suspicious IP throttling threshold is violated. Possible values: "block", "admin_notification".
/// </summary>
[JsonProperty("shields")]
public IList<string> Shields { get; set; }

/// <summary>
/// List of trusted IP addresses that will not have attack protection enforced against them.
/// </summary>
[JsonProperty("allowlist")]
public IList<string> Allowlist { get; set; }

/// <summary>
/// Holds per-stage configuration options (max_attempts and rate).
/// </summary>
[JsonProperty("stage")]
public Stage Stage { get; set; }
}

}
125 changes: 125 additions & 0 deletions tests/Auth0.ManagementApi.IntegrationTests/AttackProtectionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System.Threading.Tasks;
using Auth0.ManagementApi.Models.AttackProtection;
using Auth0.Tests.Shared;
using FluentAssertions;
using Xunit;

namespace Auth0.ManagementApi.IntegrationTests
{
public class AttackProtectionTests : TestBase, IAsyncLifetime
{
private ManagementApiClient _apiClient;
public async Task InitializeAsync()
{
string token = await GenerateManagementApiToken();

_apiClient = new ManagementApiClient(token, GetVariable("AUTH0_MANAGEMENT_API_URL"), new HttpClientManagementConnection(options: new HttpClientManagementConnectionOptions { NumberOfHttpRetries = 9 }));
}

public Task DisposeAsync()
{
_apiClient.Dispose();
return Task.CompletedTask;
}

[Fact]
public async Task Test_suspicious_ip_throttling_crud_sequence()
{
var before = await _apiClient.AttackProtection.GetSuspiciousIpThrottlingAsync();

try
{
var toUpdate = new SuspiciousIpThrottling
{
Allowlist = new[] { "1.1.1.1", "2.2.2.2" },
Shields = new[] { "block" },
Enabled = true,
Stage = new Stage
{
PreLogin = new Stage.StageEntry
{
MaxAttempts = 12,
Rate = 864000
},
PreUserRegistration = new Stage.StageEntry
{
MaxAttempts = 12,
Rate = 1728000
}
}
};

var updated = await _apiClient.AttackProtection.UpdateSuspiciousIpThrottlingAsync(toUpdate);

var after = await _apiClient.AttackProtection.GetSuspiciousIpThrottlingAsync();


updated.Should().BeEquivalentTo(toUpdate);
after.Should().BeEquivalentTo(updated);
}
finally
{
await _apiClient.AttackProtection.UpdateSuspiciousIpThrottlingAsync(before);
}
}

[Fact]
public async Task Test_breached_password_detection_crud_sequence()
{
var before = await _apiClient.AttackProtection.GetBreachedPasswordDetectionAsync();

try
{
var toUpdate = new BreachedPasswordDetection
{
Shields = new[] { "admin_notification" },
AdminNotificationFrequency = new[] { "daily" },
Method = "enhanced",
Enabled = true,
};

var updated = await _apiClient.AttackProtection.UpdateBreachedPasswordDetectionAsync(toUpdate);

var after = await _apiClient.AttackProtection.GetBreachedPasswordDetectionAsync();


updated.Should().BeEquivalentTo(toUpdate);
after.Should().BeEquivalentTo(updated);
}
finally
{
await _apiClient.AttackProtection.UpdateBreachedPasswordDetectionAsync(before);
}
}

[Fact]
public async Task Test_brute_force_protection_crud_sequence()
{
var before = await _apiClient.AttackProtection.GetBruteForceProtectionAsync();

try
{
var toUpdate = new BruteForceProtection
{
Enabled = true,
Shields = new[] { "block" },
Allowlist = new[] { "1.1.1.1", "2.2.2.2" },
Mode = "count_per_identifier",
MaxAttempts = 11,
};

var updated = await _apiClient.AttackProtection.UpdateBruteForceProtectionAsync(toUpdate);

var after = await _apiClient.AttackProtection.GetBruteForceProtectionAsync();


updated.Should().BeEquivalentTo(toUpdate);
after.Should().BeEquivalentTo(updated);
}
finally
{
await _apiClient.AttackProtection.UpdateBruteForceProtectionAsync(before);
}
}
}
}