diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 1c1e2ea..fb564c4 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -12,7 +12,7 @@ jobs:
os:
- "windows-latest"
- "ubuntu-latest"
- #- "macos-latest"
+ - "macos-latest"
dotnet:
- "8.0"
runs-on: ${{ matrix.os }}
@@ -37,9 +37,6 @@ jobs:
- name: "Construct files"
run: |
cp -r E5Renewer/bin/Release/net${{ matrix.dotnet }}/publish dist
- mkdir -p dist/modules
- cp -r E5Renewer.Modules.TomlParser/bin/Release/net${{ matrix.dotnet }}/publish dist/modules/E5Renewer.Modules.TomlParser
- cp -r E5Renewer.Modules.YamlParser/bin/Release/net${{ matrix.dotnet }}/publish dist/modules/E5Renewer.Modules.YamlParser
- name: "Create archive"
run: "7z a E5Renewer-${{ steps.build-env-info.outputs.system }}-${{ steps.build-env-info.outputs.machine }}.7z ./dist/*"
diff --git a/Directory.Build.props b/Directory.Build.props
index 70825f6..99303cd 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,6 @@
- 0.1.0
+ 0.2.0
true
true
true
diff --git a/E5Renewer.Modules.TomlParser.Tests/E5Renewer.Modules.TomlParser.Tests.csproj b/E5Renewer.Modules.TomlParser.Tests/E5Renewer.Modules.TomlParser.Tests.csproj
deleted file mode 100644
index 840ebf0..0000000
--- a/E5Renewer.Modules.TomlParser.Tests/E5Renewer.Modules.TomlParser.Tests.csproj
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
- false
- true
-
-
-
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
diff --git a/E5Renewer.Modules.TomlParser.Tests/GlobalUsings.cs b/E5Renewer.Modules.TomlParser.Tests/GlobalUsings.cs
deleted file mode 100644
index 540383d..0000000
--- a/E5Renewer.Modules.TomlParser.Tests/GlobalUsings.cs
+++ /dev/null
@@ -1 +0,0 @@
-global using Microsoft.VisualStudio.TestTools.UnitTesting;
diff --git a/E5Renewer.Modules.TomlParser.Tests/TomlParserTests.cs b/E5Renewer.Modules.TomlParser.Tests/TomlParserTests.cs
deleted file mode 100644
index d169690..0000000
--- a/E5Renewer.Modules.TomlParser.Tests/TomlParserTests.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-namespace E5Renewer.Modules.TomlParser.Tests;
-
-/// Test
-///
-///
-[TestClass]
-public class TomlParserTests
-{
- private readonly TomlParser parser = new();
-
- /// Ensure name.
- [TestMethod]
- public void TestName() => Assert.AreEqual("TomlParser", this.parser.name);
- /// Ensure author.
- [TestMethod]
- public void TestAuthor() => Assert.AreEqual("E5Renewer", this.parser.author);
-
- /// Test
- ///
- ///
- [TestMethod]
- [DataRow("test.toml", true)]
- [DataRow("test.yaml", false)]
- [DataRow("test.json", false)]
- public void TestIsSupported(string path, bool result) => Assert.AreEqual(result, this.parser.IsSupported(path));
-
- /// Test
- ///
- ///
- [TestMethod]
- [DataRow("", false)]
- [DataRow("auth_token = \"test-token\"", true)]
- public async Task TestParseConfigAsyncAuthToken(string json, bool result)
- {
- string tmpPath = Path.GetTempFileName();
- await File.WriteAllTextAsync(tmpPath, json);
- E5Renewer.Models.Config.Config config = await this.parser.ParseConfigAsync(tmpPath);
- bool compareResult = config.authToken == "test-token";
- Assert.AreEqual(result, compareResult);
- }
-}
diff --git a/E5Renewer.Modules.TomlParser/E5Renewer.Modules.TomlParser.csproj b/E5Renewer.Modules.TomlParser/E5Renewer.Modules.TomlParser.csproj
deleted file mode 100644
index 77fab2f..0000000
--- a/E5Renewer.Modules.TomlParser/E5Renewer.Modules.TomlParser.csproj
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
- net8.0
- enable
- enable
- true
-
-
-
-
-
-
-
-
-
- false
- runtime
-
-
-
-
diff --git a/E5Renewer.Modules.TomlParser/TomlParser.cs b/E5Renewer.Modules.TomlParser/TomlParser.cs
deleted file mode 100644
index bdf168b..0000000
--- a/E5Renewer.Modules.TomlParser/TomlParser.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using CaseConverter;
-
-using E5Renewer.Models;
-using E5Renewer.Models.Config;
-using E5Renewer.Models.Modules;
-
-using Tomlyn;
-
-namespace E5Renewer.Modules.TomlParser;
-
-///
-/// Parse toml to
-/// Config.
-///
-[Module]
-public class TomlParser : IConfigParser
-{
- ///
- public string name { get => nameof(TomlParser); }
-
- ///
- public string author { get => "E5Renewer"; }
-
- ///
- public SemanticVersioning.Version apiVersion
- {
- get => typeof(TomlParser).Assembly.GetName().Version?.ToSemanticVersion() ?? new(0, 1, 0);
- }
-
- ///
- public bool IsSupported(string path) => path.EndsWith(".toml");
-
- ///
- public async ValueTask ParseConfigAsync(string path)
- {
- Config runtimeConfig;
- using (StreamReader stream = File.OpenText(path))
- {
- runtimeConfig = Toml.ToModel(
- await stream.ReadToEndAsync(), path, new()
- {
- ConvertPropertyName = (input) => input.ToSnakeCase()
- }
- );
- }
- return runtimeConfig;
- }
-}
diff --git a/E5Renewer.Modules.YamlParser.Tests/E5Renewer.Modules.YamlParser.Tests.csproj b/E5Renewer.Modules.YamlParser.Tests/E5Renewer.Modules.YamlParser.Tests.csproj
deleted file mode 100644
index 42c2431..0000000
--- a/E5Renewer.Modules.YamlParser.Tests/E5Renewer.Modules.YamlParser.Tests.csproj
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
- false
- true
-
-
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
diff --git a/E5Renewer.Modules.YamlParser.Tests/GlobalUsings.cs b/E5Renewer.Modules.YamlParser.Tests/GlobalUsings.cs
deleted file mode 100644
index 540383d..0000000
--- a/E5Renewer.Modules.YamlParser.Tests/GlobalUsings.cs
+++ /dev/null
@@ -1 +0,0 @@
-global using Microsoft.VisualStudio.TestTools.UnitTesting;
diff --git a/E5Renewer.Modules.YamlParser.Tests/YamlParserTests.cs b/E5Renewer.Modules.YamlParser.Tests/YamlParserTests.cs
deleted file mode 100644
index ce848ee..0000000
--- a/E5Renewer.Modules.YamlParser.Tests/YamlParserTests.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-namespace E5Renewer.Modules.YamlParser.Tests;
-
-/// Test
-///
-///
-[TestClass]
-public class YamlParserTests
-{
- private readonly YamlParser parser = new();
-
- /// Ensure name.
- [TestMethod]
- public void TestName() => Assert.AreEqual("YamlParser", this.parser.name);
-
- /// Ensure author.
- [TestMethod]
- public void TestAuthor() => Assert.AreEqual("E5Renewer", this.parser.author);
-
- /// Test
- ///
- ///
- [TestMethod]
- [DataRow("test.toml", false)]
- [DataRow("test.yaml", true)]
- [DataRow("test.yml", true)]
- [DataRow("test.json", false)]
- public void TestIsSupported(string path, bool result) => Assert.AreEqual(result, this.parser.IsSupported(path));
-
- /// Test
- ///
- ///
- [TestMethod]
- [DataRow("{}", false)]
- [DataRow("auth_token: test-token", true)]
- public async Task TestParseConfigAsyncAuthToken(string json, bool result)
- {
- string tmpPath = Path.GetTempFileName();
- await File.WriteAllTextAsync(tmpPath, json);
- E5Renewer.Models.Config.Config config = await this.parser.ParseConfigAsync(tmpPath);
- bool compareResult = config.authToken == "test-token";
- Assert.AreEqual(result, compareResult);
- }
-}
diff --git a/E5Renewer.Modules.YamlParser/E5Renewer.Modules.YamlParser.csproj b/E5Renewer.Modules.YamlParser/E5Renewer.Modules.YamlParser.csproj
deleted file mode 100644
index acdffda..0000000
--- a/E5Renewer.Modules.YamlParser/E5Renewer.Modules.YamlParser.csproj
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
- net8.0
- enable
- enable
- true
-
-
-
-
-
-
-
-
-
- false
- runtime
-
-
-
-
diff --git a/E5Renewer.Modules.YamlParser/YamlParser.cs b/E5Renewer.Modules.YamlParser/YamlParser.cs
deleted file mode 100644
index 3446f5c..0000000
--- a/E5Renewer.Modules.YamlParser/YamlParser.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using E5Renewer.Models;
-using E5Renewer.Models.Config;
-using E5Renewer.Models.Modules;
-
-using YamlDotNet.Serialization;
-using YamlDotNet.Serialization.NamingConventions;
-
-namespace E5Renewer.Modules.YamlParser;
-
-///
-/// Parse yaml to
-/// Config.
-///
-[Module]
-public class YamlParser : IConfigParser
-{
- ///
- public string name { get => nameof(YamlParser); }
-
- ///
- public string author { get => "E5Renewer"; }
-
- ///
- public SemanticVersioning.Version apiVersion
- {
- get => typeof(YamlParser).Assembly.GetName().Version?.ToSemanticVersion() ?? new(0, 1, 0);
- }
-
- ///
- public bool IsSupported(string path) => path.EndsWith(".yaml") || path.EndsWith(".yml");
-
- ///
- public async ValueTask ParseConfigAsync(string path)
- {
- Config runtimeConfig;
- using (StreamReader stream = File.OpenText(path))
- {
- IDeserializer deserializer = new DeserializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build();
- runtimeConfig = deserializer.Deserialize(await stream.ReadToEndAsync());
- }
- return runtimeConfig;
- }
-
-}
diff --git a/E5Renewer.Tests/Controllers/JsonAPIV1ControllerTests.cs b/E5Renewer.Tests/Controllers/JsonAPIV1ControllerTests.cs
index 49942d4..d064ae0 100644
--- a/E5Renewer.Tests/Controllers/JsonAPIV1ControllerTests.cs
+++ b/E5Renewer.Tests/Controllers/JsonAPIV1ControllerTests.cs
@@ -5,6 +5,7 @@
using Microsoft.Extensions.Logging;
using NSubstitute;
+using Microsoft.AspNetCore.Http;
namespace E5Renewer.Tests.Controllers;
@@ -30,11 +31,18 @@ public JsonAPIV1ControllerTests()
IAPIFunction apiFunction = Substitute.For();
apiFunction.id.Returns("test");
-
List apiFunctions = [apiFunction];
- IUnixTimestampGenerator generator = new UnixTimestampGenerator();
- this.controller = new(logger, statusManager, apiFunctions, generator);
+ IUnixTimestampGenerator generator = Substitute.For();
+ generator.GetUnixTimestamp().Returns((long)42);
+
+ IDummyResultGenerator dummyResultGenerator = Substitute.For();
+ InvokeResult dummyResult = new();
+ HttpContext context = new DefaultHttpContext();
+ dummyResultGenerator.GenerateDummyResultAsync(context).ReturnsForAnyArgs(dummyResult);
+ dummyResultGenerator.GenerateDummyResult(context).ReturnsForAnyArgs(dummyResult);
+
+ this.controller = new(logger, statusManager, apiFunctions, generator, dummyResultGenerator);
}
/// Test
///
@@ -85,4 +93,13 @@ public async Task TestGetUserResults()
string? status = ((IEnumerable?)result.result)?.First();
Assert.AreEqual("200 - OK", status);
}
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestHandle()
+ {
+ InvokeResult result = await this.controller.Handle();
+ Assert.AreEqual(new(), result);
+ }
}
diff --git a/E5Renewer.Tests/Controllers/SimpleDummyResultGeneratorTests.cs b/E5Renewer.Tests/Controllers/SimpleDummyResultGeneratorTests.cs
new file mode 100644
index 0000000..e035299
--- /dev/null
+++ b/E5Renewer.Tests/Controllers/SimpleDummyResultGeneratorTests.cs
@@ -0,0 +1,47 @@
+using E5Renewer.Controllers;
+using E5Renewer.Models.Statistics;
+
+using NSubstitute;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Http;
+
+namespace E5Renewer.Tests.Controllers;
+
+/// Test
+///
+///
+[TestClass]
+public class SimpleDummyResultGeneratorTests
+{
+ private readonly SimpleDummyResultGenerator dummyResultGenerator;
+
+ /// Initialize with no argument.
+ public SimpleDummyResultGeneratorTests()
+ {
+ ILogger logger = Substitute.For>();
+ IUnixTimestampGenerator timestampGenerator = Substitute.For();
+ timestampGenerator.GetUnixTimestamp().ReturnsForAnyArgs((long)42);
+ this.dummyResultGenerator = new(logger, timestampGenerator);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestGenerateDummyResultAsync()
+ {
+ HttpContext context = new DefaultHttpContext();
+ InvokeResult result = await this.dummyResultGenerator.GenerateDummyResultAsync(context);
+ Assert.AreEqual((long)42, result.timestamp);
+ }
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public void TestGenerateDummyResult()
+ {
+ HttpContext context = new DefaultHttpContext();
+ InvokeResult result = this.dummyResultGenerator.GenerateDummyResult(context);
+ Assert.AreEqual((long)42, result.timestamp);
+ }
+}
diff --git a/E5Renewer.Tests/Controllers/UnspecifiedControllerTests.cs b/E5Renewer.Tests/Controllers/UnspecifiedControllerTests.cs
new file mode 100644
index 0000000..a915215
--- /dev/null
+++ b/E5Renewer.Tests/Controllers/UnspecifiedControllerTests.cs
@@ -0,0 +1,40 @@
+using E5Renewer.Controllers;
+
+using NSubstitute;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Http;
+
+namespace E5Renewer.Tests.Controllers;
+
+/// Test
+///
+///
+[TestClass]
+public class UnspecifiedControllerTests
+{
+ private readonly UnspecifiedController controller;
+
+ /// Initialize with no argument.
+ public UnspecifiedControllerTests()
+ {
+ ILogger logger = Substitute.For>();
+ IDummyResultGenerator dummyResultGenerator = Substitute.For();
+ InvokeResult result = new();
+ HttpContext context = new DefaultHttpContext();
+ dummyResultGenerator.GenerateDummyResultAsync(context).Returns(Task.FromResult(result));
+ dummyResultGenerator.GenerateDummyResult(context).Returns(result);
+ this.controller = new(logger, dummyResultGenerator);
+ this.controller.ControllerContext.HttpContext = context;
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestHandle()
+ {
+ InvokeResult result = await this.controller.Handle();
+ Assert.AreEqual(new(), result);
+ }
+}
+
diff --git a/E5Renewer.Tests/Models/Config/ConfigCertificatePasswordProviderTests.cs b/E5Renewer.Tests/Models/Config/ConfigCertificatePasswordProviderTests.cs
deleted file mode 100644
index 8b46f43..0000000
--- a/E5Renewer.Tests/Models/Config/ConfigCertificatePasswordProviderTests.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System.Security.Cryptography;
-
-using E5Renewer.Models.Config;
-
-using Microsoft.Extensions.Logging;
-
-using NSubstitute;
-
-namespace E5Renewer.Tests.Models.Config;
-
-/// Test
-///
-///
-[TestClass]
-public class ConfigCertificatePasswordProviderTests
-{
- private readonly string tmpFilePath = Path.GetTempFileName();
- private readonly ConfigCertificatePasswordProvider provider;
-
- /// Initialize with no argument.
- public ConfigCertificatePasswordProviderTests()
- {
- Random random = new();
- byte[] buffer = new byte[1024];
- random.NextBytes(buffer);
- byte[] hash = SHA512.HashData(buffer);
- string hashString = BitConverter.ToString(hash).Replace("-", "").ToLower();
- Dictionary passwords = new()
- {
- {hashString, "example-secret"}
- };
- using (Stream stream = File.OpenWrite(this.tmpFilePath))
- {
- stream.Write(buffer);
- }
- ILogger logger = Substitute.For>();
- this.provider = new(logger, passwords);
- }
- /// Test
- ///
- ///
- [TestMethod]
- public async Task TestGetPasswordForCertificateAsync()
- {
- string? password = await this.provider.GetPasswordForCertificateAsync(this.tmpFilePath);
- Assert.AreEqual("example-secret", password);
- }
-}
diff --git a/E5Renewer.Tests/Models/Config/JsonConfigParserTests.cs b/E5Renewer.Tests/Models/Config/JsonConfigParserTests.cs
deleted file mode 100644
index 8ecbef3..0000000
--- a/E5Renewer.Tests/Models/Config/JsonConfigParserTests.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using E5Renewer.Models.Config;
-
-namespace E5Renewer.Tests.Models.Config;
-
-/// Test
-///
-///
-[TestClass]
-public class JsonConfigParserTests
-{
- private readonly JsonConfigParser parser = new();
-
- /// Ensure name.
- [TestMethod]
- public void TestName() => Assert.AreEqual("JsonConfigParser", this.parser.name);
-
- /// Ensure author.
- [TestMethod]
- public void TestAuthor() => Assert.AreEqual("E5Renewer", this.parser.author);
-
- /// Test
- ///
- ///
- [TestMethod]
- [DataRow("test.toml", false)]
- [DataRow("test.yaml", false)]
- [DataRow("test.json", true)]
- public void TestIsSupported(string path, bool result) => Assert.AreEqual(result, this.parser.IsSupported(path));
-
- /// Test
- ///
- ///
- [TestMethod]
- [DataRow("{}", false)]
- [DataRow("{\"auth_token\": \"test-token\"}", true)]
- public async Task TestParseConfigAsyncAuthToken(string json, bool result)
- {
- string tmpPath = Path.GetTempFileName();
- await File.WriteAllTextAsync(tmpPath, json);
- E5Renewer.Models.Config.Config config = await this.parser.ParseConfigAsync(tmpPath);
- bool compareResult = config.authToken == "test-token";
- Assert.AreEqual(result, compareResult);
- }
-}
diff --git a/E5Renewer.Tests/Models/Modules/DeprecatedModulesCheckerTests.cs b/E5Renewer.Tests/Models/Modules/DeprecatedModulesCheckerTests.cs
new file mode 100644
index 0000000..2e81898
--- /dev/null
+++ b/E5Renewer.Tests/Models/Modules/DeprecatedModulesCheckerTests.cs
@@ -0,0 +1,33 @@
+using E5Renewer.Models.Modules;
+
+using NSubstitute;
+using Microsoft.Extensions.Logging;
+
+namespace E5Renewer.Tests.Models.Modules;
+
+/// Test
+///
+///
+[TestClass]
+public class DeprecatedModulesCheckerTests
+{
+ private readonly DeprecatedModulesChecker checker;
+
+ /// Initialize with no argument.
+ public DeprecatedModulesCheckerTests()
+ {
+ ILogger logger = Substitute.For>();
+ this.checker = new(logger);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public void TestCheckModules()
+ {
+ IModule module = Substitute.For();
+ module.isDeprecated.Returns(false);
+ this.checker.CheckModules(module);
+ }
+}
diff --git a/E5Renewer.Tests/Models/Secrets/Json/JsonUserSecretLoaderTests.cs b/E5Renewer.Tests/Models/Secrets/Json/JsonUserSecretLoaderTests.cs
new file mode 100644
index 0000000..b7a76e3
--- /dev/null
+++ b/E5Renewer.Tests/Models/Secrets/Json/JsonUserSecretLoaderTests.cs
@@ -0,0 +1,55 @@
+using E5Renewer.Models.Secrets;
+using E5Renewer.Models.Secrets.Json;
+
+namespace E5Renewer.Tests.Models.Secrets.Json;
+
+/// Test
+///
+///
+[TestClass]
+public class JsonUserSecretLoaderTests : UserSecretLoaderTests
+{
+ private const string validJsonContentWithSecret = """{"users":[{"name":"test", "tenant_id":"test","client_id":"test","secret":"test"}]}""";
+ private const string validJsonContentWithSecretAndDays = """{"users":[{"name":"test", "tenant_id":"test","client_id":"test","secret":"test", "days": [1]}]}""";
+
+ private const string invalidJsonContentWithCertificateNotExist = """{"users":[{"name":"test", "tenant_id":"test","client_id":"test","certificate":"not-exist"}]}""";
+ private const string invalidJsonContentWithoutSecretOrCertificate = """{"users":[{"name":"test", "tenant_id":"test","client_id":"test"}]}""";
+
+ private readonly JsonUserSecretLoader loader;
+
+ /// Initialize with no argument.
+ public JsonUserSecretLoaderTests()
+ {
+ this.loader = new();
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ [DataRow("/path/to/json", false)]
+ [DataRow("C:\\json", false)]
+ [DataRow("/path/to/file.json", true)]
+ [DataRow("C:\\file.json", true)]
+ public override void TestIsSupported(string path, bool result)
+ {
+ FileInfo info = new(path);
+ bool actual = this.loader.IsSupported(info);
+ Assert.AreEqual(result, actual);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ [DataRow(validJsonContentWithSecret, true)]
+ [DataRow(validJsonContentWithSecretAndDays, true)]
+ [DataRow(invalidJsonContentWithoutSecretOrCertificate, false)]
+ [DataRow(invalidJsonContentWithCertificateNotExist, false)]
+ public override async Task TestLoadSecretAsync(string jsonContent, bool expected)
+ {
+ FileInfo tempFile = await this.PrepareContent(jsonContent);
+ UserSecret secret = await this.loader.LoadSecretAsync(tempFile);
+ Assert.AreEqual(expected, secret.valid);
+ }
+}
diff --git a/E5Renewer.Tests/Models/Secrets/SimpleSecretProviderTests.cs b/E5Renewer.Tests/Models/Secrets/SimpleSecretProviderTests.cs
new file mode 100644
index 0000000..5c80c0e
--- /dev/null
+++ b/E5Renewer.Tests/Models/Secrets/SimpleSecretProviderTests.cs
@@ -0,0 +1,61 @@
+using E5Renewer.Models.Secrets;
+using NSubstitute;
+using Microsoft.Extensions.Logging;
+
+namespace E5Renewer.Tests.Models.Secrets;
+
+/// Test
+///
+///
+[TestClass]
+public class SimpleSecretProviderTests
+{
+ private readonly SimpleSecretProvider provider;
+
+ /// Initialize with no argument.
+ public SimpleSecretProviderTests()
+ {
+ ILogger logger = Substitute.For>();
+ FileInfo userSecret = new("test");
+ IUserSecretLoader userSecretLoader = Substitute.For();
+ UserSecret secret = new();
+ userSecretLoader.IsSupported(userSecret).Returns(true);
+ userSecretLoader.LoadSecretAsync(userSecret).Returns(Task.FromResult(secret));
+ IEnumerable userSecretLoaders = Substitute.For>();
+ IUserSecretLoader[] userSecretLoaderArray = [userSecretLoader];
+ userSecretLoaders.GetEnumerator().Returns(userSecretLoaderArray.ToList().GetEnumerator());
+ this.provider = new(logger, userSecret, userSecretLoaders);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestGetPasswordForCertificateAsync()
+ {
+ FileInfo noExist = new("no-exist");
+ string? password = await this.provider.GetPasswordForCertificateAsync(noExist);
+ Assert.IsNull(password);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestGetRuntimeTokenAsync()
+ {
+ string token = await this.provider.GetRuntimeTokenAsync();
+ bool tokenNull = string.IsNullOrEmpty(token);
+ Assert.IsFalse(tokenNull);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestGetUserSecretAsync()
+ {
+ UserSecret secret = await this.provider.GetUserSecretAsync();
+ Assert.AreEqual(new(), secret);
+ }
+}
diff --git a/E5Renewer.Tests/Models/Secrets/Toml/TomlUserSecretLoaderTests.cs b/E5Renewer.Tests/Models/Secrets/Toml/TomlUserSecretLoaderTests.cs
new file mode 100644
index 0000000..3245630
--- /dev/null
+++ b/E5Renewer.Tests/Models/Secrets/Toml/TomlUserSecretLoaderTests.cs
@@ -0,0 +1,80 @@
+using E5Renewer.Models.Secrets;
+using E5Renewer.Models.Secrets.Toml;
+
+namespace E5Renewer.Tests.Models.Secrets.Toml;
+
+/// Test
+///
+///
+[TestClass]
+public class TomlUserSecretLoaderTests : UserSecretLoaderTests
+{
+ private const string validTomlContentWithSecret =
+ """
+ [[users]]
+ name="test"
+ tenant_id="test"
+ client_id="test"
+ secret="test"
+ """;
+ private const string validTomlContentWithSecretAndDays =
+ """
+ [[users]]
+ name="test"
+ tenant_id="test"
+ client_id="test"
+ secret="test"
+ days=[1]
+ """;
+
+ private const string invalidTomlContentWithCertificateNotExist =
+ """
+ [[users]]
+ name="test"
+ tenant_id="test"
+ client_id="test"
+ certificate="not-exist"
+ """;
+ private const string invalidTomlContentWithoutSecretOrCertificate =
+ """
+ [[users]]
+ name="test"
+ tenant_id="test"
+ client_id="test"
+ """;
+
+ private readonly TomlUserSecretLoader loader;
+
+ /// Initialize with no argument.
+ public TomlUserSecretLoaderTests() => this.loader = new();
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ [DataRow("/path/to/toml", false)]
+ [DataRow("C:\\toml", false)]
+ [DataRow("/path/to/file.toml", true)]
+ [DataRow("C:\\file.toml", true)]
+ public override void TestIsSupported(string path, bool result)
+ {
+ FileInfo info = new(path);
+ bool actual = this.loader.IsSupported(info);
+ Assert.AreEqual(result, actual);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ [DataRow(validTomlContentWithSecret, true)]
+ [DataRow(validTomlContentWithSecretAndDays, true)]
+ [DataRow(invalidTomlContentWithoutSecretOrCertificate, false)]
+ [DataRow(invalidTomlContentWithCertificateNotExist, false)]
+ public override async Task TestLoadSecretAsync(string tomlContent, bool expected)
+ {
+ FileInfo tempFile = await this.PrepareContent(tomlContent);
+ UserSecret secret = await this.loader.LoadSecretAsync(tempFile);
+ Assert.AreEqual(expected, secret.valid);
+ }
+}
diff --git a/E5Renewer.Tests/Models/Secrets/UserSecretLoaderTests.cs b/E5Renewer.Tests/Models/Secrets/UserSecretLoaderTests.cs
new file mode 100644
index 0000000..0a4e4c1
--- /dev/null
+++ b/E5Renewer.Tests/Models/Secrets/UserSecretLoaderTests.cs
@@ -0,0 +1,28 @@
+using E5Renewer.Models.Secrets;
+
+namespace E5Renewer.Tests.Models.Secrets;
+
+/// Base class to provide some utils for testing
+/// implementations.
+///
+public abstract class UserSecretLoaderTests
+{
+ /// Generate a temp file with content given.
+ /// The of temp file.
+ protected async Task PrepareContent(string content)
+ {
+ FileInfo tempFile = new(Path.GetTempFileName());
+ using (StreamWriter streamWriter = tempFile.CreateText())
+ {
+ await streamWriter.WriteAsync(content);
+ await streamWriter.FlushAsync();
+ }
+ return tempFile;
+ }
+
+ /// Test
+ public abstract void TestIsSupported(string path, bool result);
+
+ /// Test
+ public abstract Task TestLoadSecretAsync(string jsonContent, bool expected);
+}
diff --git a/E5Renewer.Tests/Models/Secrets/Yaml/YamlUserSecretLoaderTests.cs b/E5Renewer.Tests/Models/Secrets/Yaml/YamlUserSecretLoaderTests.cs
new file mode 100644
index 0000000..886591f
--- /dev/null
+++ b/E5Renewer.Tests/Models/Secrets/Yaml/YamlUserSecretLoaderTests.cs
@@ -0,0 +1,85 @@
+using E5Renewer.Models.Secrets;
+using E5Renewer.Models.Secrets.Yaml;
+
+namespace E5Renewer.Tests.Models.Secrets.Yaml;
+
+/// Test
+///
+///
+[TestClass]
+public class YamlUserSecretLoaderTests : UserSecretLoaderTests
+{
+ private const string validYamlContentWithSecret =
+ """
+ users:
+ - name: test
+ tenant_id: test
+ client_id: test
+ secret: test
+ """;
+ private const string validYamlContentWithSecretAndDays =
+ """
+ users:
+ - name: test
+ tenant_id: test
+ client_id: test
+ secret: test
+ days:
+ - 1
+ """;
+
+ private const string invalidYamlContentWithCertificateNotExist =
+ """
+ users:
+ - name: test
+ tenant_id: test
+ client_id: test
+ certificate: not-exist
+ """;
+ private const string invalidYamlContentWithNoSecretOrCertificate =
+ """
+ users:
+ - name: test
+ tenant_id: test
+ client_id: test
+ """;
+
+ private readonly YamlUserSecretLoader loader;
+
+ /// Initialize with no argument.
+ public YamlUserSecretLoaderTests() => this.loader = new();
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ [DataRow("/path/to/yaml", false)]
+ [DataRow("/path/to/yml", false)]
+ [DataRow("C:\\yaml", false)]
+ [DataRow("C:\\yml", false)]
+ [DataRow("/path/to/file.yaml", true)]
+ [DataRow("/path/to/file.yml", true)]
+ [DataRow("C:\\file.yaml", true)]
+ [DataRow("C:\\file.yml", true)]
+ public override void TestIsSupported(string path, bool result)
+ {
+ FileInfo info = new(path);
+ bool actual = this.loader.IsSupported(info);
+ Assert.AreEqual(result, actual);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ [DataRow(validYamlContentWithSecret, true)]
+ [DataRow(validYamlContentWithSecretAndDays, true)]
+ [DataRow(invalidYamlContentWithCertificateNotExist, false)]
+ [DataRow(invalidYamlContentWithNoSecretOrCertificate, false)]
+ public override async Task TestLoadSecretAsync(string yamlContent, bool expected)
+ {
+ FileInfo tempFile = await this.PrepareContent(yamlContent);
+ UserSecret secret = await this.loader.LoadSecretAsync(tempFile);
+ Assert.AreEqual(expected, secret.valid);
+ }
+}
diff --git a/E5Renewer.Tests/Models/Statistics/MemoryStatusManagerTests.cs b/E5Renewer.Tests/Models/Statistics/MemoryStatusManagerTests.cs
new file mode 100644
index 0000000..d308229
--- /dev/null
+++ b/E5Renewer.Tests/Models/Statistics/MemoryStatusManagerTests.cs
@@ -0,0 +1,75 @@
+using E5Renewer.Models.Statistics;
+
+namespace E5Renewer.Tests.Models.Statistics;
+
+/// Test
+///
+///
+[TestClass]
+public class MemoryStatusManagerTests
+{
+ private readonly MemoryStatusManager manager;
+
+ /// Initialize with no argument.
+ public MemoryStatusManagerTests()
+ {
+ this.manager = new();
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestGetRunningUsersAsync()
+ {
+ IEnumerable runningUsers = await this.manager.GetRunningUsersAsync();
+ int count = runningUsers.Count();
+ Assert.AreEqual(0, count);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestGetWaitingUsersAsync()
+ {
+ IEnumerable waitingUsers = await this.manager.GetWaitingUsersAsync();
+ int count = waitingUsers.Count();
+ Assert.AreEqual(0, count);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ [DataRow("test-running", true, 1, 0)]
+ [DataRow("test-waiting", false, 0, 1)]
+ public async Task TestSetUserStatusAsync(string name, bool running, int targetRunningCount, int targetWaitingCount)
+ {
+ await this.manager.SetUserStatusAsync(name, running);
+ int runningCount = (await this.manager.GetRunningUsersAsync()).Count();
+ int waitingCount = (await this.manager.GetWaitingUsersAsync()).Count();
+ Assert.AreEqual(targetRunningCount, runningCount);
+ Assert.AreEqual(targetWaitingCount, waitingCount);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestGetResultsAsync()
+ {
+ IEnumerable results = await this.manager.GetResultsAsync("test", "Test.Example");
+ int count = results.Count();
+ Assert.AreEqual(0, count);
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public async Task TestSetResultAsync()
+ {
+ await this.manager.SetResultAsync("test", "Test.Example.Set", "Success");
+ }
+}
diff --git a/E5Renewer.Tests/Models/Statistics/UnixTimestampGeneratorTests.cs b/E5Renewer.Tests/Models/Statistics/UnixTimestampGeneratorTests.cs
new file mode 100644
index 0000000..adee162
--- /dev/null
+++ b/E5Renewer.Tests/Models/Statistics/UnixTimestampGeneratorTests.cs
@@ -0,0 +1,29 @@
+using E5Renewer.Models.Statistics;
+
+namespace E5Renewer.Tests.Models.Statistics;
+
+/// Test
+///
+///
+[TestClass]
+public class UnixTimestampGeneratorTests
+{
+ private readonly UnixTimestampGenerator generator;
+
+ /// Initialize with no argument.
+ public UnixTimestampGeneratorTests()
+ {
+ this.generator = new();
+ }
+
+ /// Test
+ ///
+ ///
+ [TestMethod]
+ public void TestGetUnixTimestamp()
+ {
+ long result1 = this.generator.GetUnixTimestamp();
+ long result2 = this.generator.GetUnixTimestamp();
+ Assert.AreNotSame(result1, result2);
+ }
+}
diff --git a/E5Renewer.Tests/Models/UintExtendsTests.cs b/E5Renewer.Tests/Models/UintExtendsTests.cs
deleted file mode 100644
index 706df8f..0000000
--- a/E5Renewer.Tests/Models/UintExtendsTests.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using E5Renewer.Models;
-
-namespace E5Renewer.Tests.Models;
-
-/// Test
-///
-///
-[TestClass]
-public class UintExtendsTests
-{
- ///
- /// Test
- ///
- ///
- [TestMethod]
- [DataRow(0b000000000, UnixFileMode.None)]
- [DataRow(0b000000001, UnixFileMode.OtherExecute)]
- [DataRow(0b000000010, UnixFileMode.OtherWrite)]
- [DataRow(0b000000011, UnixFileMode.OtherWrite | UnixFileMode.OtherExecute)]
- [DataRow(0b000000100, UnixFileMode.OtherRead)]
- [DataRow(0b000000101, UnixFileMode.OtherRead | UnixFileMode.OtherExecute)]
- [DataRow(0b000000110, UnixFileMode.OtherRead | UnixFileMode.OtherWrite)]
- [DataRow(0b000000111, UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute)]
- [DataRow(0b000001000, UnixFileMode.GroupExecute)]
- [DataRow(0b000010000, UnixFileMode.GroupWrite)]
- [DataRow(0b000011000, UnixFileMode.GroupWrite | UnixFileMode.GroupExecute)]
- [DataRow(0b000100000, UnixFileMode.GroupRead)]
- [DataRow(0b000101000, UnixFileMode.GroupRead | UnixFileMode.GroupExecute)]
- [DataRow(0b000110000, UnixFileMode.GroupRead | UnixFileMode.GroupWrite)]
- [DataRow(0b000111000, UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute)]
- [DataRow(0b001000000, UnixFileMode.UserExecute)]
- [DataRow(0b010000000, UnixFileMode.UserWrite)]
- [DataRow(0b011000000, UnixFileMode.UserWrite | UnixFileMode.UserExecute)]
- [DataRow(0b100000000, UnixFileMode.UserRead)]
- [DataRow(0b101000000, UnixFileMode.UserRead | UnixFileMode.UserExecute)]
- [DataRow(0b110000000, UnixFileMode.UserRead | UnixFileMode.UserWrite)]
- [DataRow(0b111000000, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute)]
- [DataRow(0b100100100, UnixFileMode.UserRead | UnixFileMode.GroupRead | UnixFileMode.OtherRead)]
- public void TestToUnixFileMode(int permission, UnixFileMode mode)
- {
- Assert.AreEqual(mode, ((uint)permission).ToUnixFileMode());
- }
-}
diff --git a/E5Renewer.Tests/TypeArrayExtendsTests.cs b/E5Renewer.Tests/TypeArrayExtendsTests.cs
deleted file mode 100644
index 49e10c2..0000000
--- a/E5Renewer.Tests/TypeArrayExtendsTests.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace E5Renewer.Tests;
-
-///
-/// Test
-///
-///
-[TestClass]
-public class TypeArrayExtendsTests
-{
- private void TestGetNonAbstractClassesAssainableToHelper(uint count)
- {
- IEnumerable typesFound = typeof(TypeArrayExtendsTests).Assembly.GetTypes().GetNonAbstractClassesAssainableTo();
- Assert.AreEqual((int)count, typesFound.Count());
- }
-
- ///
- /// Test
- ///
- ///
- [TestMethod]
- public void TestGetNonAbstractClassesAssainableTo()
- {
- this.TestGetNonAbstractClassesAssainableToHelper(1);
- }
-}
diff --git a/E5Renewer.Tests/WebApplicationExtendsTests.cs b/E5Renewer.Tests/WebApplicationExtendsTests.cs
deleted file mode 100644
index 4348d28..0000000
--- a/E5Renewer.Tests/WebApplicationExtendsTests.cs
+++ /dev/null
@@ -1,289 +0,0 @@
-using System.Net;
-using System.Net.Http.Json;
-
-using E5Renewer.Models.Statistics;
-
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.TestHost;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace E5Renewer.Tests;
-
-/// Test
-/// .
-///
-[TestClass]
-public class WebApplicationExtendsTests
-{
- private static readonly Uri baseAddress = new("http://localhost:65530/");
- private const string requestUri = "/test";
- private static readonly Func responseAction = () => "OK";
- /// Test
- ///
- ///
- [TestMethod]
- [DataRow("example-auth-token-invalid", HttpStatusCode.Forbidden)]
- [DataRow("example-auth-token", HttpStatusCode.OK)]
- public async Task TestUseAuthTokenAuthentication(string token, HttpStatusCode target)
- {
- const string validAuthToken = "example-auth-token";
-
- WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder();
- webApplicationBuilder.WebHost.UseTestServer((opt) => opt.BaseAddress = WebApplicationExtendsTests.baseAddress);
- using (WebApplication app = webApplicationBuilder.Build())
- {
- app.UseAuthTokenAuthentication(validAuthToken);
- app.MapGet(WebApplicationExtendsTests.requestUri, WebApplicationExtendsTests.responseAction);
- await app.StartAsync();
- HttpClient client = app.GetTestClient();
-
- client.DefaultRequestHeaders.Add("Authentication", token);
- using (HttpResponseMessage response = await client.GetAsync(WebApplicationExtendsTests.requestUri))
- {
- Assert.AreEqual(target, response.StatusCode);
- }
- await app.StopAsync();
- }
- }
- /// Test
- ///
- ///
- [TestMethod]
- public async Task TestUseAuthTokenAuthentication()
- {
- WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder();
- webApplicationBuilder.WebHost.UseTestServer((opt) => opt.BaseAddress = WebApplicationExtendsTests.baseAddress);
- using (WebApplication app = webApplicationBuilder.Build())
- {
- app.UseAuthTokenAuthentication("example-auth-token");
- app.MapGet(WebApplicationExtendsTests.requestUri, WebApplicationExtendsTests.responseAction);
- await app.StartAsync();
- HttpClient client = app.GetTestClient();
-
- using (HttpResponseMessage response = await client.GetAsync(WebApplicationExtendsTests.requestUri))
- {
- Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
- }
- await app.StopAsync();
- }
- }
-
- /// Test
- ///
- ///
- [TestMethod]
- [DataRow("GET", HttpStatusCode.OK)]
- [DataRow("POST", HttpStatusCode.MethodNotAllowed)]
- public async Task TestUseHttpMethodChecker(string method, HttpStatusCode target)
- {
- const string methodAllowed = "GET";
-
- WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder();
- webApplicationBuilder.WebHost.UseTestServer((opt) => opt.BaseAddress = WebApplicationExtendsTests.baseAddress);
- using (WebApplication app = webApplicationBuilder.Build())
- {
- app.UseHttpMethodChecker(methodAllowed);
- app.MapGet(WebApplicationExtendsTests.requestUri, WebApplicationExtendsTests.responseAction);
- await app.StartAsync();
- HttpClient client = app.GetTestClient();
-
- HttpRequestMessage msg = new(
- new(method),
- WebApplicationExtendsTests.requestUri
- );
- using (HttpResponseMessage response = await client.SendAsync(msg))
- {
- Assert.AreEqual(target, response.StatusCode);
- }
- await app.StopAsync();
- }
-
- }
-
- /// Test
- ///
- ///
- [TestClass]
- public class TestUseUnixTimestampChecker
- {
- /// Test
- ///
- /// When no timestamp in get request.
- ///
- [TestMethod]
- public async Task TestUseUnixTimestampCheckerMissingGet()
- {
- WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder();
- webApplicationBuilder.WebHost.UseTestServer((opt) => opt.BaseAddress = WebApplicationExtendsTests.baseAddress);
- webApplicationBuilder.Services.AddSingleton();
- using (WebApplication app = webApplicationBuilder.Build())
- {
- app.UseUnixTimestampChecker();
- app.MapGet(WebApplicationExtendsTests.requestUri, WebApplicationExtendsTests.responseAction);
- await app.StartAsync();
- HttpClient client = app.GetTestClient();
- using (HttpResponseMessage response = await client.GetAsync(WebApplicationExtendsTests.requestUri))
- {
- Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
- }
- await app.StopAsync();
- }
- }
- /// Test
- ///
- /// When no timestamp in post request.
- ///
- [TestMethod]
- public async Task TestUseUnixTimestampCheckerMissingPost()
- {
- WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder();
- webApplicationBuilder.WebHost.UseTestServer((opt) => opt.BaseAddress = WebApplicationExtendsTests.baseAddress);
- webApplicationBuilder.Services.AddSingleton();
- using (WebApplication app = webApplicationBuilder.Build())
- {
- app.UseUnixTimestampChecker();
- app.MapPost(WebApplicationExtendsTests.requestUri, WebApplicationExtendsTests.responseAction);
- await app.StartAsync();
- HttpClient client = app.GetTestClient();
- using (HttpResponseMessage response = await client.PostAsync(
- WebApplicationExtendsTests.requestUri,
- new StringContent("{}")
- ))
- {
- Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
- }
- await app.StopAsync();
- }
- }
- /// Test
- ///
- /// When invalid timestamp in get request.
- ///
- [TestMethod]
- public async Task TestUseUnixTimestampCheckerInvalidGet()
- {
- WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder();
- webApplicationBuilder.WebHost.UseTestServer();
- webApplicationBuilder.Services.AddSingleton();
- using (WebApplication app = webApplicationBuilder.Build())
- {
- app.UseUnixTimestampChecker();
- app.MapGet(WebApplicationExtendsTests.requestUri, WebApplicationExtendsTests.responseAction);
- await app.StartAsync();
- HttpClient client = app.GetTestClient();
-
- UnixTimestampGenerator timestampGenerator = new();
- long badTimestamp = timestampGenerator.GetUnixTimestamp() - 40 * 1000;
- using (HttpResponseMessage response = await client.GetAsync(
- string.Format(
- "{0}?timestamp={1}",
- WebApplicationExtendsTests.requestUri,
- badTimestamp.ToString()
- )
- ))
- {
- Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
- }
- await app.StopAsync();
- }
- }
- /// Test
- ///
- /// When invalid timestamp in post request.
- ///
- [TestMethod]
- public async Task TestUseUnixTimestampCheckerInvalidPost()
- {
- WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder();
- webApplicationBuilder.WebHost.UseTestServer((opt) => opt.BaseAddress = WebApplicationExtendsTests.baseAddress);
- webApplicationBuilder.Services.AddSingleton();
- using (WebApplication app = webApplicationBuilder.Build())
- {
- app.UseUnixTimestampChecker();
- app.MapPost(WebApplicationExtendsTests.requestUri, WebApplicationExtendsTests.responseAction);
- await app.StartAsync();
- HttpClient client = app.GetTestClient();
-
- UnixTimestampGenerator timestampGenerator = new();
- long badTimestamp = timestampGenerator.GetUnixTimestamp() - 40 * 1000;
- Dictionary data = new()
- {
- {"timestamp", badTimestamp.ToString()}
- };
- using (HttpResponseMessage response = await client.PostAsync(
- WebApplicationExtendsTests.requestUri,
- JsonContent.Create(data)
- ))
- {
- Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
- }
- await app.StopAsync();
- }
- }
- /// Test
- ///
- /// When timestamp is correct in get request.
- ///
- [TestMethod]
- public async Task TestUseUnixTimestampCheckerGet()
- {
- WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder();
- webApplicationBuilder.WebHost.UseTestServer((opt) => opt.BaseAddress = WebApplicationExtendsTests.baseAddress);
- webApplicationBuilder.Services.AddSingleton();
- using (WebApplication app = webApplicationBuilder.Build())
- {
- app.UseUnixTimestampChecker();
- app.MapGet(WebApplicationExtendsTests.requestUri, WebApplicationExtendsTests.responseAction);
- await app.StartAsync();
- HttpClient client = app.GetTestClient();
-
- UnixTimestampGenerator timestampGenerator = new();
- long timestamp = timestampGenerator.GetUnixTimestamp();
- using (HttpResponseMessage response = await client.GetAsync(
- string.Format(
- "{0}?timestamp={1}",
- WebApplicationExtendsTests.requestUri,
- timestamp.ToString()
- )
- ))
- {
- Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
- }
- await app.StopAsync();
- }
- }
- /// Test
- ///
- /// When timestamp is correct in post request.
- ///
- [TestMethod]
- public async Task TestUseUnixTimestampCheckerPost()
- {
- WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder();
- webApplicationBuilder.WebHost.UseTestServer((opt) => opt.BaseAddress = WebApplicationExtendsTests.baseAddress);
- webApplicationBuilder.Services.AddSingleton();
- using (WebApplication app = webApplicationBuilder.Build())
- {
- app.UseUnixTimestampChecker();
- app.MapPost(WebApplicationExtendsTests.requestUri, WebApplicationExtendsTests.responseAction);
- await app.StartAsync();
- HttpClient client = app.GetTestClient();
-
- UnixTimestampGenerator timestampGenerator = new();
- long timestamp = timestampGenerator.GetUnixTimestamp();
- Dictionary data = new()
- {
- {"timestamp", timestamp.ToString()}
- };
- using (HttpResponseMessage response = await client.PostAsync(
- WebApplicationExtendsTests.requestUri,
- JsonContent.Create(data)
- ))
- {
- Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
- }
- await app.StopAsync();
- }
- }
- }
-}
diff --git a/E5Renewer.sln b/E5Renewer.sln
index 173f7b8..f3933ed 100644
--- a/E5Renewer.sln
+++ b/E5Renewer.sln
@@ -1,4 +1,4 @@
-
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
@@ -7,14 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E5Renewer", "E5Renewer\E5Re
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E5Renewer.Tests", "E5Renewer.Tests\E5Renewer.Tests.csproj", "{74395711-5B07-4A82-841B-FCF2FF33D8D9}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E5Renewer.Modules.TomlParser", "E5Renewer.Modules.TomlParser\E5Renewer.Modules.TomlParser.csproj", "{26DEFD21-4507-4FB3-89F7-D113C1157F7D}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E5Renewer.Modules.YamlParser", "E5Renewer.Modules.YamlParser\E5Renewer.Modules.YamlParser.csproj", "{461922E6-887C-4C49-A703-EEAE9883A99E}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E5Renewer.Modules.TomlParser.Tests", "E5Renewer.Modules.TomlParser.Tests\E5Renewer.Modules.TomlParser.Tests.csproj", "{79622D22-A66D-486D-B615-7917F6234F23}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E5Renewer.Modules.YamlParser.Tests", "E5Renewer.Modules.YamlParser.Tests\E5Renewer.Modules.YamlParser.Tests.csproj", "{088145AC-FF9F-4A20-8146-493587DF5D83}"
-EndProject
Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -52,53 +44,5 @@ Global
{74395711-5B07-4A82-841B-FCF2FF33D8D9}.Release|x64.Build.0 = Release|Any CPU
{74395711-5B07-4A82-841B-FCF2FF33D8D9}.Release|x86.ActiveCfg = Release|Any CPU
{74395711-5B07-4A82-841B-FCF2FF33D8D9}.Release|x86.Build.0 = Release|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Debug|x64.ActiveCfg = Debug|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Debug|x64.Build.0 = Debug|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Debug|x86.ActiveCfg = Debug|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Debug|x86.Build.0 = Debug|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Release|Any CPU.Build.0 = Release|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Release|x64.ActiveCfg = Release|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Release|x64.Build.0 = Release|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Release|x86.ActiveCfg = Release|Any CPU
- {26DEFD21-4507-4FB3-89F7-D113C1157F7D}.Release|x86.Build.0 = Release|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Debug|x64.ActiveCfg = Debug|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Debug|x64.Build.0 = Debug|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Debug|x86.ActiveCfg = Debug|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Debug|x86.Build.0 = Debug|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Release|Any CPU.Build.0 = Release|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Release|x64.ActiveCfg = Release|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Release|x64.Build.0 = Release|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Release|x86.ActiveCfg = Release|Any CPU
- {461922E6-887C-4C49-A703-EEAE9883A99E}.Release|x86.Build.0 = Release|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Debug|x64.ActiveCfg = Debug|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Debug|x64.Build.0 = Debug|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Debug|x86.ActiveCfg = Debug|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Debug|x86.Build.0 = Debug|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Release|Any CPU.Build.0 = Release|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Release|x64.ActiveCfg = Release|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Release|x64.Build.0 = Release|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Release|x86.ActiveCfg = Release|Any CPU
- {79622D22-A66D-486D-B615-7917F6234F23}.Release|x86.Build.0 = Release|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Debug|x64.ActiveCfg = Debug|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Debug|x64.Build.0 = Debug|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Debug|x86.ActiveCfg = Debug|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Debug|x86.Build.0 = Debug|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Release|Any CPU.Build.0 = Release|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Release|x64.ActiveCfg = Release|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Release|x64.Build.0 = Release|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Release|x86.ActiveCfg = Release|Any CPU
- {088145AC-FF9F-4A20-8146-493587DF5D83}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/E5Renewer/AssemblyExtends.cs b/E5Renewer/AssemblyExtends.cs
new file mode 100644
index 0000000..a4c1608
--- /dev/null
+++ b/E5Renewer/AssemblyExtends.cs
@@ -0,0 +1,16 @@
+using System.Reflection;
+
+using E5Renewer.Models.Modules;
+
+namespace E5Renewer
+{
+ internal static class AssemblyExtends
+ {
+ public static IEnumerable IterE5RenewerModules(this Assembly assembly)
+ {
+ return assembly.GetTypes().GetNonAbstractClassesAssainableTo().Where(
+ (t) => t.IsDefined(typeof(ModuleAttribute))
+ );
+ }
+ }
+}
diff --git a/E5Renewer/Controllers/IDummyResultGenerator.cs b/E5Renewer/Controllers/IDummyResultGenerator.cs
new file mode 100644
index 0000000..f800e05
--- /dev/null
+++ b/E5Renewer/Controllers/IDummyResultGenerator.cs
@@ -0,0 +1,12 @@
+namespace E5Renewer.Controllers
+{
+ /// The api interface to generate dummy response.
+ public interface IDummyResultGenerator
+ {
+ /// Generate a dummy result when something not right.
+ public Task GenerateDummyResultAsync(HttpContext httpContext);
+
+ /// Generate a dummy result when something not right.
+ public InvokeResult GenerateDummyResult(HttpContext httpContext);
+ }
+}
diff --git a/E5Renewer/Controllers/JsonAPIV1Controller.cs b/E5Renewer/Controllers/JsonAPIV1Controller.cs
index 9fbb582..872492b 100644
--- a/E5Renewer/Controllers/JsonAPIV1Controller.cs
+++ b/E5Renewer/Controllers/JsonAPIV1Controller.cs
@@ -14,25 +14,24 @@ public class JsonAPIV1Controller : ControllerBase
private readonly IStatusManager statusManager;
private readonly IEnumerable apiFunctions;
private readonly IUnixTimestampGenerator unixTimestampGenerator;
+ private readonly IDummyResultGenerator dummyResponseGenerator;
/// Initialize controller by logger given.
/// The logger to create logs.
/// The implementation.
/// The implementation.
/// The implementation.
+ /// The implementation.
/// All the params are injected by Asp.Net Core runtime.
public JsonAPIV1Controller(
ILogger logger,
IStatusManager statusManager,
IEnumerable apiFunctions,
- IUnixTimestampGenerator unixTimestampGenerator
- )
- {
- this.logger = logger;
- this.statusManager = statusManager;
- this.apiFunctions = apiFunctions;
- this.unixTimestampGenerator = unixTimestampGenerator;
- }
+ IUnixTimestampGenerator unixTimestampGenerator,
+ IDummyResultGenerator dummyResponseGenerator
+ ) =>
+ (this.logger, this.statusManager, this.apiFunctions, this.unixTimestampGenerator, this.dummyResponseGenerator) =
+ (logger, statusManager, apiFunctions, unixTimestampGenerator, dummyResponseGenerator);
/// Handler for /v1/list_apis.
[HttpGet("list_apis")]
@@ -99,5 +98,10 @@ string apiName
this.unixTimestampGenerator.GetUnixTimestamp()
);
}
+
+ /// Handler for /v1/*.
+ [Route("{*method}")]
+ public async ValueTask Handle() =>
+ await this.dummyResponseGenerator.GenerateDummyResultAsync(this.HttpContext);
}
}
diff --git a/E5Renewer/Controllers/SimpleDummyResultGenerator.cs b/E5Renewer/Controllers/SimpleDummyResultGenerator.cs
new file mode 100644
index 0000000..882a73d
--- /dev/null
+++ b/E5Renewer/Controllers/SimpleDummyResultGenerator.cs
@@ -0,0 +1,62 @@
+using System.Text.Json;
+
+using E5Renewer.Models.Statistics;
+
+namespace E5Renewer.Controllers
+{
+ /// Generate a dummy result when something is not right.
+ public class SimpleDummyResultGenerator : IDummyResultGenerator
+ {
+ private readonly ILogger logger;
+ private readonly IUnixTimestampGenerator unixTimestampGenerator;
+
+ /// Initialize with arguments given.
+ /// The logger to generate log.
+ /// The implementation.
+ /// All parameters should be injected by Asp.Net Core.
+ public SimpleDummyResultGenerator(ILogger logger, IUnixTimestampGenerator unixTimestampGenerator) =>
+ (this.logger, this.unixTimestampGenerator) = (logger, unixTimestampGenerator);
+
+ ///
+ public async Task GenerateDummyResultAsync(HttpContext httpContext)
+ {
+ Dictionary queries;
+ switch (httpContext.Request.Method)
+ {
+ case "GET":
+ queries = httpContext.Request.Query.Select(
+ (kv) => new KeyValuePair(kv.Key, kv.Value.FirstOrDefault() as object)
+ ).ToDictionary();
+ break;
+ case "POST":
+ byte[] buffer = new byte[httpContext.Request.ContentLength ?? httpContext.Request.Body.Length];
+ int length = await httpContext.Request.Body.ReadAsync(buffer);
+ byte[] contents = buffer.Take(length).ToArray();
+ queries = JsonSerializer.Deserialize>(contents) ?? new();
+ break;
+ default:
+ queries = new();
+ break;
+ }
+ if (queries.ContainsKey("timestamp"))
+ {
+ queries.Remove("timestamp");
+ }
+ string fullPath = httpContext.Request.PathBase + httpContext.Request.Path;
+ int lastOfSlash = fullPath.LastIndexOf("/");
+ int firstOfQuote = fullPath.IndexOf("?");
+ string methodName =
+ firstOfQuote > lastOfSlash ?
+ fullPath.Substring(lastOfSlash + 1, firstOfQuote - lastOfSlash) :
+ fullPath.Substring(lastOfSlash + 1);
+ return new(methodName,
+ queries,
+ null,
+ this.unixTimestampGenerator.GetUnixTimestamp()
+ );
+ }
+
+ ///
+ public InvokeResult GenerateDummyResult(HttpContext httpContext) => this.GenerateDummyResultAsync(httpContext).Result;
+ }
+}
diff --git a/E5Renewer/Controllers/UnspecifiedController.cs b/E5Renewer/Controllers/UnspecifiedController.cs
new file mode 100644
index 0000000..12777db
--- /dev/null
+++ b/E5Renewer/Controllers/UnspecifiedController.cs
@@ -0,0 +1,31 @@
+using System.Net;
+
+using Microsoft.AspNetCore.Mvc;
+
+namespace E5Renewer.Controllers
+{
+ /// All unspecified requests controller.
+ [ApiController]
+ public class UnspecifiedController : ControllerBase
+ {
+ private readonly IDummyResultGenerator dummyResultGenerator;
+ private readonly ILogger logger;
+
+ /// Initialize with parameters given.
+ /// The logger to generate log.
+ /// The implementation.
+ /// All parameters should be injected by Asp.Net Core.
+ public UnspecifiedController(ILogger logger, IDummyResultGenerator dummyResultGenerator) =>
+ (this.logger, this.dummyResultGenerator) = (logger, dummyResultGenerator);
+
+ /// Handle all unspecified requests.
+ [Route("{*method}")]
+ public async ValueTask Handle()
+ {
+ this.logger.LogWarning("Someone called a unspecified path {0}.", this.HttpContext.Request.Path);
+ this.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
+ return await this.dummyResultGenerator.GenerateDummyResultAsync(this.HttpContext);
+ }
+
+ }
+}
diff --git a/E5Renewer/E5Renewer.csproj b/E5Renewer/E5Renewer.csproj
index 9444cc7..51df3be 100644
--- a/E5Renewer/E5Renewer.csproj
+++ b/E5Renewer/E5Renewer.csproj
@@ -10,9 +10,10 @@
+
-
+
diff --git a/E5Renewer/ILoggingBuilderExtends.cs b/E5Renewer/ILoggingBuilderExtends.cs
new file mode 100644
index 0000000..10e8b49
--- /dev/null
+++ b/E5Renewer/ILoggingBuilderExtends.cs
@@ -0,0 +1,28 @@
+namespace E5Renewer
+{
+ internal static class ILoggingBuilderExtends
+ {
+ public static ILoggingBuilder AddConsole(this ILoggingBuilder builder, bool systemd, LogLevel level)
+ {
+ const string timeStampFormat = "yyyy-MM-dd HH:mm:ss ";
+
+ builder.ClearProviders();
+ if (systemd)
+ {
+ builder.AddSystemdConsole((config) => config.TimestampFormat = timeStampFormat);
+ }
+ else
+ {
+ builder.AddSimpleConsole(
+ (config) =>
+ {
+ config.SingleLine = true;
+ config.TimestampFormat = timeStampFormat;
+ }
+ );
+ }
+ builder.SetMinimumLevel(level);
+ return builder;
+ }
+ }
+}
diff --git a/E5Renewer/IServiceCollectionExtends.cs b/E5Renewer/IServiceCollectionExtends.cs
new file mode 100644
index 0000000..ecce100
--- /dev/null
+++ b/E5Renewer/IServiceCollectionExtends.cs
@@ -0,0 +1,76 @@
+using System.Reflection;
+
+using E5Renewer.Controllers;
+using E5Renewer.Models.BackgroundServices;
+using E5Renewer.Models.GraphAPIs;
+using E5Renewer.Models.Modules;
+using E5Renewer.Models.Secrets;
+using E5Renewer.Models.Statistics;
+
+namespace E5Renewer
+{
+ internal static class IServiceCollectionExtends
+ {
+ public static IServiceCollection AddDummyResultGenerator(this IServiceCollection services) =>
+ services.AddTransient();
+
+ public static IServiceCollection AddAPIFunctionImplementations(this IServiceCollection services)
+ {
+ IEnumerable apiFunctionsTypes = Assembly.GetExecutingAssembly().GetTypes()
+ .GetNonAbstractClassesAssainableTo();
+ foreach (Type t in apiFunctionsTypes)
+ {
+ services.AddSingleton(typeof(IAPIFunction), t);
+ }
+ return services;
+ }
+
+ public static IServiceCollection AddHostedServices(this IServiceCollection services)
+ {
+ services.AddHostedService();
+ return services;
+ }
+
+ public static IServiceCollection AddSecretProvider(this IServiceCollection services) =>
+ services.AddSingleton();
+
+ public static IServiceCollection AddStatusManager(this IServiceCollection services) =>
+ services.AddSingleton();
+
+ public static IServiceCollection AddTimeStampGenerator(this IServiceCollection services) =>
+ services.AddTransient();
+
+ public static IServiceCollection AddTokenOverride(this IServiceCollection services, string? token, FileInfo? tokenFile) =>
+ services.AddSingleton(_ => new(token, tokenFile));
+
+ public static IServiceCollection AddUserSecretFile(this IServiceCollection services, FileInfo userSecret) =>
+ services.AddKeyedSingleton(nameof(userSecret), userSecret);
+
+ public static IServiceCollection AddModules(this IServiceCollection services, params Assembly[] assemblies)
+ {
+ foreach (Assembly assembly in assemblies)
+ {
+ foreach (Type t in assembly.IterE5RenewerModules())
+ {
+ if (t.IsAssignableTo(typeof(IModulesChecker)))
+ {
+ services.AddSingleton(typeof(IModulesChecker), t);
+ }
+ else if (t.IsAssignableTo(typeof(IUserSecretLoader)))
+ {
+ services.AddSingleton(typeof(IUserSecretLoader), t);
+ }
+ else if (t.IsAssignableTo(typeof(IGraphAPICaller)))
+ {
+ services.AddSingleton(typeof(IGraphAPICaller), t);
+ }
+ else if (t.IsAssignableTo(typeof(IModule)))
+ {
+ services.AddSingleton(typeof(IModule), t);
+ }
+ }
+ }
+ return services;
+ }
+ }
+}
diff --git a/E5Renewer/Models/BackgroundServices/ModulesCheckerService.cs b/E5Renewer/Models/BackgroundServices/ModulesCheckerService.cs
deleted file mode 100644
index 9a41336..0000000
--- a/E5Renewer/Models/BackgroundServices/ModulesCheckerService.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using E5Renewer.Models.GraphAPIs;
-using E5Renewer.Models.Modules;
-
-namespace E5Renewer.Models.BackgroundServices
-{
- /// class to check modules passed to AspNet.Core part.
- public class ModulesCheckerService : BackgroundService
- {
- private readonly IEnumerable graphAPICallers;
- private readonly IEnumerable modulesCheckers;
- private readonly IEnumerable aspNetModules;
-
- /// Initialize ModulesCheckerService with parameters given.
- /// All GraphAPICallers modules.
- /// All ModulesChecker modules.
- /// All modules do not belongs to apicaller or modules checker.
- /// All the parameters should be injected by Asp.Net Core.
- public ModulesCheckerService(IEnumerable graphAPICallers, IEnumerable modulesCheckers, IEnumerable aspNetModules)
- {
- this.graphAPICallers = graphAPICallers;
- this.modulesCheckers = modulesCheckers;
- this.aspNetModules = aspNetModules;
- }
-
- ///
- protected override Task ExecuteAsync(CancellationToken token)
- {
- List modules = new List();
- modules.AddRange(this.graphAPICallers);
- modules.AddRange(this.aspNetModules);
- foreach (IModule module in modules)
- {
- foreach (IModulesChecker checker in this.modulesCheckers)
- {
- checker.CheckModules(module);
- }
- }
- return Task.CompletedTask;
- }
- }
-}
diff --git a/E5Renewer/Models/BackgroundServices/PrepareUsersService.cs b/E5Renewer/Models/BackgroundServices/PrepareUsersService.cs
index 66a1e7e..56d5ff8 100644
--- a/E5Renewer/Models/BackgroundServices/PrepareUsersService.cs
+++ b/E5Renewer/Models/BackgroundServices/PrepareUsersService.cs
@@ -1,5 +1,5 @@
-using E5Renewer.Models.Config;
using E5Renewer.Models.GraphAPIs;
+using E5Renewer.Models.Secrets;
using E5Renewer.Models.Statistics;
namespace E5Renewer.Models.BackgroundServices
@@ -11,60 +11,64 @@ namespace E5Renewer.Models.BackgroundServices
public class PrepareUsersService : BackgroundService
{
private readonly ILogger logger;
- private readonly IEnumerable users;
- private readonly IServiceProvider serviceProvider;
+ private readonly ISecretProvider secretProvider;
private readonly IStatusManager statusManager;
+ private readonly IEnumerable apiCallers;
+ private readonly Dictionary callerCache = new();
/// Initialize PrepareUsersService with parameters.
/// The logger to create log.
- /// The GraphUsers to process.
- /// The IServiceProvider implementation.
- /// The IStatusManager implementation.
+ /// The implicit.
+ /// The implementation.
+ /// A series of implementations.
/// All parameters should be injected by AspNet.Core.
public PrepareUsersService(
ILogger logger,
- IEnumerable users,
- IServiceProvider serviceProvider,
- IStatusManager statusManager
+ ISecretProvider secretProvider,
+ IStatusManager statusManager,
+ IEnumerable apiCallers
)
{
this.logger = logger;
- this.users = users;
- this.serviceProvider = serviceProvider;
+ this.secretProvider = secretProvider;
this.statusManager = statusManager;
+ this.apiCallers = apiCallers;
}
///
protected override async Task ExecuteAsync(CancellationToken token)
{
- foreach (GraphUser user in this.users)
+ IEnumerable users =
+ (await this.secretProvider.GetUserSecretAsync()).users;
+ // TODO: Parallel?
+ foreach (User user in users)
{
- await this.DoAPICallForUser(token, user, this.statusManager);
- }
-
- }
-
- private async Task DoAPICallForUser(CancellationToken token, GraphUser user, IStatusManager statusManager)
- {
- IGraphAPICaller apiCaller = this.serviceProvider.GetRequiredKeyedService(user);
- while (!token.IsCancellationRequested)
- {
- await statusManager.SetUserStatusAsync(user.name, user.enabled);
- if (user.enabled)
- {
- await apiCaller.CallNextAPIAsync(user);
- await apiCaller.CalmDownAsync(token, user);
- }
- else
+ while (!token.IsCancellationRequested && user.valid)
{
- TimeSpan delay = user.timeToStart;
- this.logger.LogDebug(
- "Sleeping for {0} day(s), {1} hour(s), {2} miniute(s), {3} second(s) and {4} millisecond(s) to wait starting user {5}...",
- delay.Days, delay.Hours, delay.Minutes, delay.Seconds, delay.Milliseconds, user.name
- );
- await Task.Delay(delay, token);
+ bool enabled = user.timeToStart == TimeSpan.Zero;
+ await this.statusManager.SetUserStatusAsync(user.name, enabled);
+ if (enabled)
+ {
+ if (!this.callerCache.ContainsKey(user))
+ {
+ Random random = new();
+ this.callerCache[user] = random.GetItems(this.apiCallers.ToArray(), 1)[0];
+ }
+ await this.callerCache[user].CallNextAPIAsync(user);
+ await this.callerCache[user].CalmDownAsync(token, user);
+ }
+ else
+ {
+ TimeSpan delay = user.timeToStart;
+ this.logger.LogDebug(
+ "Sleeping for {0} day(s), {1} hour(s), {2} miniute(s), {3} second(s) and {4} millisecond(s) to wait starting user {5}...",
+ delay.Days, delay.Hours, delay.Minutes, delay.Seconds, delay.Milliseconds, user.name
+ );
+ await Task.Delay(delay, token);
+ }
}
}
+
}
}
}
diff --git a/E5Renewer/Models/Config/Config.cs b/E5Renewer/Models/Config/Config.cs
deleted file mode 100644
index bafce6c..0000000
--- a/E5Renewer/Models/Config/Config.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-namespace E5Renewer.Models.Config
-{
- /// class to store program config.
- /// For compatibility to Python version.
- public sealed record class Config : ICheckable
- {
- /// Authentication token.
- public string authToken { get; set; }
-
- /// HTTP json api listen address.
- public string listenAddr { get; set; }
-
- /// HTTP json api listen port.
- public uint listenPort { get; set; }
-
- ///
- /// Use Unix Domain Socket instead TCP for HTTP json api.
- ///
- ///
- /// Only available when HTTP listen failed and your plaform supports Unix Domain Socket.
- ///
- public string listenSocket { get; set; }
-
- ///
- /// The permission of Unix Domain Socket.
- ///
- ///
- /// In octal number like 777 or 644.
- ///
- public uint listenSocketPermission { get; set; }
-
- /// The list of
- /// GraphUsers
- /// to access msgraph apis.
- public List users { get; set; }
-
- /// The map of passwords for certificate.
- public Dictionary? passwords { get; set; }
-
- ///
- public bool isCheckPassed
- {
- get
- {
- return !string.IsNullOrEmpty(this.authToken) &&
- users.All(
- (user) => user.isCheckPassed
- );
- }
- }
-
- /// Initialize Config with default values.
- public Config()
- {
- this.authToken = "";
- this.listenAddr = "127.0.0.1";
- this.listenPort = 8888;
- this.listenSocket = "/run/e5renewer/e5renewer.socket";
- this.listenSocketPermission = Convert.ToUInt32("666", 8);
- this.users = new();
- }
- }
-}
diff --git a/E5Renewer/Models/Config/ConfigCertificatePasswordProvider.cs b/E5Renewer/Models/Config/ConfigCertificatePasswordProvider.cs
deleted file mode 100644
index 8b8d5f8..0000000
--- a/E5Renewer/Models/Config/ConfigCertificatePasswordProvider.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System.Security.Cryptography;
-
-namespace E5Renewer.Models.Config
-{
- /// Get password for a certificate from config.
- public class ConfigCertificatePasswordProvider : ICertificatePasswordProvider
- {
- private readonly ILogger logger;
- private readonly Dictionary? passwords;
- private readonly Dictionary cache = new();
-
- /// Initialize ConfigCertificatePasswordProvider with parameters given.
- /// The logger to create log.
- /// The passwords of certificates. Defaults to null
- /// All parameters should be injected by Asp.Net Core.
- public ConfigCertificatePasswordProvider(
- ILogger logger,
- Dictionary? passwords = null
- )
- {
- this.passwords = passwords;
- this.logger = logger;
- }
-
- ///
- public async Task GetPasswordForCertificateAsync(string certificate)
- {
- if (!this.cache.ContainsKey(certificate))
- {
- if (File.Exists(certificate))
- {
- byte[] sha512Hash;
- using (Stream stream = File.OpenRead(certificate))
- {
- sha512Hash = await SHA512.HashDataAsync(stream);
- }
- string fileHash = BitConverter.ToString(sha512Hash).Replace("-", "").ToLower();
- this.logger.LogDebug("File hash for {0} is {1}", certificate, fileHash);
- this.cache[certificate] =
- this.passwords is Dictionary passwordsDict && passwordsDict.ContainsKey(fileHash)
- ? passwordsDict[fileHash]
- : null
- ;
- }
- }
- return this.cache[certificate];
- }
- }
-}
diff --git a/E5Renewer/Models/Config/GraphUser.cs b/E5Renewer/Models/Config/GraphUser.cs
deleted file mode 100644
index 4f901b8..0000000
--- a/E5Renewer/Models/Config/GraphUser.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-namespace E5Renewer.Models.Config
-{
- /// class to store info to access msgraph apis.
- /// For compatibility to Python version.
- public sealed record class GraphUser : ICheckable
- {
- /// The name of user.
- public string name { get; set; }
-
- /// The tenant id of user.
- public string tenantId { get; set; }
-
- /// The client id of user.
- public string clientId { get; set; }
-
- /// The secret of user.
- public string secret { get; set; }
-
- /// The path to certificate of user.
- public string certificate { get; set; }
-
- /// When to start the user.
- public TimeOnly fromTime { get; set; }
-
- /// When to stop the user.
- public TimeOnly toTime { get; set; }
-
- /// Which days are allowed to start the user.
- public List days { get; set; }
-
- /// When to start the user.
- public bool isCheckPassed
- {
- get
- {
- string[] items = [this.name, this.tenantId, this.clientId];
- return items.All((item) => !string.IsNullOrEmpty(item)) && this.IsCertificateOrSecretValid();
- }
- }
-
- /// If the user is allowed to start.
- public bool enabled
- {
- get
- {
- DateTime now = DateTime.Now;
- DateOnly today = DateOnly.FromDateTime(now);
- DateTime fromTime = new(today, this.fromTime);
- DateTime toTime = new(today, this.toTime);
- while (fromTime >= toTime)
- {
- toTime = toTime.AddDays(1);
- }
- return now >= fromTime &&
- now < toTime &&
- this.days.Contains(now.DayOfWeek);
- }
- }
-
- /// How long to start the user.
- public TimeSpan timeToStart
- {
- get
- {
- if (this.enabled)
- {
- return new();
- }
- DateTime now = DateTime.Now;
- DateOnly today = DateOnly.FromDateTime(now);
- DateTime fromTime = new(today, this.fromTime);
- DateTime toTime = new(today, this.toTime);
- while (fromTime >= toTime)
- {
- toTime = toTime.AddDays(1);
- }
- if (now < fromTime)
- {
- return fromTime - now;
- }
- if (now >= toTime)
- {
- return this.GetSuitableDateTime(
- now,
- (item) => now < item,
- (date) =>
- new(
- DateOnly.FromDateTime(date).AddDays(1),
- this.fromTime
- )
- ) - now;
- }
- // this.days do not contains today
- return this.GetSuitableDateTime(
- now,
- (item) => this.days.Contains(item.DayOfWeek),
- (date) =>
- new(
- DateOnly.FromDateTime(date).AddDays(1),
- this.fromTime
- )
- ) - now;
- }
- }
-
- ///
- /// Initialize GraphUser with default values.
- ///
- public GraphUser()
- {
- this.name = "";
- this.tenantId = "";
- this.clientId = "";
- this.secret = "";
- this.certificate = "";
- this.fromTime = new(8, 0);
- this.toTime = new(16, 0);
- this.days = new()
- {
- DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday,
- DayOfWeek.Thursday, DayOfWeek.Friday
- };
- }
-
- private DateTime GetSuitableDateTime(DateTime start, Predicate passCondition, Func genNextItem)
- {
- while (!passCondition(start))
- {
- start = genNextItem(start);
- }
- return start;
- }
-
- private bool IsCertificateOrSecretValid()
- {
- return File.Exists(this.certificate) || !string.IsNullOrEmpty(this.secret);
- }
- }
-}
diff --git a/E5Renewer/Models/Config/ICertificatePasswordProvider.cs b/E5Renewer/Models/Config/ICertificatePasswordProvider.cs
deleted file mode 100644
index e088ba8..0000000
--- a/E5Renewer/Models/Config/ICertificatePasswordProvider.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace E5Renewer.Models.Config
-{
- /// The api interface for getting password for a certificate.
- public interface ICertificatePasswordProvider
- {
- /// Get password for a certificate.
- /// The password for the certificate given. null
if no password.
- public Task GetPasswordForCertificateAsync(string certificate);
- }
-}
diff --git a/E5Renewer/Models/Config/ICheckable.cs b/E5Renewer/Models/Config/ICheckable.cs
deleted file mode 100644
index 1f32c29..0000000
--- a/E5Renewer/Models/Config/ICheckable.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace E5Renewer.Models.Config
-{
- /// The api interface for checkable object.
- public interface ICheckable
- {
- /// If the object is checked.
- public bool isCheckPassed { get; }
- }
-}
diff --git a/E5Renewer/Models/Config/IConfigParser.cs b/E5Renewer/Models/Config/IConfigParser.cs
deleted file mode 100644
index 35981cf..0000000
--- a/E5Renewer/Models/Config/IConfigParser.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using E5Renewer.Models.Modules;
-
-namespace E5Renewer.Models.Config
-{
- /// The api interface for config parser.
- public interface IConfigParser : IModule
- {
- /// If this parser supports path given.
- /// The path to the config.
- public bool IsSupported(string path);
-
- /// If this parser supports path given.
- /// The FileInfo to the config.
- public bool IsSupported(FileInfo fileInfo)
- {
- return IsSupported(fileInfo.FullName);
- }
-
- /// Parse config.
- /// The path to the config.
- /// Parsed result.
- public ValueTask ParseConfigAsync(string path);
-
- /// Parse config.
- /// The FileInfo to the config.
- /// Parsed result.
- public async ValueTask ParseConfigAsync(FileInfo fileInfo)
- {
- return await this.ParseConfigAsync(fileInfo.FullName);
- }
- }
-}
diff --git a/E5Renewer/Models/Config/JsonConfigParser.cs b/E5Renewer/Models/Config/JsonConfigParser.cs
deleted file mode 100644
index 4d1a11d..0000000
--- a/E5Renewer/Models/Config/JsonConfigParser.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Text.Json;
-
-using E5Renewer.Models.Modules;
-
-namespace E5Renewer.Models.Config
-{
- /// class for parsing json config.
- [Module]
- public class JsonConfigParser : IConfigParser
- {
- ///
- public string name { get => nameof(JsonConfigParser); }
-
- ///
- public string author { get => "E5Renewer"; }
-
- ///
- public SemanticVersioning.Version apiVersion
- {
- get => typeof(JsonConfigParser).Assembly.GetName().Version?.ToSemanticVersion() ?? new(0, 1, 0);
- }
-
- ///
- public bool IsSupported(string path) => path.EndsWith(".json");
-
- ///
- public async ValueTask ParseConfigAsync(string path)
- {
- JsonSerializerOptions options = new()
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
- };
- using (FileStream fileStream = File.OpenRead(path))
- {
- return await JsonSerializer.DeserializeAsync(fileStream, options) ?? new();
- }
- }
- }
-}
diff --git a/E5Renewer/Models/GraphAPIs/IGraphAPICaller.cs b/E5Renewer/Models/GraphAPIs/IGraphAPICaller.cs
index 687ac8c..69637ce 100644
--- a/E5Renewer/Models/GraphAPIs/IGraphAPICaller.cs
+++ b/E5Renewer/Models/GraphAPIs/IGraphAPICaller.cs
@@ -1,18 +1,18 @@
-using E5Renewer.Models.Config;
using E5Renewer.Models.Modules;
+using E5Renewer.Models.Secrets;
namespace E5Renewer.Models.GraphAPIs
{
/// The api interface of msgraph apis callers.
- public interface IGraphAPICaller : IAspNetModule
+ public interface IGraphAPICaller : IModule
{
/// Call next api for user.
/// The user to call api.
- public Task CallNextAPIAsync(GraphUser user);
+ public Task CallNextAPIAsync(User user);
/// Wait for some time after one msgraph api is called.
/// The token to cancel calming down.
/// The user to calm down.
- public Task CalmDownAsync(CancellationToken token, GraphUser user);
+ public Task CalmDownAsync(CancellationToken token, User user);
}
}
diff --git a/E5Renewer/Models/GraphAPIs/RandomGraphAPICaller.cs b/E5Renewer/Models/GraphAPIs/RandomGraphAPICaller.cs
index fbecbd2..f742357 100644
--- a/E5Renewer/Models/GraphAPIs/RandomGraphAPICaller.cs
+++ b/E5Renewer/Models/GraphAPIs/RandomGraphAPICaller.cs
@@ -3,8 +3,8 @@
using Azure.Core;
using Azure.Identity;
-using E5Renewer.Models.Config;
using E5Renewer.Models.Modules;
+using E5Renewer.Models.Secrets;
using E5Renewer.Models.Statistics;
using Microsoft.Graph;
@@ -21,26 +21,26 @@ public class RandomGraphAPICaller : IGraphAPICaller
private readonly ILogger logger;
private readonly IEnumerable apiFunctions;
private readonly IStatusManager statusManager;
- private readonly IEnumerable certificatePasswordProviders;
- private readonly Dictionary clients = new();
+ private readonly ISecretProvider secretProvider;
+ private readonly Dictionary clients = new();
/// Initialize RandomGraphAPICaller with parameters given.
/// The logger to generate log.
/// All known api functions with their id.
- /// IStatusManager implementation.
- /// ICertificatePasswordProvider implementations.
+ /// implementation.
+ /// implementations.
/// All parameters should be injected by Asp.Net Core.
public RandomGraphAPICaller(
ILogger logger,
IEnumerable apiFunctions,
IStatusManager statusManager,
- IEnumerable certificatePasswordProviders
+ ISecretProvider secretProvider
)
{
this.logger = logger;
this.apiFunctions = apiFunctions;
this.statusManager = statusManager;
- this.certificatePasswordProviders = certificatePasswordProviders;
+ this.secretProvider = secretProvider;
this.logger.LogDebug("Found {0} api functions", this.apiFunctions.Count());
}
@@ -57,7 +57,7 @@ public SemanticVersioning.Version apiVersion
}
///
- public async Task CallNextAPIAsync(GraphUser user)
+ public async Task CallNextAPIAsync(User user)
{
if (!this.clients.ContainsKey(user))
{
@@ -66,22 +66,31 @@ public async Task CallNextAPIAsync(GraphUser user)
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
TokenCredential credential;
- if (File.Exists(user.certificate))
+ if (user.certificate?.Exists ?? false)
{
this.logger.LogDebug("Using certificate to get user token.");
- string? password = await this.GetPasswordForCertificateAsync(user.certificate);
+ string? password = await this.secretProvider.GetPasswordForCertificateAsync(user.certificate);
if (password is not null)
{
this.logger.LogDebug("Found password for certificate given.");
}
- X509Certificate2 certificate = new(user.certificate, password);
- credential = new ClientCertificateCredential(user.tenantId, user.clientId, certificate, options);
+ using (FileStream fileStream = user.certificate.OpenRead())
+ {
+ byte[] buffer = new byte[user.certificate.Length];
+ int size = await fileStream.ReadAsync(buffer);
+ X509Certificate2 certificate = new(buffer.Take(size).ToArray(), password);
+ credential = new ClientCertificateCredential(user.tenantId, user.clientId, certificate, options);
+ }
}
- else
+ else if (!string.IsNullOrEmpty(user.secret))
{
this.logger.LogDebug("Using secret to get user token.");
credential = new ClientSecretCredential(user.tenantId, user.clientId, user.secret, options);
}
+ else
+ {
+ throw new NullReferenceException($"{nameof(user.certificate)} and {nameof(user.secret)} are both invalid.");
+ }
GraphServiceClient client = new(credential, ["https://graph.microsoft.com/.default"]);
this.clients[user] = client;
}
@@ -107,9 +116,9 @@ int GetFunctionWeightOfCurrentUser(IAPIFunction function)
}
///
- public async Task CalmDownAsync(CancellationToken token, GraphUser user)
+ public async Task CalmDownAsync(CancellationToken token, User user)
{
- if (user.enabled)
+ if (user.timeToStart == TimeSpan.Zero)
{
const int calmDownMinMilliSeconds = 300000;
const int calmDownMaxMilliSeconds = 500000;
@@ -119,18 +128,5 @@ public async Task CalmDownAsync(CancellationToken token, GraphUser user)
await Task.Delay(milliseconds, token);
}
}
-
- private async Task GetPasswordForCertificateAsync(string certificate)
- {
- foreach (ICertificatePasswordProvider provider in this.certificatePasswordProviders)
- {
- string? password = await provider.GetPasswordForCertificateAsync(certificate);
- if (password is not null)
- {
- return password;
- }
- }
- return null;
- }
}
}
diff --git a/E5Renewer/Models/Modules/DeprecatedModulesChecker.cs b/E5Renewer/Models/Modules/DeprecatedModulesChecker.cs
index bd6cc38..9585de3 100644
--- a/E5Renewer/Models/Modules/DeprecatedModulesChecker.cs
+++ b/E5Renewer/Models/Modules/DeprecatedModulesChecker.cs
@@ -7,12 +7,10 @@ public class DeprecatedModulesChecker : IModulesChecker
private readonly ILogger logger;
/// Initialize the DeprecatedModulesChecker instance.
- /// The logger factory to create logger to generate outputs.
+ /// The logger factory to create logger to generate outputs.
/// All the parameters should be injected by Asp.Net Core.
- public DeprecatedModulesChecker(ILoggerFactory loggerFactory)
- {
- this.logger = loggerFactory.CreateLogger();
- }
+ public DeprecatedModulesChecker(ILogger logger) =>
+ this.logger = logger;
///
public string name { get => nameof(DeprecatedModulesChecker); }
diff --git a/E5Renewer/Models/Modules/IAspNetModule.cs b/E5Renewer/Models/Modules/IAspNetModule.cs
deleted file mode 100644
index 64d10aa..0000000
--- a/E5Renewer/Models/Modules/IAspNetModule.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace E5Renewer.Models.Modules
-{
- /// The api interface for modules should be injected into AspNet.
- public interface IAspNetModule : IModule { }
-}
diff --git a/E5Renewer/Models/Modules/IModulesChecker.cs b/E5Renewer/Models/Modules/IModulesChecker.cs
index 9a3aede..1176580 100644
--- a/E5Renewer/Models/Modules/IModulesChecker.cs
+++ b/E5Renewer/Models/Modules/IModulesChecker.cs
@@ -1,7 +1,7 @@
namespace E5Renewer.Models.Modules
{
/// The api interface of checking module.
- public interface IModulesChecker : IAspNetModule
+ public interface IModulesChecker : IModule
{
/// Check the module.
/// The module to check.
diff --git a/E5Renewer/Models/Secrets/ISecretProvider.cs b/E5Renewer/Models/Secrets/ISecretProvider.cs
new file mode 100644
index 0000000..0807c37
--- /dev/null
+++ b/E5Renewer/Models/Secrets/ISecretProvider.cs
@@ -0,0 +1,19 @@
+namespace E5Renewer.Models.Secrets
+{
+ /// The api interface for getting secrets.
+ public interface ISecretProvider
+ {
+ /// Get password for certificate.
+ /// The password of the certificate, null
if not found.
+ public Task GetPasswordForCertificateAsync(FileInfo certificate);
+
+ /// Generate runtime token for authentication.
+ /// The token.
+ public Task GetRuntimeTokenAsync();
+
+ /// Get user secrets from file.
+ /// A series of loaded from file.
+ /// Thorw if no loader can be found to load secret.
+ public Task GetUserSecretAsync();
+ }
+}
diff --git a/E5Renewer/Models/Secrets/IUserSecretLoader.cs b/E5Renewer/Models/Secrets/IUserSecretLoader.cs
new file mode 100644
index 0000000..be0d2b5
--- /dev/null
+++ b/E5Renewer/Models/Secrets/IUserSecretLoader.cs
@@ -0,0 +1,14 @@
+using E5Renewer.Models.Modules;
+
+namespace E5Renewer.Models.Secrets
+{
+ /// The api interface for loading user secret.
+ public interface IUserSecretLoader : IModule
+ {
+ /// If file given is supported by the loader.
+ public bool IsSupported(FileInfo secretFile);
+
+ /// Load a series of from file given.
+ public Task LoadSecretAsync(FileInfo secretFile);
+ }
+}
diff --git a/E5Renewer/Models/Secrets/Json/FileInfoOrNullConverter.cs b/E5Renewer/Models/Secrets/Json/FileInfoOrNullConverter.cs
new file mode 100644
index 0000000..eb26e0a
--- /dev/null
+++ b/E5Renewer/Models/Secrets/Json/FileInfoOrNullConverter.cs
@@ -0,0 +1,20 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace E5Renewer.Models.Secrets.Json
+{
+ /// Custom to convert between and .
+ internal class FileInfoOrNullConverter : JsonConverter
+ {
+ ///
+ public override FileInfo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ string? read = reader.GetString();
+ return read is not null ? new(read) : null;
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, FileInfo? value, JsonSerializerOptions options) => writer.WriteStringValue(value?.FullName);
+
+ }
+}
diff --git a/E5Renewer/Models/Secrets/Json/JsonUserSecretLoader.cs b/E5Renewer/Models/Secrets/Json/JsonUserSecretLoader.cs
new file mode 100644
index 0000000..94d2883
--- /dev/null
+++ b/E5Renewer/Models/Secrets/Json/JsonUserSecretLoader.cs
@@ -0,0 +1,51 @@
+using System.Text.Json;
+
+using E5Renewer.Models.Modules;
+
+namespace E5Renewer.Models.Secrets.Json
+{
+ /// Class for loading user secret from json file.
+ [Module]
+ public class JsonUserSecretLoader : IUserSecretLoader
+ {
+ private readonly Dictionary cache = new();
+
+ ///
+ public string name { get => nameof(JsonUserSecretLoader); }
+
+ ///
+ public string author { get => "E5Renewer"; }
+
+ ///
+ public SemanticVersioning.Version apiVersion
+ {
+ get => typeof(JsonUserSecretLoader).Assembly.GetName().Version?.ToSemanticVersion() ?? new(0, 1, 0);
+ }
+
+ ///
+ public bool IsSupported(FileInfo userSecret)
+ {
+ return userSecret.Name.EndsWith(".json");
+ }
+
+ ///
+ public async Task LoadSecretAsync(FileInfo userSecret)
+ {
+ if (!this.cache.ContainsKey(userSecret))
+ {
+ JsonSerializerOptions options = new()
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+ IncludeFields = true
+ };
+ options.Converters.Add(new FileInfoOrNullConverter());
+ using (FileStream fileStream = userSecret.OpenRead())
+ {
+ this.cache[userSecret] = await JsonSerializer.DeserializeAsync(fileStream, options);
+ }
+ }
+ return this.cache[userSecret];
+
+ }
+ }
+}
diff --git a/E5Renewer/Models/Secrets/SimpleSecretProvider.cs b/E5Renewer/Models/Secrets/SimpleSecretProvider.cs
new file mode 100644
index 0000000..bfd4aaf
--- /dev/null
+++ b/E5Renewer/Models/Secrets/SimpleSecretProvider.cs
@@ -0,0 +1,133 @@
+using System.Collections.Immutable;
+using System.Security.Cryptography;
+
+namespace E5Renewer.Models.Secrets
+{
+ /// Simple implementation of .
+ public class SimpleSecretProvider : ISecretProvider
+ {
+ private readonly FileInfo userSecret;
+ private readonly ILogger logger;
+ private readonly IEnumerable secretLoaders;
+ private readonly TokenOverride? tokenOverride;
+ private readonly string randomGeneratedToken;
+ private readonly Dictionary certificatePasswordsCache = new();
+ private bool notifyRandom = true;
+ private UserSecret? secretCache = null;
+
+ /// Initialize with parameters given.
+ /// The logger to create logs.
+ /// The file contains user secret info.
+ /// The implementation.
+ /// The instance.
+ /// All parameters should be injected by Asp.Net Core.
+ public SimpleSecretProvider(
+ ILogger logger,
+ [FromKeyedServices(nameof(userSecret))] FileInfo userSecret,
+ IEnumerable secretLoaders,
+ TokenOverride? tokenOverride = null
+ )
+ {
+ this.logger = logger;
+ this.userSecret = userSecret;
+ this.secretLoaders = secretLoaders;
+ this.tokenOverride = tokenOverride;
+
+ const int tokenSize = 32;
+ const string tokenSource = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ0123456789";
+ Random random = new();
+ this.randomGeneratedToken = "";
+ for (int i = 0; i < tokenSize; i++)
+ {
+ this.randomGeneratedToken += tokenSource[random.Next(tokenSource.Length)];
+ }
+ }
+
+ ///
+ public async Task GetPasswordForCertificateAsync(FileInfo certificate)
+ {
+ if (this.certificatePasswordsCache.ContainsKey(certificate))
+ {
+ return this.certificatePasswordsCache[certificate];
+ }
+ else if (certificate.Exists)
+ {
+ ImmutableDictionary passwords =
+ (await this.GetUserSecretAsync()).passwords ?? ImmutableDictionary.Create();
+ byte[] sha512Hash;
+ using (Stream stream = certificate.OpenRead())
+ {
+ sha512Hash = await SHA512.HashDataAsync(stream);
+ }
+ string fileHash = BitConverter.ToString(sha512Hash).Replace("-", "").ToLower();
+ this.logger.LogDebug("File hash for {0} is {1}", certificate, fileHash);
+ this.certificatePasswordsCache[certificate] =
+ passwords.ContainsKey(fileHash)
+ ? passwords[fileHash]
+ : null
+ ;
+ return this.certificatePasswordsCache[certificate];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ public async Task GetRuntimeTokenAsync()
+ {
+ string? token = this.tokenOverride?.token;
+ FileInfo? tokenFile = this.tokenOverride?.tokenFile;
+
+ if (token is not null)
+ {
+ return token;
+ }
+
+ if (tokenFile is not null)
+ {
+ using (StreamReader stream = tokenFile.OpenText())
+ {
+ string? line = await stream.ReadLineAsync();
+ if (line is not null)
+ {
+ return line.Trim();
+ }
+ }
+ }
+ if (this.notifyRandom)
+ {
+ this.logger.LogWarning("Using random generated token {0}", this.randomGeneratedToken);
+ this.notifyRandom = false;
+ }
+ return this.randomGeneratedToken;
+ }
+
+ ///
+ public async Task GetUserSecretAsync()
+ {
+ if (this.secretCache is UserSecret secretCache)
+ {
+ return secretCache;
+ }
+ try
+ {
+ IUserSecretLoader loader = this.secretLoaders.First((l) => l.IsSupported(this.userSecret));
+ this.logger.LogDebug("Using {0} to load user secret {1}.", loader.name, this.userSecret);
+ UserSecret secretResult = await loader.LoadSecretAsync(this.userSecret);
+ this.secretCache = secretResult;
+ return secretResult;
+ }
+ catch (InvalidOperationException ioe)
+ {
+ this.logger.LogError("Failed to find {0} for user secret from because {1}.", nameof(IUserSecretLoader), ioe.Message);
+ }
+ catch (Exception e)
+ {
+ this.logger.LogError("Failed to load user secret from because {0}.", e.Message);
+ }
+ throw new InvalidDataException($"Cannot load {this.userSecret}.");
+ }
+ }
+}
diff --git a/E5Renewer/Models/Secrets/TokenOverride.cs b/E5Renewer/Models/Secrets/TokenOverride.cs
new file mode 100644
index 0000000..5a234b2
--- /dev/null
+++ b/E5Renewer/Models/Secrets/TokenOverride.cs
@@ -0,0 +1,19 @@
+namespace E5Renewer.Models.Secrets
+{
+ /// Token overrides random generated one.
+ public class TokenOverride
+ {
+ /// Override in plain text.
+ public readonly string? token;
+
+ /// Override in file content.
+ public readonly FileInfo? tokenFile;
+
+ /// Initialize with arguments given.
+ public TokenOverride(string? token, FileInfo? tokenFile)
+ {
+ this.token = token;
+ this.tokenFile = tokenFile;
+ }
+ }
+}
diff --git a/E5Renewer/Models/Secrets/Toml/TomlDocumentNodeExtends.cs b/E5Renewer/Models/Secrets/Toml/TomlDocumentNodeExtends.cs
new file mode 100644
index 0000000..9bafb96
--- /dev/null
+++ b/E5Renewer/Models/Secrets/Toml/TomlDocumentNodeExtends.cs
@@ -0,0 +1,56 @@
+using System.Collections.Immutable;
+
+using CaseConverter;
+
+using CsToml;
+
+namespace E5Renewer.Models.Secrets.Toml
+{
+ internal static class TomlDocumentNodeExtends
+ {
+ public static UserSecret ToUserSecret(this TomlDocumentNode node)
+ {
+ List users = new();
+ TomlDocumentNode usersNode = node[nameof(users).ToSnakeCase()];
+ for (int i = 0; i < usersNode.GetArray().Count; i++)
+ {
+ users.Add(usersNode[i].ToUser());
+ }
+
+ Dictionary? passwords;
+ bool passwordsDeserialized = node[nameof(passwords).ToSnakeCase()].TryGetValue>(out passwords);
+
+ return new(
+ ImmutableList.CreateRange(users),
+ passwordsDeserialized && passwords is not null ? ImmutableDictionary.CreateRange(passwords) : null
+ );
+ }
+
+ public static User ToUser(this TomlDocumentNode node)
+ {
+ string name, tenantId, clientId;
+ name = node[nameof(name).ToSnakeCase()].GetString();
+ tenantId = node[nameof(tenantId).ToSnakeCase()].GetString();
+ clientId = node[nameof(clientId).ToSnakeCase()].GetString();
+
+ string? secret;
+ secret = node[nameof(secret).ToSnakeCase()].TryGetString(out secret) ? secret : null;
+
+ FileInfo? certificate;
+ certificate = node[nameof(certificate).ToSnakeCase()].TryGetString(out string path) ? new(path) : null;
+
+ TimeOnly? fromTime, toTime;
+ fromTime = node[nameof(fromTime).ToSnakeCase()].TryGetTimeOnly(out TimeOnly resultFromTime) ? resultFromTime : null;
+ toTime = node[nameof(toTime).ToSnakeCase()].TryGetTimeOnly(out TimeOnly resultToTime) ? resultToTime : null;
+
+ List? days;
+ bool daysDeserialized = node[nameof(days).ToSnakeCase()].TryGetValue>(out days);
+
+ return new(
+ name, tenantId, clientId, secret, certificate,
+ fromTime, toTime,
+ daysDeserialized && days is not null ? ImmutableList.CreateRange(days) : null
+ );
+ }
+ }
+}
diff --git a/E5Renewer/Models/Secrets/Toml/TomlUserSecretLoader.cs b/E5Renewer/Models/Secrets/Toml/TomlUserSecretLoader.cs
new file mode 100644
index 0000000..2d917f6
--- /dev/null
+++ b/E5Renewer/Models/Secrets/Toml/TomlUserSecretLoader.cs
@@ -0,0 +1,40 @@
+using CsToml;
+
+using E5Renewer.Models.Modules;
+
+namespace E5Renewer.Models.Secrets.Toml
+{
+ /// Class for loading user secret from toml file.
+ [Module]
+ public class TomlUserSecretLoader : IUserSecretLoader
+ {
+ ///
+ public string name { get => nameof(TomlUserSecretLoader); }
+
+ ///
+ public string author { get => "E5Renewer"; }
+
+ ///
+ public SemanticVersioning.Version apiVersion
+ {
+ get => typeof(TomlUserSecretLoader).Assembly.GetName().Version?.ToSemanticVersion() ?? new(0, 1, 0);
+ }
+
+ ///
+ public bool IsSupported(FileInfo userSecret) => userSecret.Name.EndsWith(".toml");
+
+ ///
+ public async Task LoadSecretAsync(FileInfo userSecret)
+ {
+ TomlDocument document;
+ using (FileStream fileStream = userSecret.OpenRead())
+ {
+ byte[] buffer = new byte[userSecret.Length];
+ int length = await fileStream.ReadAsync(buffer);
+ document = CsTomlSerializer.Deserialize(buffer.Take(length).ToArray());
+ }
+
+ return document.RootNode.ToUserSecret();
+ }
+ }
+}
diff --git a/E5Renewer/Models/Secrets/UserSecret.cs b/E5Renewer/Models/Secrets/UserSecret.cs
new file mode 100644
index 0000000..26b7c85
--- /dev/null
+++ b/E5Renewer/Models/Secrets/UserSecret.cs
@@ -0,0 +1,119 @@
+using System.Collections.Immutable;
+using System.Text.Json.Serialization;
+
+namespace E5Renewer.Models.Secrets
+{
+ /// Readonly struct to store user secret and certificate passwords.
+ public readonly struct UserSecret
+ {
+ /// Users in the secret.
+ public ImmutableList users { get; }
+
+ /// Passwords for user certificates in the secret.
+ public ImmutableDictionary? passwords { get; }
+
+ /// If this is correct to be used.
+ [JsonIgnore]
+ public bool valid { get => this.users.All((user) => user.valid); }
+
+ /// Initialize instance with arguments given.
+ [JsonConstructor]
+ public UserSecret(ImmutableList users, ImmutableDictionary? passwords) =>
+ (this.users, this.passwords) = (users, passwords);
+ }
+
+ /// Readonly struct to store user secret.
+ public readonly struct User
+ {
+ /// The name of user.
+ public string name { get; }
+
+ /// The tenant id of user.
+ public string tenantId { get; }
+
+ /// The client id of user.
+ public string clientId { get; }
+
+ /// The secret of user.
+ public string? secret { get; }
+
+ /// The path to certificate of user.
+ public FileInfo? certificate { get; }
+
+ /// When to start the user.
+ [JsonInclude]
+ private TimeOnly? fromTime { get; }
+
+ /// When to stop the user.
+ [JsonInclude]
+ private TimeOnly? toTime { get; }
+
+ /// Which days are allowed to start the user.
+ [JsonInclude]
+ private ImmutableList? days { get; }
+
+ /// If this secret is valid to be used.
+ [JsonIgnore]
+ public bool valid
+ {
+ get
+ {
+ string[] nonEmptyItems = [this.name, this.tenantId, this.clientId];
+ return nonEmptyItems.All((i) => !string.IsNullOrEmpty(i)) &&
+ (this.certificate?.Exists ?? false || !string.IsNullOrEmpty(this.secret));
+ }
+ }
+
+ /// How long to start the user.
+ [JsonIgnore]
+ public TimeSpan timeToStart
+ {
+ get
+ {
+ TimeOnly fromTime = this.fromTime ?? new(8, 0);
+ TimeOnly toTime = this.toTime ?? new(16, 0);
+ ImmutableList days = this.days ?? ImmutableList.Create(
+ DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday);
+
+ DateTime now = DateTime.Now;
+ DateOnly today = DateOnly.FromDateTime(now);
+ DateTime fromDateTime = new(today, fromTime);
+ DateTime toDateTime = new(today, toTime);
+ while (fromDateTime >= toDateTime)
+ {
+ toDateTime = toDateTime.AddDays(1);
+ }
+ if (now <= fromDateTime)
+ {
+ DateTime nextFromTime = fromDateTime;
+ while (!days.Contains(nextFromTime.DayOfWeek))
+ {
+ nextFromTime = nextFromTime.AddDays(1);
+ }
+ return nextFromTime - now;
+ }
+ else
+ {
+ DateTime nextFromDateTime = now;
+ DateTime nextToDateTime = toDateTime;
+ while (!days.Contains(nextFromDateTime.DayOfWeek) || nextFromDateTime >= nextToDateTime)
+ {
+ DateOnly startDate = DateOnly.FromDateTime(nextFromDateTime).AddDays(1);
+ nextFromDateTime = new(startDate, fromTime);
+ nextToDateTime = nextToDateTime.AddDays(1);
+ }
+ return nextFromDateTime - now;
+ }
+ }
+ }
+
+ /// Initialize instance with arguments given.
+ [JsonConstructor]
+ public User(
+ string name, string tenantId, string clientId, string? secret, FileInfo? certificate,
+ TimeOnly? fromTime, TimeOnly? toTime, ImmutableList? days
+ ) => (this.name, this.tenantId, this.clientId, this.secret, this.certificate, this.fromTime, this.toTime, this.days) =
+ (name, tenantId, clientId, secret, certificate, fromTime, toTime, days);
+
+ }
+}
diff --git a/E5Renewer/Models/Secrets/Yaml/YamlSerializeCompatibleUser.cs b/E5Renewer/Models/Secrets/Yaml/YamlSerializeCompatibleUser.cs
new file mode 100644
index 0000000..b0cc52d
--- /dev/null
+++ b/E5Renewer/Models/Secrets/Yaml/YamlSerializeCompatibleUser.cs
@@ -0,0 +1,50 @@
+using System.Collections.Immutable;
+
+using VYaml.Annotations;
+
+namespace E5Renewer.Models.Secrets.Yaml
+{
+ [YamlObject(NamingConvention.SnakeCase)]
+ internal partial class YamlSerializeCompatibleUser
+ {
+ [YamlIgnore]
+ public User value
+ {
+ get
+ {
+ FileInfo? certificate = this.certificate is not null ? new(this.certificate) : null;
+ ImmutableList? days =
+ this.days is not null
+ ? ImmutableList.CreateRange(this.days.Select((day) => (DayOfWeek)day))
+ : null;
+ return new(
+ this.name, this.tenantId, this.clientId, this.secret, certificate,
+ this.fromTime, this.toTime, days
+ );
+ }
+ }
+
+ public string name { get; }
+
+ public string tenantId { get; }
+
+ public string clientId { get; }
+
+ public string? secret { get; }
+
+ public string? certificate { get; }
+
+ public TimeOnly? fromTime { get; }
+
+ public TimeOnly? toTime { get; }
+
+ public List? days { get; }
+
+ [YamlConstructor]
+ public YamlSerializeCompatibleUser(
+ string name, string tenantId, string clientId,
+ string? secret, string? certificate, TimeOnly? fromTime, TimeOnly? toTime, List? days
+ ) => (this.name, this.tenantId, this.clientId, this.secret, this.certificate, this.fromTime, this.toTime, this.days) =
+ (name, tenantId, clientId, secret, certificate, fromTime, toTime, days);
+ }
+}
diff --git a/E5Renewer/Models/Secrets/Yaml/YamlSerializeCompatibleUserSecret.cs b/E5Renewer/Models/Secrets/Yaml/YamlSerializeCompatibleUserSecret.cs
new file mode 100644
index 0000000..e497651
--- /dev/null
+++ b/E5Renewer/Models/Secrets/Yaml/YamlSerializeCompatibleUserSecret.cs
@@ -0,0 +1,27 @@
+using System.Collections.Immutable;
+
+using VYaml.Annotations;
+
+namespace E5Renewer.Models.Secrets.Yaml
+{
+ [YamlObject(NamingConvention.SnakeCase)]
+ internal partial class YamlSerializeCompatibleUserSecret
+ {
+ [YamlIgnore]
+ public UserSecret value
+ {
+ get => new(
+ ImmutableList.CreateRange(users.Select((user) => user.value)),
+ passwords is not null ? ImmutableDictionary.CreateRange(passwords) : null
+ );
+ }
+
+ public List users { get; }
+
+ public Dictionary? passwords { get; }
+
+ [YamlConstructor]
+ public YamlSerializeCompatibleUserSecret(List users, Dictionary? passwords) =>
+ (this.users, this.passwords) = (users, passwords);
+ }
+}
diff --git a/E5Renewer/Models/Secrets/Yaml/YamlUserSecretLoader.cs b/E5Renewer/Models/Secrets/Yaml/YamlUserSecretLoader.cs
new file mode 100644
index 0000000..93c2a28
--- /dev/null
+++ b/E5Renewer/Models/Secrets/Yaml/YamlUserSecretLoader.cs
@@ -0,0 +1,38 @@
+using E5Renewer.Models.Modules;
+
+using VYaml.Serialization;
+
+namespace E5Renewer.Models.Secrets.Yaml
+{
+ /// Class for loading user secret from yaml file.
+ [Module]
+ public class YamlUserSecretLoader : IUserSecretLoader
+ {
+ ///
+ public string name { get => nameof(YamlUserSecretLoader); }
+
+ ///
+ public string author { get => "E5Renewer"; }
+
+ ///
+ public SemanticVersioning.Version apiVersion
+ {
+ get => typeof(YamlUserSecretLoader).Assembly.GetName().Version?.ToSemanticVersion() ?? new(0, 1, 0);
+ }
+
+ ///
+ public bool IsSupported(FileInfo userSecret) => userSecret.Name.EndsWith(".yml") || userSecret.Name.EndsWith(".yaml");
+
+ ///
+ public async Task LoadSecretAsync(FileInfo userSecret)
+ {
+ YamlSerializeCompatibleUserSecret userSecretObject;
+ using (FileStream fileStream = userSecret.OpenRead())
+ {
+ userSecretObject = await YamlSerializer.DeserializeAsync(fileStream);
+ }
+ return userSecretObject.value;
+ }
+
+ }
+}
diff --git a/E5Renewer/Models/UintExtends.cs b/E5Renewer/Models/UintExtends.cs
deleted file mode 100644
index 0d5f714..0000000
--- a/E5Renewer/Models/UintExtends.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace E5Renewer.Models
-{
- /// Extended methods to
- /// uint.
- ///
- public static class UintExtends
- {
- private const uint minPermission = 0; // 0o000
- private const uint maxPermission = 511; // 0o777
-
- /// Convert
- /// uint
- /// to
- /// UnixFileMode.
- ///
- public static UnixFileMode ToUnixFileMode(this uint permission)
- {
-
- if (permission < UintExtends.minPermission)
- {
- permission = UintExtends.minPermission;
- }
-
- if (permission > UintExtends.maxPermission)
- {
- permission = UintExtends.maxPermission;
- }
-
- return (UnixFileMode)permission;
- }
- }
-}
diff --git a/E5Renewer/Program.cs b/E5Renewer/Program.cs
index 922dd61..063ce85 100644
--- a/E5Renewer/Program.cs
+++ b/E5Renewer/Program.cs
@@ -1,408 +1,177 @@
using System.Net;
-using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Text.Json;
using E5Renewer;
using E5Renewer.Controllers;
-using E5Renewer.Models;
-using E5Renewer.Models.BackgroundServices;
-using E5Renewer.Models.Config;
-using E5Renewer.Models.GraphAPIs;
using E5Renewer.Models.Modules;
-using E5Renewer.Models.Statistics;
using Microsoft.AspNetCore.Mvc;
-///
-public static class Program
+const UnixFileMode defaultListenUnixSocketPermission =
+ UnixFileMode.UserRead | UnixFileMode.UserWrite |
+ UnixFileMode.GroupRead | UnixFileMode.GroupWrite |
+ UnixFileMode.OtherRead | UnixFileMode.OtherWrite;
+bool setSocketPermission = false;
+
+// Variables from Configuration
+bool systemd;
+IPEndPoint? listenTcpSocket;
+string? listenUnixSocketPath;
+UnixFileMode listenUnixSocketPermission;
+FileInfo? userSecret;
+FileInfo? tokenFile;
+string? token;
+
+Dictionary commandLineSwitchMap = new()
{
- private const string timeStampFormat = "yyyy-MM-dd HH:mm:ss ";
- private static readonly List modulesCheckers = new();
- private static readonly List configParsers = new();
- private static readonly List aspNetModules = new();
- private static ILogger logger = null!;
-
- /// Start E5Renewer.
- /// The path to config file.
- /// If program runs in systemd environment.
- public static async Task Main(FileInfo config, bool systemd = false)
- {
- string env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production";
- LogLevel minimumLevel = env == "Debug" ? LogLevel.Debug : LogLevel.Information;
- Program.logger = LoggerFactory.Create(
- (configure) =>
- {
- configure.ClearProviders();
- if (systemd)
- {
- configure.AddSystemdConsole((config) => config.TimestampFormat = Program.timeStampFormat);
- }
- else
- {
- configure.AddSimpleConsole((config) =>
- {
- config.SingleLine = true;
- config.TimestampFormat = Program.timeStampFormat;
- });
- }
- configure.SetMinimumLevel(minimumLevel);
- }
- ).CreateLogger(nameof(Program));
-
- Program.modulesCheckers.Clear();
- Program.configParsers.Clear();
- Program.aspNetModules.Clear();
-
- Program.DiscoverModules(Assembly.GetExecutingAssembly());
- IEnumerable assemblies = GetPossibleModulesPaths().
- Select(
- (directory) =>
- {
- FileInfo[] files = directory.GetFiles(directory.Name + ".dll", SearchOption.TopDirectoryOnly);
- if (files.Count() > 0)
- {
- FileInfo file = files[0];
- ModuleLoadContext context = new(file);
- string assemblyName = Path.GetFileNameWithoutExtension(file.FullName);
- try
- {
- Assembly assembly = context.LoadFromAssemblyName(
- new(assemblyName)
- );
- Program.logger.LogDebug("Load assembly from {0} success.", assemblyName);
- return assembly;
- }
- catch (Exception e)
- {
- Program.logger.LogError("Failed to load assembly because {0}.", e.Message);
- }
- }
- return null;
- }
- ).OfType();
- Program.DiscoverModules(assemblies.ToArray());
-
- Program.CheckModules();
-
- await Program.LaunchServerAsync(await Program.ParseConfigAsync(config), systemd);
- }
-
- private static void CheckModules()
+ {"--systemd", nameof(systemd)},
+ {"--user-secret", nameof(userSecret)},
+ {"--token", nameof(token)},
+ {"--token-file", nameof(tokenFile)},
+ {"--listen-tcp-socket", nameof(listenTcpSocket)},
+ {"--listen-unix-socket-path", nameof(listenUnixSocketPath)},
+ {"--listen-unix-socket-permission", nameof(listenUnixSocketPermission)}
+};
+
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+builder.Configuration.AddCommandLine(args, commandLineSwitchMap);
+
+systemd = args.ContainsFlag(nameof(systemd)) || builder.Configuration.GetValue(nameof(systemd));
+listenTcpSocket = builder.Configuration.GetValue(nameof(listenTcpSocket))?.AsIPEndPoint();
+listenUnixSocketPath = builder.Configuration.GetValue(nameof(listenUnixSocketPath));
+listenUnixSocketPermission = builder.Configuration.GetValue(
+ nameof(listenUnixSocketPermission), defaultListenUnixSocketPermission);
+userSecret = builder.Configuration.GetValue(nameof(userSecret))?.AsFileInfo();
+tokenFile = builder.Configuration.GetValue(nameof(tokenFile))?.AsFileInfo();
+token = builder.Configuration.GetValue(nameof(token));
+
+builder.Logging.AddConsole(systemd, builder.Environment.IsDevelopment() ? LogLevel.Debug : LogLevel.Information);
+
+builder.WebHost.ConfigureKestrel(
+(kestrelServerOptions) =>
{
- foreach (IConfigParser parser in Program.configParsers)
+ if (listenTcpSocket is not null)
{
- foreach (IModulesChecker checker in Program.modulesCheckers)
- {
- Program.logger.LogDebug("Checking module {0} with checker {1}...", parser.name, checker.name);
- checker.CheckModules(parser);
- }
+ kestrelServerOptions.Listen(listenTcpSocket);
}
- }
- private static async ValueTask ParseConfigAsync(FileInfo config)
- {
- Config runtimeConfig;
- try
- {
- IConfigParser parser = Program.configParsers.First((parser) => parser.IsSupported(config));
- runtimeConfig = await parser.ParseConfigAsync(config);
- }
- catch (InvalidOperationException)
- {
- Program.logger.LogError("Failed to find parser for config {0}.", config);
- throw;
- }
- catch (Exception e)
+ if (listenUnixSocketPath is not null && Socket.OSSupportsUnixDomainSockets)
{
- Program.logger.LogError("Failed to parse config {0} because {1}.", config, e.Message);
- throw;
+ kestrelServerOptions.ListenUnixSocket(listenUnixSocketPath);
+ setSocketPermission = true;
}
- return runtimeConfig;
}
+);
- private static async ValueTask LaunchServerAsync(Config config, bool systemd)
+builder.Services.AddModules(Assembly.GetExecutingAssembly());
+IEnumerable assemblies = GetPossibleModulesPaths().
+Select(
+ (directory) =>
{
- if (!config.isCheckPassed)
+ FileInfo[] files = directory.GetFiles(directory.Name + ".dll", SearchOption.TopDirectoryOnly);
+ if (files.Count() > 0)
{
- throw new InvalidDataException("config check failed.");
- }
- bool setSocketPermission = false;
- WebApplicationBuilder builder = WebApplication.CreateBuilder();
- builder.Logging.ClearProviders();
- if (systemd)
- {
- builder.Logging.AddSystemdConsole((config) => config.TimestampFormat = Program.timeStampFormat);
- }
- else
- {
- builder.Logging.AddSimpleConsole(
- (config) =>
- {
- config.SingleLine = true; config.TimestampFormat = Program.timeStampFormat;
- }
- );
- }
- builder.WebHost.ConfigureKestrel(
- (configure) =>
+ ModuleLoadContext context = new(files[0]);
+ try
{
- const uint minPort = 1;
- const uint maxPort = 65535;
-
- bool portValid =
- config.listenPort >= minPort &&
- config.listenPort <= maxPort &&
- !Program.IsPortUsed(config.listenPort);
-
- bool listenHttp =
- IPAddress.TryParse(config.listenAddr, out IPAddress? listenAddress) &&
- portValid;
- if (listenHttp && listenAddress is not null)
- {
- configure.Listen(
- listenAddress,
- (int)config.listenPort
- );
- }
- else if (Socket.OSSupportsUnixDomainSockets)
- {
- configure.ListenUnixSocket(config.listenSocket);
- setSocketPermission = true;
- }
- else
- {
- throw new Exception("Cannot Bind to HTTP or Unix Domain Socket.");
- }
+ Assembly assembly = context.LoadFromAssemblyName(
+ new(Path.GetFileNameWithoutExtension(files[0].FullName))
+ );
+ return assembly;
}
- );
-
- builder.Services.ApplyRuntimeConfig(config);
-
- WebApplication app = builder.Build();
- app.UseExceptionHandler(
- (exceptionHandlerApp) => exceptionHandlerApp.Run(
- async (context) =>
- {
- InvokeResult result = await GenerateDummyResult(context);
- await context.Response.WriteAsJsonAsync(result);
- }
- )
- );
- app.UseRouting();
- string[] allowedMethods = ["GET", "POST"];
- app.Logger.LogDebug("Setting allowed method to {0}", string.Join(", ", allowedMethods));
- app.UseHttpMethodChecker(allowedMethods);
- app.Logger.LogDebug("Setting check Unix timestamp in request");
- app.UseUnixTimestampChecker();
- app.Logger.LogDebug("Setting authToken");
- app.UseAuthTokenAuthentication(config.authToken);
- app.Logger.LogDebug("Mapping controllers");
- app.MapControllers();
- await app.StartAsync();
- if (setSocketPermission && !OperatingSystem.IsWindows())
- {
- File.SetUnixFileMode(config.listenSocket, config.listenSocketPermission.ToUnixFileMode());
+ catch { }
}
- await app.WaitForShutdownAsync();
+ return null;
}
+).OfType();
+builder.Services.AddModules(assemblies.ToArray());
- private static IServiceCollection ApplyRuntimeConfig(this IServiceCollection services, Config config)
- {
- if (config.isCheckPassed)
- {
- if (config.passwords is not null)
- {
- services.AddSingleton(config.passwords);
- }
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddHostedService();
- services.AddHostedService();
- foreach (IModulesChecker checker in Program.modulesCheckers)
- {
- services.AddSingleton(checker);
- }
- Random random = new();
- IEnumerable graphAPICallers = Program.aspNetModules.OfType();
- IEnumerable otherAspNetModules = Program.aspNetModules.Where((module) => (module as IGraphAPICaller) is null);
- foreach (GraphUser user in config.users)
- {
- services.AddSingleton(user);
- IGraphAPICaller caller = random.GetItems(graphAPICallers.ToArray(), 1)[0];
- services.AddKeyedSingleton(user, caller);
- }
- foreach (IAspNetModule module in otherAspNetModules)
- {
- services.AddSingleton(module);
- }
- IEnumerable apiFunctionsTypes = Assembly.GetExecutingAssembly().GetTypes().GetNonAbstractClassesAssainableTo();
- foreach (Type t in apiFunctionsTypes)
- {
- logger.LogDebug("Registering {0} as {1}", t.FullName, nameof(IAPIFunction));
- services.AddSingleton(typeof(IAPIFunction), t);
- }
- }
+if (userSecret is not null)
+{
+ builder.Services.AddUserSecretFile(userSecret);
+}
+else
+{
+ throw new NullReferenceException(nameof(userSecret));
+}
- services.AddControllers(
- ).AddJsonOptions(
- (options) =>
- {
- options.JsonSerializerOptions.WriteIndented = true;
- options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
- }
- ).ConfigureApiBehaviorOptions(
- (options) =>
- {
- options.InvalidModelStateResponseFactory = (actionContext) =>
+builder.Services
+ .AddTokenOverride(token, tokenFile)
+ .AddDummyResultGenerator()
+ .AddSecretProvider()
+ .AddStatusManager()
+ .AddTimeStampGenerator()
+ .AddAPIFunctionImplementations()
+ .AddHostedServices()
+ .AddControllers()
+ .AddJsonOptions(
+ (options) =>
+ options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
+ )
+ .ConfigureApiBehaviorOptions(
+ (options) =>
+ options.InvalidModelStateResponseFactory =
+ (actionContext) =>
{
- InvokeResult result = Program.GenerateDummyResult(actionContext.HttpContext).Result;
+ InvokeResult result = actionContext.HttpContext.RequestServices.GetRequiredService()
+ .GenerateDummyResult(actionContext.HttpContext);
return new JsonResult(result);
- };
- }
- );
- return services;
- }
-
- private static void DiscoverModules(params Assembly[] assemblies)
- {
- foreach (Assembly assembly in assemblies)
- {
- foreach (Type t in Program.GetModules(assembly))
- {
- IModule? instance = null;
- try
- {
- instance = Activator.CreateInstance(t) as IModule;
- }
- catch (Exception e)
- {
- Program.logger.LogError("Failed to create instance for module {0} because {1}.", t.FullName, e.Message);
- }
-
- bool discovered = false;
- if (instance is IModulesChecker moduleChecker)
- {
- Program.logger.LogDebug(
- "Found modules checker {0} with name {1}.",
- moduleChecker.GetType().FullName,
- moduleChecker.name
- );
- modulesCheckers.Add(moduleChecker);
- discovered = true;
- }
-
- if (instance is IConfigParser configParser)
- {
- Program.logger.LogDebug(
- "Found config parser {0} with name {1}.",
- configParser.GetType().FullName,
- configParser.name
- );
- configParsers.Add(configParser);
- discovered = true;
}
+ );
- if (instance is IAspNetModule aspNetModule)
- {
- Program.logger.LogDebug(
- "Found aspnet module {0} with name {1}.",
- aspNetModule.GetType().FullName,
- aspNetModule.name
- );
- aspNetModules.Add(aspNetModule);
- discovered = true;
- }
- if (!discovered && instance is not null)
- {
- Program.logger.LogWarning(
- "Found unknown module {0} with name {1}, ignoring",
- instance.GetType().FullName,
- instance.name);
- }
- }
- }
- }
+WebApplication app = builder.Build();
- private static IEnumerable GetModules(Assembly assembly)
- {
- return assembly.GetTypes().GetNonAbstractClassesAssainableTo().Where(
- (t) => t.IsDefined(typeof(ModuleAttribute))
- );
- }
+app.UseModulesCheckers();
- private static IEnumerable GetPossibleModulesPaths()
- {
- const string modulesBaseFolderName = "modules";
- const string modulesBaseFileName = "E5Renewer.Modules.*.dll";
- Dictionary> results = new();
- DirectoryInfo assemblyDirectory = new(Path.Combine(AppContext.BaseDirectory, modulesBaseFolderName));
- DirectoryInfo[] directoriesToCheck = [assemblyDirectory];
- foreach (DirectoryInfo currentDirectory in directoriesToCheck)
- {
- if (currentDirectory.Exists && !results.ContainsKey(currentDirectory.FullName))
- {
- // /modules/*/E5Renewer.Modules.*/E5Renewer.Modules.*.dll
- IEnumerable directories = currentDirectory.GetFiles(modulesBaseFileName, SearchOption.AllDirectories).Where(
- (fileInfo) => (fileInfo.Directory?.Name ?? string.Empty) == fileInfo.Name.Substring(0, fileInfo.Name.Length - 4)
- ).Select((x) => x.Directory).OfType();
- results[currentDirectory.FullName] = directories;
- }
- }
- return results.Values.SelectMany((x) => x);
- }
- private static bool IsPortUsed(uint port)
- {
- IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
- TcpConnectionInformation[] tcpConnInfoArray = ipGlobalProperties.GetActiveTcpConnections();
- foreach (TcpConnectionInformation info in tcpConnInfoArray)
- {
- if (info.LocalEndPoint.Port == port)
+app.UseExceptionHandler(
+ (exceptionHandlerApp) =>
+ exceptionHandlerApp.Run(
+ async (context) =>
{
- return true;
+ InvokeResult result = await context.RequestServices.GetRequiredService()
+ .GenerateDummyResultAsync(context);
+ await context.Response.WriteAsJsonAsync(result);
}
- }
- return false;
- }
- private static async ValueTask GenerateDummyResult(HttpContext context)
+ )
+
+);
+app.UseRouting();
+string[] allowedMethods = ["GET", "POST"];
+app.Logger.LogDebug("Setting allowed method to {0}", string.Join(", ", allowedMethods));
+app.UseHttpMethodChecker(allowedMethods);
+app.Logger.LogDebug("Setting check Unix timestamp in request");
+app.UseUnixTimestampChecker();
+app.Logger.LogDebug("Setting authToken");
+app.UseAuthTokenAuthentication();
+app.Logger.LogDebug("Mapping controllers");
+app.MapControllers();
+await app.StartAsync();
+if (setSocketPermission && !OperatingSystem.IsWindows() && listenUnixSocketPath is not null)
+{
+ File.SetUnixFileMode(listenUnixSocketPath, listenUnixSocketPermission);
+}
+await app.WaitForShutdownAsync();
+
+static IEnumerable GetPossibleModulesPaths()
+{
+ const string modulesBaseFolderName = "modules";
+ const string modulesBaseFileName = "E5Renewer.Modules.*.dll";
+ Dictionary> results = new();
+ DirectoryInfo assemblyDirectory = new(Path.Combine(AppContext.BaseDirectory, modulesBaseFolderName));
+ DirectoryInfo[] directoriesToCheck = [assemblyDirectory];
+ foreach (DirectoryInfo currentDirectory in directoriesToCheck)
{
- IUnixTimestampGenerator unixTimestampGenerator = context.RequestServices.GetRequiredService();
- Dictionary queries;
- switch (context.Request.Method)
- {
- case "GET":
- queries = context.Request.Query.Select(
- (kv) => new KeyValuePair(kv.Key, kv.Value.FirstOrDefault() as object)
- ).ToDictionary();
- break;
- case "POST":
- byte[] buffer = new byte[context.Request.ContentLength ?? context.Request.Body.Length];
- int length = await context.Request.Body.ReadAsync(buffer);
- byte[] contents = buffer.Take(length).ToArray();
- queries = JsonSerializer.Deserialize>(contents) ?? new();
- break;
- default:
- queries = new();
- break;
- }
- if (queries.ContainsKey("timestamp"))
+ if (currentDirectory.Exists && !results.ContainsKey(currentDirectory.FullName))
{
- queries.Remove("timestamp");
+ // /modules/*/E5Renewer.Modules.*/E5Renewer.Modules.*.dll
+ IEnumerable directories = currentDirectory.GetFiles(modulesBaseFileName, SearchOption.AllDirectories).Where(
+ (fileInfo) => (fileInfo.Directory?.Name ?? string.Empty) == fileInfo.Name.Substring(0, fileInfo.Name.Length - 4)
+ ).Select((x) => x.Directory).OfType();
+ results[currentDirectory.FullName] = directories;
}
- string fullPath = context.Request.PathBase + context.Request.Path;
- int lastOfSlash = fullPath.LastIndexOf("/");
- int firstOfQuote = fullPath.IndexOf("?");
- string methodName =
- firstOfQuote > lastOfSlash ?
- fullPath.Substring(lastOfSlash + 1, firstOfQuote - lastOfSlash) :
- fullPath.Substring(lastOfSlash + 1);
- return new InvokeResult(
- methodName,
- queries,
- null,
- unixTimestampGenerator.GetUnixTimestamp()
- );
}
+ return results.Values.SelectMany((x) => x);
}
diff --git a/E5Renewer/StringArrayExtends.cs b/E5Renewer/StringArrayExtends.cs
new file mode 100644
index 0000000..05372dc
--- /dev/null
+++ b/E5Renewer/StringArrayExtends.cs
@@ -0,0 +1,26 @@
+namespace E5Renewer
+{
+ internal static class StringArrayExtends
+ {
+ public static bool ContainsFlag(this string[] args, string flag)
+ {
+ string[] prefixes = ["--", "-", "/"];
+ return prefixes.Any((prefix) => args.ContainsFlag(flag, prefix));
+ }
+
+ public static bool ContainsFlag(this string[] args, string flag, string prefix)
+ {
+ if (flag.Length <= 0)
+ {
+ throw new InvalidDataException($"{nameof(flag.Length)} of {nameof(flag)} is not greater than 0");
+ }
+ bool allPrefixes = flag.Chunk(prefix.Length)
+ .All((cs) => prefix.Contains(string.Concat(cs)));
+ if (allPrefixes)
+ {
+ throw new InvalidDataException($"{nameof(flag)} is invalid");
+ }
+ return args.Contains($"{prefix}{flag}");
+ }
+ }
+}
diff --git a/E5Renewer/StringExtends.cs b/E5Renewer/StringExtends.cs
new file mode 100644
index 0000000..91dc198
--- /dev/null
+++ b/E5Renewer/StringExtends.cs
@@ -0,0 +1,11 @@
+using System.Net;
+
+namespace E5Renewer
+{
+ internal static class StringExtends
+ {
+ public static FileInfo AsFileInfo(this string s) => new(s);
+
+ public static IPEndPoint? AsIPEndPoint(this string s) => IPEndPoint.TryParse(s, out IPEndPoint? result) ? result : null;
+ }
+}
diff --git a/E5Renewer/TypeArrayExtends.cs b/E5Renewer/TypeArrayExtends.cs
index 0985c27..6ce06c6 100644
--- a/E5Renewer/TypeArrayExtends.cs
+++ b/E5Renewer/TypeArrayExtends.cs
@@ -1,7 +1,7 @@
namespace E5Renewer
{
/// Extends to Type[]
- public static class TypeArrayExtends
+ internal static class TypeArrayExtends
{
/// Get types which is not abstract and is assainabble to T.
/// The array of types.
diff --git a/E5Renewer/WebApplicationExtends.cs b/E5Renewer/WebApplicationExtends.cs
index 4251fa5..624ed1d 100644
--- a/E5Renewer/WebApplicationExtends.cs
+++ b/E5Renewer/WebApplicationExtends.cs
@@ -1,38 +1,69 @@
using System.Text.Json;
+using E5Renewer.Controllers;
+using E5Renewer.Models.GraphAPIs;
+using E5Renewer.Models.Modules;
+using E5Renewer.Models.Secrets;
using E5Renewer.Models.Statistics;
using Microsoft.Extensions.Primitives;
namespace E5Renewer
{
- /// Extends to WebApplication.
- public static class WebApplicationExtends
+ internal static class WebApplicationExtends
{
- /// Use custom authentication middleware.
- /// The WebApplication instance.
- /// The token used for authentication.
- public static IApplicationBuilder UseAuthTokenAuthentication(this WebApplication app, string authToken)
+ public static IApplicationBuilder UseModulesCheckers(this WebApplication app)
+ {
+ IEnumerable modulesCheckers = app.Services.GetServices();
+ IEnumerable userSecretLoaders = app.Services.GetServices();
+ IEnumerable graphAPICallers = app.Services.GetServices();
+ IEnumerable otherModules = app.Services.GetServices();
+
+ List modulesToCheck = new();
+ modulesToCheck.AddRange(modulesCheckers);
+ modulesToCheck.AddRange(userSecretLoaders);
+ modulesToCheck.AddRange(graphAPICallers);
+ modulesToCheck.AddRange(otherModules);
+
+ modulesToCheck.ForEach(
+ (m) =>
+ {
+ foreach (IModulesChecker checker in modulesCheckers)
+ {
+
+ try
+ {
+ checker.CheckModules(m);
+ }
+ catch { }
+ }
+ }
+ );
+
+ return app;
+ }
+
+ public static IApplicationBuilder UseAuthTokenAuthentication(this WebApplication app)
{
return app.Use(
async (context, next) =>
{
+ ISecretProvider secretProvider = app.Services.GetRequiredService();
+ IDummyResultGenerator dummyResultGenerator = app.Services.GetRequiredService();
const string authenticationHeaderName = "Authentication";
string? authentication = context.Request.Headers[authenticationHeaderName].FirstOrDefault();
- if (authentication == authToken)
+ if (authentication == await secretProvider.GetRuntimeTokenAsync())
{
await next();
return;
}
context.Response.StatusCode = StatusCodes.Status403Forbidden;
- await context.Response.WriteAsync("Authenticate failed");
+ InvokeResult result = await dummyResultGenerator.GenerateDummyResultAsync(context);
+ await context.Response.WriteAsJsonAsync(result);
}
);
}
- /// Only allow methods given to connect.
- /// The WebApplication instance.
- /// The request methods to allow.
public static IApplicationBuilder UseHttpMethodChecker(this WebApplication app, params string[] methods)
{
return app.Use(
@@ -49,9 +80,6 @@ public static IApplicationBuilder UseHttpMethodChecker(this WebApplication app,
);
}
- /// Check timestamp in request.
- /// The WebApplication instance.
- /// Max allowed seconds.
public static IApplicationBuilder UseUnixTimestampChecker(this WebApplication app, uint allowedMaxSeconds = 30)
{
return app.Use(
@@ -75,7 +103,8 @@ public static IApplicationBuilder UseUnixTimestampChecker(this WebApplication ap
return;
}
context.Response.StatusCode = StatusCodes.Status403Forbidden;
- await context.Response.WriteAsync("Request is outdated");
+ InvokeResult result = await app.Services.GetRequiredService().GenerateDummyResultAsync(context);
+ await context.Response.WriteAsJsonAsync(result);
}
);
}
diff --git a/README.md b/README.md
index c7e5e0d..0cb8898 100644
--- a/README.md
+++ b/README.md
@@ -30,17 +30,15 @@ A tool to renew e5 subscription by calling msgraph APIs
2. Create Configuration
- Copy [`config.json.example`](./config.json.example) to `config.json`, edit it as your need. You can always add more credentials. Please edit `auth_token` so only people you authenticated can access the statistics.
-
- We will listen on tcp socket by default, if `listen_addr` is an empty string and your platform supports Unix domain socket, we will listen on Unix domain socket with path `listen_socket` and permission `listen_socket_permission`.
+ Copy [`config.json.example`](./config.json.example) to `config.json`, edit it as your need. You can always add more credentials.
If you want to use certificate instead secret, which is better for security, you can write a `certificate` key with path to your certificate file instead `secret` key.
+ If we find you set `certificate`, it will always be used instead `secret`.
- Tips: We support json, yaml and toml formats, just let their contents be equal, the configuration result is same.
+ Setting days is needed to be cautious, as it means `DayOfWeek` in program,
+ check [here](https://learn.microsoft.com/en-us/dotnet/api/system.dayofweek#fields) to find out its correct value.
- > [!NOTE]
- > Due to that C# does not have a native octal number support, we use the `listen_socket_permission` as unix permission directly.
- > For example, if you are using json format, you need to set it to `438` in order to see socket mode is `rw-rw-rw-`.
+ Tips: We support json, yaml and toml formats, just let their contents be equal, the configuration result is same.
3. Install .NET
@@ -53,7 +51,28 @@ A tool to renew e5 subscription by calling msgraph APIs
5. Run program
- Simply run `./E5Renewer` in binaries folder.
+ Simply run `./E5Renewer` in binaries folder with arguments needed.
+
+ Here are supported arguments:
+
+ - `--systemd`: If runs in systemd environment, most of the time you should not need it.
+ - `--user-secret`: The path to the user secret file. User secret file is used to storage sensitive information, so please keep it safe.
+ - `--token`: The string to access json api, please keep it safe.
+ - `--token-file`: The file which contains token, please keep that file safe.
+ - `--listen-tcp-socket`: The tcp socket to listen instead default one(`127.0.0.1:5000`).
+ - `--listen-unix-socket-path`: The path to create a unix domain socket to access json api.
+ - `--listen-inux-socket-permission`: The permission to the unix domain socket file.
+ - All AspNet.Core supported arguments. See [here](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/#command-line) for more info.
+
+ We will listen on tcp socket `127.0.0.1:5000` by default, if you want to customize it,
+ you need to set commandline argument `--listen-tcp-socket` like `--listen-tcp-socket=127.0.0.1:8888`.
+ You can also choose listen unix domain socket by setting commandline argument like `--listen-unix-socket-path=/path/to/socket`
+ and set socket file permission with argument like `--listen-unix-socket-permission=511`.
+
+ > [!NOTE]
+ > If `--token` and `--token-file` both are specified, we prefer `--token`. If you forget to set nither of them, we use a randomly generated value.
+ > You can find it out in log output after sending any request to the program and meeting a authentication error.
+ > If you want to set unix socket permission, you have to write its actual value instead octal format. For example, using `511` instead `777` is required.
## Get running statistics
@@ -61,6 +80,7 @@ Using `curl` or any tool which can send http request, send request to `http://`.
You will get json response if everything is fine. If it is a GET request, send milisecond timestamp in query param `timestamp`,
If it is a POST request, send milisecond timestamp in post json with key `timestamp` and convert it to string.
+Most of the time, we will return json instead plain text, but you need to check response code to see if request is success.
For example:
diff --git a/config.json.example b/config.json.example
index 8ee56f0..21419a8 100644
--- a/config.json.example
+++ b/config.json.example
@@ -1,9 +1,4 @@
{
- "auth_token": "example-auth-token",
- "listen_addr": "127.0.0.1",
- "listen_port": 8888,
- "listen_socket": "/run/e5renewer/e5renewer.socket",
- "listen_socket_permission": 666,
"users": [
{
"name": "e5renewer",
@@ -11,7 +6,8 @@
"client_id": "",
"secret": "",
"from_time": "00:00:00",
- "to_time": "23:59:59"
+ "to_time": "23:59:59",
+ "days": [1,2,3]
}
]
}
\ No newline at end of file
diff --git a/e5renewer.service b/e5renewer.service
index b5ddbe8..5bacc32 100644
--- a/e5renewer.service
+++ b/e5renewer.service
@@ -4,17 +4,22 @@ Wants=network-online.target
After=network-online.target
[Service]
-ExecStart=E5Renewer -c /etc/e5renewer/config.json --systemd
+LoadCredential=user-secret.json:%E/e5renewer/user-secret.json
+LoadCredential=token.txt:%E/e5renewer/token.txt
+ExecStart=/usr/bin/E5Renewer \
+ --user-secret=%d/user-secret.json \
+ --token-file=%d/token.txt \
+ --systemd
User=e5renewer
DynamicUser=yes
+RuntimeDirectory=e5renewer
+
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
-RuntimeDirectory=e5renewer
-ConfigurationDirectory=e5renewer
PrivateDevices=yes
PrivateNetwork=yes
PrivateUsers=yes