Skip to content
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
2 changes: 1 addition & 1 deletion GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
next-version: 5.0.1
next-version: 5.1.0
tag-prefix: '[vV]'
mode: ContinuousDeployment
branches:
Expand Down
36 changes: 23 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

# <img src="https://github.com/NinjaRocks/FeatureOne/blob/master/ninja-icon-16.png" alt="ninja" style="width:30px;"/> FeatureOne v5.0.1
[![GitHub Release](https://img.shields.io/github/v/release/ninjarocks/FeatureOne?logo=github&sort=semver)](https://github.com/ninjarocks/FeatureOne/releases/latest)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/NinjaRocks/FeatureOne/blob/master/License.md) [![build-master](https://github.com/NinjaRocks/FeatureOne/actions/workflows/Build-Master.yml/badge.svg)](https://github.com/NinjaRocks/FeatureOne/actions/workflows/Build-Master.yml)
[![CodeQL](https://github.com/NinjaRocks/FeatureOne/actions/workflows/codeql.yml/badge.svg)](https://github.com/NinjaRocks/FeatureOne/actions/workflows/codeql.yml)
# <img src="https://github.com/CodeShayk/FeatureOne/blob/master/ninja-icon-16.png" alt="ninja" style="width:30px;"/> FeatureOne v5.1.0
[![GitHub Release](https://img.shields.io/github/v/release/CodeShayk/FeatureOne?logo=github&sort=semver)](https://github.com/CodeShayk/FeatureOne/releases/latest)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/CodeShayk/FeatureOne/blob/master/License.md) [![build-master](https://github.com/CodeShayk/FeatureOne/actions/workflows/Build-Master.yml/badge.svg)](https://github.com/CodeShayk/FeatureOne/actions/workflows/Build-Master.yml)
[![CodeQL](https://github.com/CodeShayk/FeatureOne/actions/workflows/codeql.yml/badge.svg)](https://github.com/CodeShayk/FeatureOne/actions/workflows/codeql.yml)
[![.Net](https://img.shields.io/badge/.Net_Framework-4.6.2-blue)](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net46)
[![.Net](https://img.shields.io/badge/.Net_Standard-2.1-blue)](https://dotnet.microsoft.com/en-us/download/netstandard/2.1)
[![.Net](https://img.shields.io/badge/.Net-9.0-blue)](https://dotnet.microsoft.com/en-us/download/dotnet/9.0)
Expand All @@ -12,9 +12,9 @@
#### Nuget Packages
| Package | Latest | Details |
| --------| --------| --------|
|FeatureOne |[![NuGet version](https://badge.fury.io/nu/FeatureOne.svg)](https://badge.fury.io/nu/FeatureOne) | Provides core funtionality to implement feature toggles with `no` backend storage provider. Needs package consumer to provide `IStorageProvider` implementation. Ideal for use case that requires custom storage backend. Please see below for more details. |
|FeatureOne.SQL| [![NuGet version](https://badge.fury.io/nu/FeatureOne.SQL.svg)](https://badge.fury.io/nu/FeatureOne.SQL) | Provides SQL storage provider for implementing feature toggles using `SQL` backend. |
|FeatureOne.File |[![NuGet version](https://badge.fury.io/nu/FeatureOne.File.svg)](https://badge.fury.io/nu/FeatureOne.File) | Provides File storage provider for implementing feature toggles using `File System` backend. |
|FeatureOne |[![NuGet version](https://badge.fury.io/nu/FeatureOne.svg)](https://badge.fury.io/nu/FeatureOne) | Provides core functionality to implement feature toggles with `no` backend storage provider. Needs package consumer to provide `IStorageProvider` implementation. Ideal for use case that requires custom storage backend. **v5.1.0**: Security fixes, DI integration, DateRangeCondition. |
|FeatureOne.SQL| [![NuGet version](https://badge.fury.io/nu/FeatureOne.SQL.svg)](https://badge.fury.io/nu/FeatureOne.SQL) | Provides SQL storage provider for implementing feature toggles using `SQL` backend. **v5.1.0**: Security fixes, DI integration, enhanced configuration. |
|FeatureOne.File |[![NuGet version](https://badge.fury.io/nu/FeatureOne.File.svg)](https://badge.fury.io/nu/FeatureOne.File) | Provides File storage provider for implementing feature toggles using `File System` backend. **v5.1.0**: Security fixes, DI integration, enhanced configuration. |

## Concept
### What is a feature toggle?
Expand Down Expand Up @@ -58,12 +58,22 @@ If you are having problems, please let me know by [raising a new issue](https://
This project is licensed with the [MIT license](LICENSE).

## Version History
The main branch is now on .NET 9.0. The following previous versions are available:
| Version | Release Notes | Developer Guide |
| -------- | --------|--------|
| [`v4.0.0`](https://github.com/CodeShayk/FeatureOne/tree/v4.0.0) | [Notes](https://github.com/CodeShayk/FeatureOne/releases/tag/v4.0.0) | [Guide](https://github.com/CodeShayk/FeatureOne/blob/v4.0.0/DeveloperGuide.md) |
| [`v3.0.0`](https://github.com/CodeShayk/FeatureOne/tree/v3.0.0) | [Notes](https://github.com/CodeShayk/FeatureOne/releases/tag/v3.0.0) | [Guide](https://github.com/CodeShayk/FeatureOne/blob/v3.0.0/DeveloperGuide.md) |
| [`v2.0.0`](https://github.com/CodeShayk/FeatureOne/tree/v2.0.0) | [Notes](https://github.com/CodeShayk/FeatureOne/releases/tag/v2.0.0) | [Guide](https://github.com/CodeShayk/FeatureOne/blob/v2.0.0/DeveloperGuide.md) |
The following previous versions are available:

| Version | Release Notes |
| ----------------------------------------------------------------| ----------------------------------------------------------------------|
| [`v5.0.0`](https://github.com/CodeShayk/FeatureOne/tree/v5.0.0) | [Notes](https://github.com/CodeShayk/FeatureOne/releases/tag/v5.0.0) |
| [`v4.0.0`](https://github.com/CodeShayk/FeatureOne/tree/v4.0.0) | [Notes](https://github.com/CodeShayk/FeatureOne/releases/tag/v4.0.0) |
| [`v3.0.0`](https://github.com/CodeShayk/FeatureOne/tree/v3.0.0) | [Notes](https://github.com/CodeShayk/FeatureOne/releases/tag/v3.0.0) |
| [`v2.0.0`](https://github.com/CodeShayk/FeatureOne/tree/v2.0.0) | [Notes](https://github.com/CodeShayk/FeatureOne/releases/tag/v2.0.0) |

## Recent Releases

| Version | Release Date | Type | Key Changes | Backward Compatibility |
|--------|-------------|------|-------------|---------------------|
| v5.0.0 | Previous | Initial | Core feature toggle functionality | N/A (Initial release) |
| v5.1.0 | Nov 03, 2025 | Minor | **Security fixes** (ReDoS protection, secure type loading), **architectural improvements** (prefix matching, dependency injection), **new features** (DateRangeCondition, configuration validation), **DI integration** | High - maintains all existing functionality with minor security-related behavioral changes |

## Credits
Thank you for reading. Please fork, explore, contribute and report. Happy Coding !! :)

Expand Down
36 changes: 36 additions & 0 deletions src/FeatureOne.File/Extensions/FeatureOneFileExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using FeatureOne.Cache;
using FeatureOne.File.StorageProvider;
using FeatureOne.Json;
using Microsoft.Extensions.DependencyInjection;

namespace FeatureOne.File.Extensions
{
/// <summary>
/// Extension methods for adding FeatureOne services to the DI container
/// </summary>
public static class FeatureOneFileExtensions
{
/// <summary>
/// Add Feature One with File storage.
/// </summary>
/// <param name="services"></param>
/// <param name="configuration">Required: Configuration.</param>
/// <param name="deserializer">Optional: Custom Deserializer for Toggles. Pass Null to use default.</param>
/// <param name="cache">Optional: Custom Cache for Toggles. Pass Null to use default memCache.</param>
/// <returns></returns>
public static IServiceCollection AddFeatureOneWithFileStorage(this IServiceCollection services,
FileConfiguration configuration, IToggleDeserializer deserializer = null, ICache cache = null)
{
if (configuration == null)
throw new ArgumentNullException("FileConfiguration is required.");

return services
.AddFeatureOne(provider =>
new FileStorageProvider(configuration,
new FileReader(configuration),
deserializer ?? new ToggleDeserializer(new ConditionDeserializer()),
cache ?? new FeatureCache()));
}
}
}
27 changes: 20 additions & 7 deletions src/FeatureOne.File/FeatureOne.File.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,29 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/codeshayk/FeatureOne</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>feature-toggle; feature-flag; feature-flags; feature-toggles; .net8.0; featureOne; File-system; File-Backend; File-Toggles;</PackageTags>
<Version>5.0.1</Version>
<PackageTags>feature-toggle; feature-flag; feature-flags; feature-toggles; featureOne; File-system; File-Backend; File-Toggles;</PackageTags>
<Version>5.1.0</Version>
<PackageLicenseFile>License.md</PackageLicenseFile>
<PackageIcon>ninja-icon-16.png</PackageIcon>
<PackageReleaseNotes>
Release Notes v5.0.1. - Targets .Net Framework 4.6.2, .NetStandard 2.1 and .Net 9.0
Release Notes v5.1.0. - Targets .Net Framework 4.6.2, .NetStandard 2.1 and .Net 9.0
Library to Implement Feature Toggles to hide/show program features with File system storage.
- Provides Out of box Simple and Regex toggle conditions.
- Provides Out of box support for File system storage provider to store toggles on disk file.
- Provides the support for default memory caching via configuration.
- Provides extensibility for custom implementations ie.
Security Fixes:
- Fixed RegexCondition ReDoS (Regular Expression Denial of Service) vulnerability with timeout validation
- Secured dynamic type loading in ConditionDeserializer with explicit safe type registry

Architectural Improvements:
- Fixed FindStartsWith implementation for actual prefix matching
- Implemented proper dependency injection patterns with null validation

New Features:
- Added DateRangeCondition for time-based feature toggles
- Added Configuration Validation System with clear error messages

Provides Out of box Simple and Regex toggle conditions.
Provides Out of box support for File system storage provider to store toggles on disk file.
Provides the support for default memory caching via configuration.
Provides extensibility for custom implementations ie.
-- Provides extensibility for implementing custom toggle conditions for bespoke use cases.
-- Provides extensibility for implementing custom caching provider.
-- Provides extensibility for implementing custom toggle deserializer for bespoke scenarios.
Expand Down Expand Up @@ -59,6 +71,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="System.IO.FileSystem" Version="4.3.0" />
</ItemGroup>

Expand Down
3 changes: 0 additions & 3 deletions src/FeatureOne.File/FileRecord.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using System.Collections.Generic;
using System.Linq;

namespace FeatureOne.File
{
public class FileRecord
Expand Down
4 changes: 2 additions & 2 deletions src/FeatureOne.File/StorageProvider/FileReader.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;

namespace FeatureOne.File.StorageProvider
{
Expand Down
36 changes: 36 additions & 0 deletions src/FeatureOne.SQL/Extensions/FeatureOneSQLExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using FeatureOne.Cache;
using FeatureOne.Json;
using FeatureOne.SQL.StorageProvider;
using Microsoft.Extensions.DependencyInjection;

namespace FeatureOne.SQL.Extensions
{
/// <summary>
/// Extension methods for adding FeatureOne services to the DI container
/// </summary>
public static class FeatureOneSQLExtensions
{
/// <summary>
/// Add Feature One with SQL storage.
/// </summary>
/// <param name="services"></param>
/// <param name="configuration">Required: SQL Configuration.</param>
/// <param name="deserializer">Optional: Custom Deserializer for Toggles. Pass Null to use default.</param>
/// <param name="cache">Optional: Custom Cache for Toggles. Pass Null to use default memCache.</param>
/// <returns></returns>
public static IServiceCollection AddFeatureOneWithSQLStorage(this IServiceCollection services,
SQLConfiguration configuration, IToggleDeserializer deserializer = null, ICache cache = null)
{
if (configuration == null)
throw new ArgumentNullException("SQLConfiguration is required.");

return services
.AddFeatureOne(provider =>
new SQLStorageProvider(repository: new DbRepository(configuration),
deserializer: deserializer ?? new ToggleDeserializer(new ConditionDeserializer()),
cache: cache ?? new FeatureCache(),
cacheSettings: configuration.CacheSettings));
}
}
}
27 changes: 20 additions & 7 deletions src/FeatureOne.SQL/FeatureOne.SQL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,29 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/CodeShayk/FeatureOne</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>feature-toggle; feature-flag; feature-flags; feature-toggles; .net8.0; featureOne; SQL-Backend; SQL-Toggles; SQL</PackageTags>
<Version>5.0.1</Version>
<PackageTags>feature-toggle; feature-flag; feature-flags; feature-toggles; featureOne; SQL-Backend; SQL-Toggles; SQL</PackageTags>
<Version>5.1.0</Version>
<PackageLicenseFile>License.md</PackageLicenseFile>
<PackageIcon>ninja-icon-16.png</PackageIcon>
<PackageReleaseNotes>
Release Notes v5.0.1. - Targets .Net Framework 4.6.2, .NetStandard 2.1 and .Net 9.0
Release Notes v5.1.0. - Targets .Net Framework 4.6.2, .NetStandard 2.1 and .Net 9.0
Library to Implement Feature Toggles to hide/show program features with SQL storage.
- Supports configuring all Db providers - MSSQL, SQLite, ODBC, OLEDB, MySQL, PostgreSQL.
- Provides Out of box Simple and Regex toggle conditions.
- Provides the support for default memory caching via configuration.
- Provides extensibility for custom implementations ie.
Security Fixes:
- Fixed RegexCondition ReDoS (Regular Expression Denial of Service) vulnerability with timeout validation
- Secured dynamic type loading in ConditionDeserializer with explicit safe type registry

Architectural Improvements:
- Fixed FindStartsWith implementation for actual prefix matching
- Implemented proper dependency injection patterns with null validation

New Features:
- Added DateRangeCondition for time-based feature toggles
- Added Configuration Validation System with clear error messages

Supports configuring all Db providers - MSSQL, SQLite, ODBC, OLEDB, MySQL, PostgreSQL.
Provides Out of box Simple and Regex toggle conditions.
Provides the support for default memory caching via configuration.
Provides extensibility for custom implementations ie.
-- Provides extensibility for implementing custom toggle conditions for bespoke use cases.
-- Provides extensibility to plugin other SQL providers.
-- Provides extensibility for implementing custom caching providers.
Expand All @@ -57,6 +69,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
Expand Down
1 change: 0 additions & 1 deletion src/FeatureOne/Cache/FeatureCache.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Runtime.Caching;

namespace FeatureOne.Cache
Expand Down
16 changes: 12 additions & 4 deletions src/FeatureOne/Core/Stores/FeatureStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,26 @@ public class FeatureStore : IFeatureStore
private readonly IStorageProvider storageProvider;
private readonly IFeatureLogger logger;

public FeatureStore(IStorageProvider storageProvider) : this(storageProvider, new NullLogger())
public FeatureStore(IStorageProvider storageProvider) : this(storageProvider, new DefaultLogger(null))
{
}

public FeatureStore(IStorageProvider storageProvider, IFeatureLogger logger)
{
this.storageProvider = storageProvider;
this.logger = logger;
this.storageProvider = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public IEnumerable<IFeature> FindStartsWith(string name)
{
try
{
if (string.IsNullOrWhiteSpace(name))
{
logger?.Info($"FeatureOne, Action='StorageProvider.Get', Message='The provided feature name was null or whitespace.'");
return Enumerable.Empty<IFeature>();
}

var features = storageProvider.GetByName(name);
if (features == null || !features.Any())
{
Expand All @@ -32,7 +38,9 @@ public IEnumerable<IFeature> FindStartsWith(string name)

var result = new List<IFeature>();

foreach (var feature in features.Where(x => x.Toggle?.Conditions != null && x.Toggle.Conditions.Any()))
foreach (var feature in features
.Where(x => x.Toggle?.Conditions != null && x.Toggle.Conditions.Any())
.Where(x => x.Name.Value.StartsWith(name, StringComparison.OrdinalIgnoreCase)))
result.Add(feature);

return result;
Expand Down
24 changes: 24 additions & 0 deletions src/FeatureOne/Core/Toggles/Conditions/DateRangeCondition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;

namespace FeatureOne.Core.Toggles.Conditions
{
public class DateRangeCondition : ICondition
{
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }

public bool Evaluate(IDictionary<string, string> claims)
{
var now = DateTime.Now.Date; // Use just the date part for comparison

if (StartDate.HasValue && now < StartDate.Value.Date)
return false;

if (EndDate.HasValue && now > EndDate.Value.Date)
return false;

return true;
}
}
}
29 changes: 22 additions & 7 deletions src/FeatureOne/Core/Toggles/Conditions/RegexCondition.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
Expand All @@ -8,6 +9,7 @@ public class RegexCondition : ICondition
{
public string Claim { get; set; }
public string Expression { get; set; }
public TimeSpan Timeout { get; set; } = Constants.DefaultRegExTimeout;

public bool Evaluate(IDictionary<string, string> claims)
{
Expand All @@ -17,13 +19,26 @@ public bool Evaluate(IDictionary<string, string> claims)
if (!claims.Any(x => x.Key != null && x.Key.Equals(Claim)))
return false;

var result = Regex.IsMatch(
claims.First(x => x.Key.Equals(Claim)).Value,
Expression,
RegexOptions.None,
Constants.DefaultRegExTimeout
);
return result;
try
{
var value = claims.First(x => x.Key.Equals(Claim)).Value;
var regex = new Regex(
Expression,
RegexOptions.None,
Timeout
);
return regex.IsMatch(value);
}
catch (RegexMatchTimeoutException)
{
// Return false when regex times out to prevent ReDoS
return false;
}
catch (ArgumentException)
{
// Invalid regex pattern
return false;
}
}
}
}
Loading
Loading