mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 06:51:55 +00:00
Merge pull request #49 from LD-Reborn/9-feature-implement-ldap-migrations
9 feature implement ldap migrations
This commit is contained in:
@@ -3,6 +3,8 @@ using Berufsschule_HAM.Services;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Novell.Directory.Ldap;
|
using Novell.Directory.Ldap;
|
||||||
using Berufsschule_HAM.Models;
|
using Berufsschule_HAM.Models;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
public class UsersController : Controller
|
public class UsersController : Controller
|
||||||
@@ -58,7 +60,7 @@ public class UsersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("Create")]
|
[HttpGet("Create")]
|
||||||
public bool Create(string cn, string sn, string? title, string? uid, string userPassword, string? description, string jpegPhoto)
|
public async Task<bool> Create(string cn, string sn, string? title, string? uid, string userPassword, string? description, string jpegPhoto)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -66,8 +68,14 @@ public class UsersController : Controller
|
|||||||
uid ??= sn.ToLower() + cn.ToLower();
|
uid ??= sn.ToLower() + cn.ToLower();
|
||||||
title ??= "";
|
title ??= "";
|
||||||
description ??= "{}";
|
description ??= "{}";
|
||||||
LdapAttributeSet attributeSet = new LdapAttributeSet
|
if (!userPassword.StartsWith('{'))
|
||||||
{
|
{
|
||||||
|
byte[] passwordBytes = Encoding.UTF8.GetBytes(userPassword);
|
||||||
|
byte[] hashedPassword = SHA256.HashData(passwordBytes);
|
||||||
|
userPassword = "{SHA256}" + Convert.ToBase64String(hashedPassword);
|
||||||
|
}
|
||||||
|
LdapAttributeSet attributeSet =
|
||||||
|
[
|
||||||
new LdapAttribute("objectClass", "inetOrgPerson"),
|
new LdapAttribute("objectClass", "inetOrgPerson"),
|
||||||
new LdapAttribute("cn", cn),
|
new LdapAttribute("cn", cn),
|
||||||
new LdapAttribute("sn", sn),
|
new LdapAttribute("sn", sn),
|
||||||
@@ -76,8 +84,8 @@ public class UsersController : Controller
|
|||||||
new LdapAttribute("jpegPhoto", jpegPhoto),
|
new LdapAttribute("jpegPhoto", jpegPhoto),
|
||||||
new LdapAttribute("description", description),
|
new LdapAttribute("description", description),
|
||||||
new LdapAttribute("userPassword", userPassword)
|
new LdapAttribute("userPassword", userPassword)
|
||||||
};
|
];
|
||||||
_ldap.CreateUser(uid, attributeSet);
|
await _ldap.CreateUser(uid, attributeSet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
3
src/Exceptions/MigrationExceptions.cs
Normal file
3
src/Exceptions/MigrationExceptions.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Berufsschule_HAM.Exceptions;
|
||||||
|
|
||||||
|
public class InvalidMigrationDescriptionModel : Exception {}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace Berufsschule_HAM.Services;
|
namespace Berufsschule_HAM.Models;
|
||||||
public class LdapOptions
|
|
||||||
|
public class LdapConfig
|
||||||
{
|
{
|
||||||
public required string Host { get; set; }
|
public required string Host { get; set; }
|
||||||
public int Port { get; set; } = 389;
|
public int Port { get; set; } = 389;
|
||||||
@@ -10,4 +11,6 @@ public class LdapOptions
|
|||||||
public string AssetsOu { get; set; } = "ou=assets";
|
public string AssetsOu { get; set; } = "ou=assets";
|
||||||
public string LocationsOu { get; set; } = "ou=locations";
|
public string LocationsOu { get; set; } = "ou=locations";
|
||||||
public string UsersOu { get; set; } = "ou=users";
|
public string UsersOu { get; set; } = "ou=users";
|
||||||
|
public string GroupsOu { get; set; } = "ou=groups";
|
||||||
|
public string MigrationsOu { get; set; } = "ou=migrations";
|
||||||
}
|
}
|
||||||
28
src/Models/MigrationModels.cs
Normal file
28
src/Models/MigrationModels.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Berufsschule_HAM.Exceptions;
|
||||||
|
|
||||||
|
namespace Berufsschule_HAM.Models;
|
||||||
|
|
||||||
|
public class MigrationModel
|
||||||
|
{
|
||||||
|
public int Version { get; set; }
|
||||||
|
public MigrationModel(Dictionary<string, string> ldapData)
|
||||||
|
{
|
||||||
|
string? description = ldapData.GetValueOrDefault("description");
|
||||||
|
if (description is null)
|
||||||
|
{
|
||||||
|
Version = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MigrationDescriptionModel? descriptionModel = JsonSerializer.Deserialize<MigrationDescriptionModel>(description)
|
||||||
|
?? throw new InvalidMigrationDescriptionModel();
|
||||||
|
Version = descriptionModel.Version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MigrationDescriptionModel
|
||||||
|
{
|
||||||
|
public required int Version { get; set; }
|
||||||
|
}
|
||||||
@@ -3,13 +3,11 @@ using ElmahCore.Mvc;
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Berufsschule_HAM.Services;
|
using Berufsschule_HAM.Services;
|
||||||
|
using Berufsschule_HAM.Models;
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Bind options
|
// Bind options
|
||||||
builder.Services.Configure<LdapOptions>(builder.Configuration.GetSection("Ldap"));
|
builder.Services.Configure<LdapConfig>(builder.Configuration.GetSection("Ldap"));
|
||||||
|
|
||||||
// Register LDAP service as singleton (it manages its own connection)
|
|
||||||
|
|
||||||
|
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
@@ -26,6 +24,7 @@ builder.Services.AddElmah<XmlFileErrorLog>(Options =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddSingleton<LdapService>();
|
builder.Services.AddSingleton<LdapService>();
|
||||||
|
builder.Services.AddSingleton<MigrationService>();
|
||||||
|
|
||||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||||
.AddCookie(options =>
|
.AddCookie(options =>
|
||||||
@@ -54,4 +53,8 @@ app.MapControllerRoute(
|
|||||||
name: "default",
|
name: "default",
|
||||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||||
|
|
||||||
|
// Run migrations
|
||||||
|
using var scope = app.Services.CreateScope();
|
||||||
|
var migrationService = scope.ServiceProvider.GetRequiredService<MigrationService>();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ using Berufsschule_HAM.Models;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Berufsschule_HAM.Services;
|
namespace Berufsschule_HAM.Services;
|
||||||
public partial class LdapService : IDisposable
|
public partial class LdapService : IDisposable
|
||||||
{
|
{
|
||||||
private readonly LdapOptions _opts;
|
private readonly LdapConfig _opts;
|
||||||
private readonly LdapConnection _conn;
|
private readonly LdapConnection _conn;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
||||||
public LdapService(IOptions<LdapOptions> options, ILogger<LdapService> logger)
|
public LdapService(IOptions<LdapConfig> options, ILogger<LdapService> logger)
|
||||||
{
|
{
|
||||||
_opts = options.Value;
|
_opts = options.Value;
|
||||||
_conn = new LdapConnection { SecureSocketLayer = _opts.UseSsl };
|
_conn = new LdapConnection { SecureSocketLayer = _opts.UseSsl };
|
||||||
@@ -39,9 +40,11 @@ public partial class LdapService : IDisposable
|
|||||||
await _conn.BindAsync(_opts.BindDn, _opts.BindPassword);
|
await _conn.BindAsync(_opts.BindDn, _opts.BindPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string AssetsBaseDn => string.IsNullOrEmpty(_opts.AssetsOu) ? _opts.BaseDn : $"{_opts.AssetsOu},{_opts.BaseDn}";
|
public string AssetsBaseDn => string.IsNullOrEmpty(_opts.AssetsOu) ? _opts.BaseDn : $"{_opts.AssetsOu},{_opts.BaseDn}";
|
||||||
private string LocationsBaseDn => string.IsNullOrEmpty(_opts.LocationsOu) ? _opts.BaseDn : $"{_opts.LocationsOu},{_opts.BaseDn}";
|
public string LocationsBaseDn => string.IsNullOrEmpty(_opts.LocationsOu) ? _opts.BaseDn : $"{_opts.LocationsOu},{_opts.BaseDn}";
|
||||||
private string UsersBaseDn => string.IsNullOrEmpty(_opts.UsersOu) ? _opts.BaseDn : $"{_opts.UsersOu},{_opts.BaseDn}";
|
public string UsersBaseDn => string.IsNullOrEmpty(_opts.UsersOu) ? _opts.BaseDn : $"{_opts.UsersOu},{_opts.BaseDn}";
|
||||||
|
public string GroupsBaseDn => string.IsNullOrEmpty(_opts.GroupsOu) ? _opts.BaseDn : $"{_opts.GroupsOu},{_opts.BaseDn}";
|
||||||
|
public string MigrationsBaseDn => string.IsNullOrEmpty(_opts.MigrationsOu) ? _opts.BaseDn : $"{_opts.MigrationsOu},{_opts.BaseDn}";
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, string>>> ListLocationsAsync()
|
public async Task<IEnumerable<Dictionary<string, string>>> ListLocationsAsync()
|
||||||
{
|
{
|
||||||
@@ -53,6 +56,41 @@ public partial class LdapService : IDisposable
|
|||||||
return await ListObjectBy(UsersBaseDn, "", ["cn", "sn", "title", "uid", "jpegPhoto", "userPassword", "description"]);
|
return await ListObjectBy(UsersBaseDn, "", ["cn", "sn", "title", "uid", "jpegPhoto", "userPassword", "description"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MigrationModel> GetMigrationVersionAsync()
|
||||||
|
{
|
||||||
|
Dictionary<string, string> objects;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
objects = (await ListObjectBy(_opts.BaseDn, _opts.MigrationsOu, ["description"])).First();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
objects = [];
|
||||||
|
}
|
||||||
|
return new MigrationModel(objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateMigrationVersionAsync(MigrationModel migrationModel)
|
||||||
|
{
|
||||||
|
await ConnectAndBind();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string dn = MigrationsBaseDn; //PrependRDN($"", MigrationsBaseDn);
|
||||||
|
string targetText = JsonSerializer.Serialize(migrationModel);
|
||||||
|
_logger.LogInformation("Setting the LDAP migration description to {targetText} for {dn}", [targetText, dn]);
|
||||||
|
var modification = new LdapModification(
|
||||||
|
LdapModification.Replace,
|
||||||
|
new LdapAttribute("description", targetText)
|
||||||
|
);
|
||||||
|
await _conn.ModifyAsync(dn, modification);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<UserModel>> ListUsersAsync(string[] attributes)
|
public async Task<IEnumerable<UserModel>> ListUsersAsync(string[] attributes)
|
||||||
{
|
{
|
||||||
List<UserModel> returnValue = [];
|
List<UserModel> returnValue = [];
|
||||||
@@ -85,25 +123,27 @@ public partial class LdapService : IDisposable
|
|||||||
return await ListObjectBy(AssetsBaseDn, "(objectClass=device)", ["CN", "description", "l", "owner", "serialNumber"]);
|
return await ListObjectBy(AssetsBaseDn, "(objectClass=device)", ["CN", "description", "l", "owner", "serialNumber"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateUser(string uid, LdapAttributeSet attributeSet)
|
public async Task CreateUser(string uid, LdapAttributeSet attributeSet)
|
||||||
{
|
{
|
||||||
string dn = PrependRDN($"uid={uid}", UsersBaseDn);
|
string dn = PrependRDN($"uid={uid}", UsersBaseDn);
|
||||||
CreateObject(dn, attributeSet);
|
await CreateObject(dn, attributeSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateAsset(LdapAttributeSet attributeSet)
|
public async Task CreateAsset(LdapAttributeSet attributeSet)
|
||||||
{
|
{
|
||||||
CreateObject(AssetsBaseDn, attributeSet);
|
await CreateObject(AssetsBaseDn, attributeSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateLocation(LdapAttributeSet attributeSet)
|
public async Task CreateLocation(LdapAttributeSet attributeSet)
|
||||||
{
|
{
|
||||||
CreateObject(LocationsBaseDn, attributeSet);
|
await CreateObject(LocationsBaseDn, attributeSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserAuthenticationResult> AuthenticateUser(string username, string password)
|
public async Task<UserAuthenticationResult> AuthenticateUser(string username, string password)
|
||||||
{
|
{
|
||||||
await ConnectAndBind();
|
await ConnectAndBind();
|
||||||
|
try
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
UserModel user = await GetUserByUidAsync(username);
|
UserModel user = await GetUserByUidAsync(username);
|
||||||
@@ -113,10 +153,15 @@ public partial class LdapService : IDisposable
|
|||||||
}
|
}
|
||||||
if (CompareStringToSha256(password, user.UserPassword))
|
if (CompareStringToSha256(password, user.UserPassword))
|
||||||
{
|
{
|
||||||
return new() { Success = true};
|
return new() { Success = true };
|
||||||
}
|
}
|
||||||
return new() { Success = false, AuthenticationState = UserNotAuthenticatedReason.InvalidCredentials };
|
return new() { Success = false, AuthenticationState = UserNotAuthenticatedReason.InvalidCredentials };
|
||||||
}
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return new() { Success = false, AuthenticationState = UserNotAuthenticatedReason.InvalidCredentials };
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (LdapException)
|
catch (LdapException)
|
||||||
{
|
{
|
||||||
return new() { Success = false, AuthenticationState = UserNotAuthenticatedReason.InvalidCredentials };
|
return new() { Success = false, AuthenticationState = UserNotAuthenticatedReason.InvalidCredentials };
|
||||||
@@ -201,13 +246,12 @@ public partial class LdapService : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async void DeleteObjectByDn(string dn)
|
public async void DeleteObjectByDn(string dn)
|
||||||
{
|
{
|
||||||
await _conn.DeleteAsync(dn);
|
await _conn.DeleteAsync(dn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void CreateObject(string dn, LdapAttributeSet attributeSet)
|
public async Task CreateObject(string dn, LdapAttributeSet attributeSet)
|
||||||
{
|
{
|
||||||
await ConnectAndBind();
|
await ConnectAndBind();
|
||||||
LdapEntry ldapEntry = new(dn, attributeSet);
|
LdapEntry ldapEntry = new(dn, attributeSet);
|
||||||
|
|||||||
115
src/Services/MigrationService.cs
Normal file
115
src/Services/MigrationService.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
using Berufsschule_HAM.Models;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Novell.Directory.Ldap;
|
||||||
|
using System.Reflection;
|
||||||
|
namespace Berufsschule_HAM.Services;
|
||||||
|
|
||||||
|
public class MigrationService
|
||||||
|
{
|
||||||
|
private readonly LdapService _ldapService;
|
||||||
|
private readonly ILogger<MigrationService> _logger;
|
||||||
|
private readonly LdapConfig _ldapConfig;
|
||||||
|
|
||||||
|
public MigrationService(LdapService ldapService, ILogger<MigrationService> logger, IOptions<LdapConfig> ldapConfig)
|
||||||
|
{
|
||||||
|
_ldapService = ldapService;
|
||||||
|
_logger = logger;
|
||||||
|
_ldapConfig = ldapConfig.Value;
|
||||||
|
MigrateAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
public async Task<MigrationModel> MigrateAsync()
|
||||||
|
{
|
||||||
|
MigrationModel migrationModel = await _ldapService.GetMigrationVersionAsync();
|
||||||
|
int version = migrationModel.Version;
|
||||||
|
string dc = _ldapConfig.BaseDn.Split("=")[1];
|
||||||
|
|
||||||
|
List<MethodInfo> updateMethods = [.. typeof(MigrationService)
|
||||||
|
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||||
|
.Where(m => m.Name.StartsWith("UpdateFrom")
|
||||||
|
&& m.Name.EndsWith("Async")
|
||||||
|
&& m.ReturnType == typeof(Task<int>))
|
||||||
|
.OrderBy(m => int.Parse(m.Name["UpdateFrom".Length..^5]))]; // TODO remove magic number ("5" -> global constant)
|
||||||
|
|
||||||
|
foreach (var method in updateMethods)
|
||||||
|
{
|
||||||
|
int methodVersion = int.Parse(method.Name["UpdateFrom".Length..^5]); // TODO TODO remove magic number ("5" -> global constant)
|
||||||
|
if (methodVersion >= version)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Migrating LDAP database from version {version} to {methodVersion}", [version, methodVersion + 1]);
|
||||||
|
if (method is null) { continue; }
|
||||||
|
#pragma warning disable CS8605 // Unboxing a possibly null value.
|
||||||
|
version = (int)method.Invoke(null, [_ldapService]);
|
||||||
|
#pragma warning restore CS8605 // Unboxing a possibly null value.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version != migrationModel.Version)
|
||||||
|
{
|
||||||
|
migrationModel.Version = version;
|
||||||
|
await _ldapService.UpdateMigrationVersionAsync(migrationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return migrationModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<int> UpdateFrom0Async(LdapService ldapService)
|
||||||
|
{
|
||||||
|
LdapAttributeSet usersAttributes =
|
||||||
|
[
|
||||||
|
new("objectClass", "organizationalUnit"),
|
||||||
|
new("objectClass", "top"),
|
||||||
|
new("ou", "users")
|
||||||
|
];
|
||||||
|
await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.UsersBaseDn, usersAttributes);
|
||||||
|
LdapAttributeSet locationsAttributes =
|
||||||
|
[
|
||||||
|
new("objectClass", "organizationalUnit"),
|
||||||
|
new("objectClass", "top"),
|
||||||
|
new("ou", "locations")
|
||||||
|
];
|
||||||
|
await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.LocationsBaseDn, locationsAttributes);
|
||||||
|
LdapAttributeSet groupsAttributes =
|
||||||
|
[
|
||||||
|
new("objectClass", "organizationalUnit"),
|
||||||
|
new("objectClass", "top"),
|
||||||
|
new("ou", "groups")
|
||||||
|
];
|
||||||
|
await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.GroupsBaseDn, groupsAttributes);
|
||||||
|
LdapAttributeSet assetsAttributes =
|
||||||
|
[
|
||||||
|
new("objectClass", "organizationalUnit"),
|
||||||
|
new("objectClass", "top"),
|
||||||
|
new("ou", "assets")
|
||||||
|
];
|
||||||
|
await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.AssetsBaseDn, assetsAttributes);
|
||||||
|
LdapAttributeSet migrationsAttributes =
|
||||||
|
[
|
||||||
|
new("objectClass", "organizationalUnit"),
|
||||||
|
new("objectClass", "top"),
|
||||||
|
new("ou", "migrations"),
|
||||||
|
new("description", "{\"Version\": 1}")
|
||||||
|
];
|
||||||
|
await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.MigrationsBaseDn, migrationsAttributes);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task TryCreateObjectIgnoreAlreadyExists(LdapService ldapService, string dn, LdapAttributeSet ldapAttributes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ldapService.CreateObject(dn, ldapAttributes);
|
||||||
|
}
|
||||||
|
catch (LdapException ex)
|
||||||
|
{
|
||||||
|
if (ex.ResultCode != LdapException.EntryAlreadyExists)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
{ "Name": "File", "Args": { "path": "logs/log.txt", "rollingInterval": "Day", "retainedFileCountLimit": 7 } }
|
{ "Name": "File", "Args": { "path": "logs/log.txt", "rollingInterval": "Day", "retainedFileCountLimit": 7 } }
|
||||||
],
|
],
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"Application": "Embeddingsearch.Indexer"
|
"Application": "Berufsschule_HAM"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Elmah": {
|
"Elmah": {
|
||||||
|
|||||||
Reference in New Issue
Block a user