Integrazione base del DB esistente e login tramite LDAP

This commit is contained in:
2025-09-23 12:52:49 +02:00
parent 0a2de567a5
commit 6d5bc278d7
30 changed files with 2002 additions and 13 deletions

View File

@@ -24,8 +24,8 @@
<MudGrid>
<MudItem md="12">
<MudStaticTextField For="@(() => Input.Email)" @bind-Value="Input.Email"
Label="Email" Placeholder="name@example.com"
<MudStaticTextField For="@(() => Input.Username)" @bind-Value="Input.Username"
Label="Username" Placeholder="MarioR"
UserAttributes="@(new() { { "autocomplete", "username" }, { "aria-required", "true" } } )" />
</MudItem>
<MudItem md="12">
@@ -82,7 +82,7 @@
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
var result = await SignInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
Logger.LogInformation("User logged in.");
@@ -108,8 +108,8 @@
private sealed class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = "";
// [EmailAddress]
public string Username { get; set; } = "";
[Required]
[DataType(DataType.Password)]

View File

@@ -0,0 +1,101 @@
using IntegryControlPanel.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace IntegryControlPanel.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class LdapController : ControllerBase
{
private readonly ILdapService _ldapService;
private readonly LdapUserManager _ldapUserManager;
private readonly ILogger<LdapController> _logger;
public LdapController(
ILdapService ldapService,
LdapUserManager ldapUserManager,
ILogger<LdapController> logger)
{
_ldapService = ldapService;
_ldapUserManager = ldapUserManager;
_logger = logger;
}
[HttpPost("sync-user")]
[Authorize]
public async Task<IActionResult> SyncUser([FromBody] string username)
{
if (string.IsNullOrWhiteSpace(username))
return BadRequest("Username is required");
try
{
var user = await _ldapUserManager.SyncUserFromLdapAsync(username);
if (user == null)
return NotFound($"User {username} not found in LDAP");
return Ok(new
{
Message = "User synced successfully",
User = new
{
user.UserName,
user.Email,
user.FirstName,
user.LastName,
user.Department,
user.Title,
user.LastLdapSync
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error syncing user {Username}", username);
return StatusCode(500, "Internal server error");
}
}
[HttpGet("users-in-group/{groupName}")]
[Authorize]
public async Task<IActionResult> GetUsersInGroup(string groupName)
{
if (string.IsNullOrWhiteSpace(groupName))
return BadRequest("Group name is required");
try
{
var users = await _ldapService.GetUsersInGroupAsync(groupName);
return Ok(users);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting users in group {GroupName}", groupName);
return StatusCode(500, "Internal server error");
}
}
[HttpGet("user/{username}")]
[Authorize]
public async Task<IActionResult> GetUser(string username)
{
if (string.IsNullOrWhiteSpace(username))
return BadRequest("Username is required");
try
{
var user = await _ldapService.GetUserAsync(username);
if (user == null)
return NotFound($"User {username} not found in LDAP");
return Ok(user);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting user {Username}", username);
return StatusCode(500, "Internal server error");
}
}
}
}

View File

@@ -5,6 +5,13 @@ namespace IntegryControlPanel.Data
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? DisplayName { get; set; }
public string? Department { get; set; }
public string? Title { get; set; }
public bool IsLdapUser { get; set; } = false;
public string? LdapUsername { get; set; }
public DateTime? LastLdapSync { get; set; }
}
}

View File

@@ -0,0 +1,515 @@
using System;
using System.Collections.Generic;
using IntegryControlPanel.Models.IntegryControlPanel;
using Microsoft.EntityFrameworkCore;
namespace IntegryControlPanel.Data;
public partial class IntegryControlPanelDbContext : DbContext
{
public IntegryControlPanelDbContext()
{
}
public IntegryControlPanelDbContext(DbContextOptions<IntegryControlPanelDbContext> options)
: base(options)
{
}
public virtual DbSet<ApplicationInfo> ApplicationInfos { get; set; }
public virtual DbSet<Models.IntegryControlPanel.Client> Clients { get; set; }
public virtual DbSet<Configurazioni> Configurazionis { get; set; }
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<DatabaseEngine> DatabaseEngines { get; set; }
public virtual DbSet<DatabasesInfo> DatabasesInfos { get; set; }
public virtual DbSet<Device> Devices { get; set; }
public virtual DbSet<Installation> Installations { get; set; }
public virtual DbSet<PvmsInfo> PvmsInfos { get; set; }
public virtual DbSet<Release> Releases { get; set; }
public virtual DbSet<SalvataggiSoap> SalvataggiSoaps { get; set; }
public virtual DbSet<Server> Servers { get; set; }
public virtual DbSet<Service> Services { get; set; }
public virtual DbSet<VwServerLastUpdate> VwServerLastUpdates { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseSqlServer("Server=SERVERDB2019;Database=integry_control_panel_test;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True;");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ApplicationInfo>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__applicat__3213E83F4185DE9F");
entity.ToTable("application_infos");
entity.HasIndex(e => e.CustomerId, "IDX_D5E65179395C3F3");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.AnnoContab).HasColumnName("anno_contab");
entity.Property(e => e.AnnoMagaz).HasColumnName("anno_magaz");
entity.Property(e => e.AnsiPadding)
.IsRequired()
.HasDefaultValueSql("('0')")
.HasColumnName("ansi_padding");
entity.Property(e => e.ConcatNullYieldsNull)
.IsRequired()
.HasDefaultValueSql("('0')")
.HasColumnName("concat_null_yields_null");
entity.Property(e => e.CustomerId).HasColumnName("customer_id");
entity.Property(e => e.DelimitedIdentifier)
.IsRequired()
.HasDefaultValueSql("('0')")
.HasColumnName("delimited_identifier");
entity.Property(e => e.MenuPersonalizzato)
.HasMaxLength(255)
.HasColumnName("menu_personalizzato");
entity.Property(e => e.Name)
.HasMaxLength(255)
.HasColumnName("name");
entity.Property(e => e.NewUpdProgMaga).HasColumnName("new_upd_prog_maga");
entity.HasOne(d => d.Customer).WithMany(p => p.ApplicationInfos)
.HasForeignKey(d => d.CustomerId)
.HasConstraintName("FK_D5E65179395C3F3");
});
modelBuilder.Entity<Models.IntegryControlPanel.Client>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__clients__3213E83FFF8DC3E2");
entity.ToTable("clients");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.DeviceId)
.HasMaxLength(255)
.HasColumnName("device_id");
entity.Property(e => e.InsertDate)
.HasPrecision(6)
.HasColumnName("insert_date");
entity.Property(e => e.LastUpdate)
.HasPrecision(6)
.HasColumnName("last_update");
entity.Property(e => e.NomeAzienda)
.HasMaxLength(255)
.HasColumnName("nome_azienda");
entity.Property(e => e.RemoteAddr)
.HasMaxLength(255)
.HasColumnName("remote_addr");
});
modelBuilder.Entity<Configurazioni>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__configur__3213E83FB9077FED");
entity.ToTable("configurazioni");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.JavaVersion)
.HasMaxLength(255)
.HasColumnName("java_version");
entity.Property(e => e.LastUpdate)
.HasPrecision(6)
.HasColumnName("last_update");
entity.Property(e => e.MaxPermSize)
.HasMaxLength(255)
.HasColumnName("max_perm_size");
entity.Property(e => e.NomeAzienda)
.HasMaxLength(255)
.HasColumnName("nome_azienda");
entity.Property(e => e.OsArch)
.HasMaxLength(255)
.HasColumnName("os_arch");
entity.Property(e => e.OsName)
.HasMaxLength(255)
.HasColumnName("os_name");
entity.Property(e => e.RemoteAddr)
.HasMaxLength(255)
.HasColumnName("remote_addr");
entity.Property(e => e.Xms)
.HasMaxLength(255)
.HasColumnName("xms");
entity.Property(e => e.Xmx)
.HasMaxLength(255)
.HasColumnName("xmx");
});
modelBuilder.Entity<Customer>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__customer__3213E83FD00417AC");
entity.ToTable("customers");
entity.HasIndex(e => e.Slug, "UNIQ_62534E21989D9B62")
.IsUnique()
.HasFilter("([slug] IS NOT NULL)");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Active)
.IsRequired()
.HasDefaultValueSql("('0')")
.HasColumnName("active");
entity.Property(e => e.Name)
.HasMaxLength(255)
.HasColumnName("name");
entity.Property(e => e.PartitaIva)
.HasMaxLength(255)
.HasColumnName("partita_iva");
entity.Property(e => e.Slug)
.HasMaxLength(191)
.HasColumnName("slug");
});
modelBuilder.Entity<DatabaseEngine>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__database__3213E83F8526E221");
entity.ToTable("database_engines");
entity.HasIndex(e => e.CustomerId, "UNIQ_1D94CC5C9395C3F3")
.IsUnique()
.HasFilter("([customer_id] IS NOT NULL)");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.CustomerId).HasColumnName("customer_id");
entity.Property(e => e.ProductEdition)
.HasMaxLength(255)
.HasColumnName("product_edition");
entity.Property(e => e.ProductLevel)
.HasMaxLength(255)
.HasColumnName("product_level");
entity.Property(e => e.ProductVersion)
.HasMaxLength(255)
.HasColumnName("product_version");
entity.Property(e => e.ProductVersionName)
.HasMaxLength(255)
.HasColumnName("product_version_name");
entity.HasOne(d => d.Customer).WithOne(p => p.DatabaseEngine)
.HasForeignKey<DatabaseEngine>(d => d.CustomerId)
.HasConstraintName("FK_1D94CC5C9395C3F3");
});
modelBuilder.Entity<DatabasesInfo>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__database__3213E83FC619B4B0");
entity.ToTable("databases_info");
entity.HasIndex(e => e.DatabaseEngineId, "IDX_99DAF4F8AB25983");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.DatabaseEngineId).HasColumnName("database_engine_id");
entity.Property(e => e.LogicalName)
.HasMaxLength(255)
.HasColumnName("logical_name");
entity.Property(e => e.MaxSizeMb).HasColumnName("max_size_mb");
entity.Property(e => e.Name)
.HasMaxLength(255)
.HasColumnName("name");
entity.Property(e => e.SizeMb).HasColumnName("size_mb");
entity.HasOne(d => d.DatabaseEngine).WithMany(p => p.DatabasesInfos)
.HasForeignKey(d => d.DatabaseEngineId)
.HasConstraintName("FK_99DAF4F8AB25983");
});
modelBuilder.Entity<Device>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__devices__3213E83F185C7B65");
entity.ToTable("devices");
entity.HasIndex(e => e.CustomerId, "IDX_11074E9A9395C3F3");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.CustomerId).HasColumnName("customer_id");
entity.Property(e => e.Info)
.IsUnicode(false)
.HasComment("(DC2Type:simple_array)")
.HasColumnName("info");
entity.Property(e => e.Ip)
.HasMaxLength(255)
.HasColumnName("ip");
entity.Property(e => e.Port).HasColumnName("port");
entity.HasOne(d => d.Customer).WithMany(p => p.Devices)
.HasForeignKey(d => d.CustomerId)
.HasConstraintName("FK_11074E9A9395C3F3");
});
modelBuilder.Entity<Installation>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__installa__3213E83F11ECD973");
entity.ToTable("installations");
entity.HasIndex(e => e.ServerId, "IDX_A774F67B1844E6B7");
entity.HasIndex(e => e.DeviceId, "IDX_A774F67B94A4C7D4");
entity.HasIndex(e => e.ReleaseId, "IDX_A774F67BB12A727D");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.DeviceId).HasColumnName("device_id");
entity.Property(e => e.InstallDate)
.HasPrecision(6)
.HasColumnName("install_date");
entity.Property(e => e.LastUpdate)
.HasPrecision(6)
.HasColumnName("last_update");
entity.Property(e => e.Notes)
.HasMaxLength(255)
.HasColumnName("notes");
entity.Property(e => e.Options)
.IsUnicode(false)
.HasComment("(DC2Type:simple_array)")
.HasColumnName("options");
entity.Property(e => e.ReleaseId).HasColumnName("release_id");
entity.Property(e => e.ServerId).HasColumnName("server_id");
entity.HasOne(d => d.Device).WithMany(p => p.Installations)
.HasForeignKey(d => d.DeviceId)
.HasConstraintName("FK_A774F67B94A4C7D4");
entity.HasOne(d => d.Release).WithMany(p => p.Installations)
.HasForeignKey(d => d.ReleaseId)
.HasConstraintName("FK_A774F67BB12A727D");
entity.HasOne(d => d.Server).WithMany(p => p.Installations)
.HasForeignKey(d => d.ServerId)
.HasConstraintName("FK_A774F67B1844E6B7");
});
modelBuilder.Entity<PvmsInfo>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__pvms_inf__3213E83F8EA1FE36");
entity.ToTable("pvms_info");
entity.HasIndex(e => e.CustomerId, "IDX_4BCCAB779395C3F3");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.CustomerId).HasColumnName("customer_id");
entity.Property(e => e.DefaultCharset)
.HasMaxLength(255)
.HasColumnName("default_charset");
entity.Property(e => e.Imagick)
.HasMaxLength(255)
.HasColumnName("imagick");
entity.Property(e => e.MagicQuotesGpc).HasColumnName("magic_quotes_gpc");
entity.Property(e => e.MaxExecutionTime).HasColumnName("max_execution_time");
entity.Property(e => e.MaxInputVars).HasColumnName("max_input_vars");
entity.Property(e => e.MemoryLimit)
.HasMaxLength(255)
.HasColumnName("memory_limit");
entity.Property(e => e.PhpVersion)
.HasMaxLength(255)
.HasColumnName("php_version");
entity.Property(e => e.PostMaxSize)
.HasMaxLength(255)
.HasColumnName("post_max_size");
entity.Property(e => e.SodiumMissing).HasColumnName("sodium_missing");
entity.Property(e => e.Timezone)
.HasMaxLength(255)
.HasColumnName("timezone");
entity.Property(e => e.UploadMaxSize)
.HasMaxLength(255)
.HasColumnName("upload_max_size");
entity.HasOne(d => d.Customer).WithMany(p => p.PvmsInfos)
.HasForeignKey(d => d.CustomerId)
.HasConstraintName("FK_4BCCAB779395C3F3");
});
modelBuilder.Entity<Release>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__releases__3213E83F59E90B83");
entity.ToTable("releases");
entity.HasIndex(e => e.ServiceId, "IDX_7896E4D1ED5CA9E6");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Changelog)
.IsUnicode(false)
.HasColumnName("changelog");
entity.Property(e => e.ReleaseDate)
.HasPrecision(6)
.HasColumnName("release_date");
entity.Property(e => e.ServiceId).HasColumnName("service_id");
entity.Property(e => e.Version)
.HasMaxLength(255)
.HasColumnName("version");
entity.HasOne(d => d.Service).WithMany(p => p.Releases)
.HasForeignKey(d => d.ServiceId)
.HasConstraintName("FK_7896E4D1ED5CA9E6");
});
modelBuilder.Entity<SalvataggiSoap>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__salvatag__3213E83F51C2D8BF");
entity.ToTable("salvataggi_soap");
entity.HasIndex(e => e.ApplicationInfoId, "IDX_BC9B16D5B635C4CB");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.ApplicationInfoId).HasColumnName("application_info_id");
entity.Property(e => e.Name)
.HasMaxLength(255)
.HasColumnName("name");
entity.HasOne(d => d.ApplicationInfo).WithMany(p => p.SalvataggiSoaps)
.HasForeignKey(d => d.ApplicationInfoId)
.HasConstraintName("FK_BC9B16D5B635C4CB");
});
modelBuilder.Entity<Server>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__servers__3213E83F0E4B2C74");
entity.ToTable("servers");
entity.HasIndex(e => e.CustomerId, "IDX_4F8AF5F79395C3F3");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.CreatedAt)
.HasPrecision(6)
.HasColumnName("created_at");
entity.Property(e => e.CustomerId).HasColumnName("customer_id");
entity.Property(e => e.Info)
.IsUnicode(false)
.HasComment("(DC2Type:simple_array)")
.HasColumnName("info");
entity.Property(e => e.Ip)
.HasMaxLength(255)
.HasColumnName("ip");
entity.Property(e => e.JavaVersion)
.HasMaxLength(255)
.HasColumnName("java_version");
entity.Property(e => e.LastUpdate)
.HasPrecision(6)
.HasColumnName("last_update");
entity.Property(e => e.MaxPermSize)
.HasMaxLength(255)
.HasColumnName("max_perm_size");
entity.Property(e => e.OsArch)
.HasMaxLength(255)
.HasColumnName("os_arch");
entity.Property(e => e.OsName)
.HasMaxLength(255)
.HasColumnName("os_name");
entity.Property(e => e.Port).HasColumnName("port");
entity.Property(e => e.RemoteAddr)
.HasMaxLength(255)
.HasColumnName("remote_addr");
entity.Property(e => e.UpdatedAt)
.HasPrecision(6)
.HasColumnName("updated_at");
entity.Property(e => e.Xms)
.HasMaxLength(255)
.HasColumnName("xms");
entity.Property(e => e.Xmx)
.HasMaxLength(255)
.HasColumnName("xmx");
entity.HasOne(d => d.Customer).WithMany(p => p.Servers)
.HasForeignKey(d => d.CustomerId)
.HasConstraintName("FK_4F8AF5F79395C3F3");
});
modelBuilder.Entity<Service>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__services__3213E83FB43C1FE2");
entity.ToTable("services");
entity.HasIndex(e => e.Slug, "UNIQ_7332E169989D9B62")
.IsUnique()
.HasFilter("([slug] IS NOT NULL)");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Description)
.IsUnicode(false)
.HasColumnName("description");
entity.Property(e => e.InsertDate)
.HasPrecision(6)
.HasColumnName("insert_date");
entity.Property(e => e.Language)
.HasMaxLength(255)
.HasColumnName("language");
entity.Property(e => e.Name)
.HasMaxLength(255)
.HasColumnName("name");
entity.Property(e => e.Slug)
.HasMaxLength(191)
.HasColumnName("slug");
});
modelBuilder.Entity<VwServerLastUpdate>(entity =>
{
entity
.HasNoKey()
.ToView("vw_server_last_update");
entity.Property(e => e.CreatedAt)
.HasPrecision(6)
.HasColumnName("created_at");
entity.Property(e => e.CustomerId).HasColumnName("customer_id");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Info)
.IsUnicode(false)
.HasColumnName("info");
entity.Property(e => e.Ip)
.HasMaxLength(255)
.HasColumnName("ip");
entity.Property(e => e.JavaVersion)
.HasMaxLength(255)
.HasColumnName("java_version");
entity.Property(e => e.LastUpdate)
.HasPrecision(6)
.HasColumnName("last_update");
entity.Property(e => e.MaxPermSize)
.HasMaxLength(255)
.HasColumnName("max_perm_size");
entity.Property(e => e.OsArch)
.HasMaxLength(255)
.HasColumnName("os_arch");
entity.Property(e => e.OsName)
.HasMaxLength(255)
.HasColumnName("os_name");
entity.Property(e => e.Port).HasColumnName("port");
entity.Property(e => e.RemoteAddr)
.HasMaxLength(255)
.HasColumnName("remote_addr");
entity.Property(e => e.UpdatedAt)
.HasPrecision(6)
.HasColumnName("updated_at");
entity.Property(e => e.Xms)
.HasMaxLength(255)
.HasColumnName("xms");
entity.Property(e => e.Xmx)
.HasMaxLength(255)
.HasColumnName("xmx");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@@ -0,0 +1,303 @@
// <auto-generated />
using System;
using IntegryControlPanel.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace IntegryControlPanel.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250923102919_AddLdapUserProperties")]
partial class AddLdapUserProperties
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("IntegryControlPanel.Data.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Department")
.HasColumnType("nvarchar(max)");
b.Property<string>("DisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FirstName")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsLdapUser")
.HasColumnType("bit");
b.Property<DateTime?>("LastLdapSync")
.HasColumnType("datetime2");
b.Property<string>("LastName")
.HasColumnType("nvarchar(max)");
b.Property<string>("LdapUsername")
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("IntegryControlPanel.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("IntegryControlPanel.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IntegryControlPanel.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("IntegryControlPanel.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IntegryControlPanel.Migrations
{
/// <inheritdoc />
public partial class AddLdapUserProperties : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Department",
table: "AspNetUsers",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DisplayName",
table: "AspNetUsers",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "FirstName",
table: "AspNetUsers",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "IsLdapUser",
table: "AspNetUsers",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<DateTime>(
name: "LastLdapSync",
table: "AspNetUsers",
type: "datetime2",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LastName",
table: "AspNetUsers",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LdapUsername",
table: "AspNetUsers",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Title",
table: "AspNetUsers",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Department",
table: "AspNetUsers");
migrationBuilder.DropColumn(
name: "DisplayName",
table: "AspNetUsers");
migrationBuilder.DropColumn(
name: "FirstName",
table: "AspNetUsers");
migrationBuilder.DropColumn(
name: "IsLdapUser",
table: "AspNetUsers");
migrationBuilder.DropColumn(
name: "LastLdapSync",
table: "AspNetUsers");
migrationBuilder.DropColumn(
name: "LastName",
table: "AspNetUsers");
migrationBuilder.DropColumn(
name: "LdapUsername",
table: "AspNetUsers");
migrationBuilder.DropColumn(
name: "Title",
table: "AspNetUsers");
}
}
}

View File

@@ -1,4 +1,4 @@
// <auto-generated />
// <auto-generated />
using System;
using IntegryControlPanel.Data;
using Microsoft.EntityFrameworkCore;
@@ -17,7 +17,7 @@ namespace IntegryControlPanel.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@@ -34,6 +34,12 @@ namespace IntegryControlPanel.Migrations
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Department")
.HasColumnType("nvarchar(max)");
b.Property<string>("DisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
@@ -41,6 +47,21 @@ namespace IntegryControlPanel.Migrations
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FirstName")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsLdapUser")
.HasColumnType("bit");
b.Property<DateTime?>("LastLdapSync")
.HasColumnType("datetime2");
b.Property<string>("LastName")
.HasColumnType("nvarchar(max)");
b.Property<string>("LdapUsername")
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
@@ -67,6 +88,9 @@ namespace IntegryControlPanel.Migrations
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");

View File

@@ -9,6 +9,11 @@
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.DirectoryServices.Protocols" Version="9.0.9" />
<ProjectReference Include="..\IntegryControlPanel.Client\IntegryControlPanel.Client.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.*" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.*" />

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class ApplicationInfo
{
public int Id { get; set; }
public int? CustomerId { get; set; }
public string Name { get; set; } = null!;
public bool NewUpdProgMaga { get; set; }
public string? MenuPersonalizzato { get; set; }
public int? AnnoMagaz { get; set; }
public int? AnnoContab { get; set; }
public bool? AnsiPadding { get; set; }
public bool? DelimitedIdentifier { get; set; }
public bool? ConcatNullYieldsNull { get; set; }
public virtual Customer? Customer { get; set; }
public virtual ICollection<SalvataggiSoap> SalvataggiSoaps { get; set; } = new List<SalvataggiSoap>();
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class Client
{
public int Id { get; set; }
public string NomeAzienda { get; set; } = null!;
public string DeviceId { get; set; } = null!;
public DateTime? LastUpdate { get; set; }
public DateTime InsertDate { get; set; }
public string? RemoteAddr { get; set; }
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class Configurazioni
{
public int Id { get; set; }
public string NomeAzienda { get; set; } = null!;
public string? JavaVersion { get; set; }
public string? OsArch { get; set; }
public string? OsName { get; set; }
public string? Xmx { get; set; }
public string? Xms { get; set; }
public string? MaxPermSize { get; set; }
public DateTime? LastUpdate { get; set; }
public string? RemoteAddr { get; set; }
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class Customer
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public string Slug { get; set; } = null!;
public bool? Active { get; set; }
public string? PartitaIva { get; set; }
public virtual ICollection<ApplicationInfo> ApplicationInfos { get; set; } = new List<ApplicationInfo>();
public virtual DatabaseEngine? DatabaseEngine { get; set; }
public virtual ICollection<Device> Devices { get; set; } = new List<Device>();
public virtual ICollection<PvmsInfo> PvmsInfos { get; set; } = new List<PvmsInfo>();
public virtual ICollection<Server> Servers { get; set; } = new List<Server>();
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class DatabaseEngine
{
public int Id { get; set; }
public int? CustomerId { get; set; }
public string ProductVersion { get; set; } = null!;
public string ProductVersionName { get; set; } = null!;
public string ProductLevel { get; set; } = null!;
public string ProductEdition { get; set; } = null!;
public virtual Customer? Customer { get; set; }
public virtual ICollection<DatabasesInfo> DatabasesInfos { get; set; } = new List<DatabasesInfo>();
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class DatabasesInfo
{
public int Id { get; set; }
public int? DatabaseEngineId { get; set; }
public string Name { get; set; } = null!;
public string LogicalName { get; set; } = null!;
public int SizeMb { get; set; }
public int MaxSizeMb { get; set; }
public virtual DatabaseEngine? DatabaseEngine { get; set; }
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class Device
{
public int Id { get; set; }
public int? CustomerId { get; set; }
public string Ip { get; set; } = null!;
public int Port { get; set; }
/// <summary>
/// (DC2Type:simple_array)
/// </summary>
public string Info { get; set; } = null!;
public virtual Customer? Customer { get; set; }
public virtual ICollection<Installation> Installations { get; set; } = new List<Installation>();
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class Installation
{
public int Id { get; set; }
public int? ReleaseId { get; set; }
public int? ServerId { get; set; }
public int? DeviceId { get; set; }
public string? Notes { get; set; }
/// <summary>
/// (DC2Type:simple_array)
/// </summary>
public string Options { get; set; } = null!;
public DateTime InstallDate { get; set; }
public DateTime LastUpdate { get; set; }
public virtual Device? Device { get; set; }
public virtual Release? Release { get; set; }
public virtual Server? Server { get; set; }
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class PvmsInfo
{
public int Id { get; set; }
public int? CustomerId { get; set; }
public string PhpVersion { get; set; } = null!;
public string Timezone { get; set; } = null!;
public string Imagick { get; set; } = null!;
public bool SodiumMissing { get; set; }
public int MaxExecutionTime { get; set; }
public bool MagicQuotesGpc { get; set; }
public string DefaultCharset { get; set; } = null!;
public string MemoryLimit { get; set; } = null!;
public string PostMaxSize { get; set; } = null!;
public string UploadMaxSize { get; set; } = null!;
public int MaxInputVars { get; set; }
public virtual Customer? Customer { get; set; }
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class Release
{
public int Id { get; set; }
public int? ServiceId { get; set; }
public string Version { get; set; } = null!;
public string? Changelog { get; set; }
public DateTime ReleaseDate { get; set; }
public virtual ICollection<Installation> Installations { get; set; } = new List<Installation>();
public virtual Service? Service { get; set; }
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class SalvataggiSoap
{
public int Id { get; set; }
public int? ApplicationInfoId { get; set; }
public string Name { get; set; } = null!;
public virtual ApplicationInfo? ApplicationInfo { get; set; }
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class Server
{
public int Id { get; set; }
public int? CustomerId { get; set; }
public string? Ip { get; set; }
public int? Port { get; set; }
/// <summary>
/// (DC2Type:simple_array)
/// </summary>
public string? Info { get; set; }
public string? JavaVersion { get; set; }
public string? OsArch { get; set; }
public string? OsName { get; set; }
public string? Xmx { get; set; }
public string? Xms { get; set; }
public string? MaxPermSize { get; set; }
public DateTime? LastUpdate { get; set; }
public string? RemoteAddr { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public virtual Customer? Customer { get; set; }
public virtual ICollection<Installation> Installations { get; set; } = new List<Installation>();
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class Service
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public string Slug { get; set; } = null!;
public string? Language { get; set; }
public string? Description { get; set; }
public DateTime InsertDate { get; set; }
public virtual ICollection<Release> Releases { get; set; } = new List<Release>();
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
namespace IntegryControlPanel.Models.IntegryControlPanel;
public partial class VwServerLastUpdate
{
public int Id { get; set; }
public int? CustomerId { get; set; }
public string? Ip { get; set; }
public int? Port { get; set; }
public string? Info { get; set; }
public string? JavaVersion { get; set; }
public string? OsArch { get; set; }
public string? OsName { get; set; }
public string? Xmx { get; set; }
public string? Xms { get; set; }
public string? MaxPermSize { get; set; }
public DateTime? LastUpdate { get; set; }
public string? RemoteAddr { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}

View File

@@ -0,0 +1,15 @@
namespace IntegryControlPanel.Models
{
public class LdapUser
{
public string Username { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public List<string> Groups { get; set; } = new();
public string? Department { get; set; }
public string? Title { get; set; }
public string? PhoneNumber { get; set; }
}
}

View File

@@ -2,6 +2,7 @@ using IntegryControlPanel.Client.Pages;
using IntegryControlPanel.Components;
using IntegryControlPanel.Components.Account;
using IntegryControlPanel.Data;
using IntegryControlPanel.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using MudBlazor.Services;
@@ -16,6 +17,9 @@ builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents()
.AddAuthenticationStateSerialization();
// Add controllers
builder.Services.AddControllers();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
@@ -35,9 +39,13 @@ builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddSignInManager<LdapSignInManager>()
.AddDefaultTokenProviders();
// Registra i servizi LDAP
builder.Services.AddScoped<ILdapService, LdapService>();
builder.Services.AddScoped<LdapUserManager>();
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
var app = builder.Build();
@@ -57,7 +65,6 @@ else
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
@@ -65,6 +72,9 @@ app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(IntegryControlPanel.Client._Imports).Assembly);
// Map controllers
app.MapControllers();
// Add additional endpoints required by the Identity /Account Razor components.
app.MapAdditionalIdentityEndpoints();

View File

@@ -0,0 +1,11 @@
using IntegryControlPanel.Models;
namespace IntegryControlPanel.Services
{
public interface ILdapService
{
Task<LdapUser?> AuthenticateAsync(string username, string password);
Task<LdapUser?> GetUserAsync(string username);
Task<IEnumerable<LdapUser>> GetUsersInGroupAsync(string groupName);
}
}

View File

@@ -0,0 +1,193 @@
using IntegryControlPanel.Models;
using IntegryControlPanel.Services;
using System.DirectoryServices.Protocols;
using System.Net;
using System.Text;
namespace IntegryControlPanel.Services
{
public class LdapService : ILdapService
{
private readonly IConfiguration _configuration;
private readonly ILogger<LdapService> _logger;
private string LdapServer => _configuration["LDAP:Server"] ?? throw new InvalidOperationException("LDAP Server not configured");
private int LdapPort => int.Parse(_configuration["LDAP:Port"] ?? "389");
private string BaseDn => _configuration["LDAP:BaseDN"] ?? throw new InvalidOperationException("LDAP BaseDN not configured");
private string ServiceAccountDn => _configuration["LDAP:ServiceAccountDN"] ?? throw new InvalidOperationException("LDAP ServiceAccountDN not configured");
private string ServiceAccountPassword => _configuration["LDAP:ServiceAccountPassword"] ?? throw new InvalidOperationException("LDAP ServiceAccountPassword not configured");
private bool UseSSL => bool.Parse(_configuration["LDAP:UseSSL"] ?? "false");
public LdapService(IConfiguration configuration, ILogger<LdapService> logger)
{
_configuration = configuration;
_logger = logger;
}
public async Task<LdapUser?> AuthenticateAsync(string username, string password)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
return null;
try
{
using var connection = new LdapConnection(new LdapDirectoryIdentifier(LdapServer, LdapPort));
if (UseSSL)
{
connection.SessionOptions.SecureSocketLayer = true;
}
// Prova prima a fare bind con le credenziali utente per autenticare
string userDn = $"cn={username},{BaseDn}";
connection.Credential = new NetworkCredential(userDn, password);
connection.AuthType = AuthType.Basic;
await Task.Run(() => connection.Bind());
// Se l'autenticazione ha successo, ottieni le informazioni dell'utente
var user = await GetUserInfoAsync(connection, username);
return user;
}
catch (LdapException ex)
{
_logger.LogWarning("LDAP authentication failed for user {Username}: {Message}", username, ex.Message);
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during LDAP authentication for user {Username}", username);
return null;
}
}
public async Task<LdapUser?> GetUserAsync(string username)
{
try
{
using var connection = await CreateServiceConnectionAsync();
return await GetUserInfoAsync(connection, username);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting user {Username} from LDAP", username);
return null;
}
}
public async Task<IEnumerable<LdapUser>> GetUsersInGroupAsync(string groupName)
{
try
{
using var connection = await CreateServiceConnectionAsync();
var searchFilter = $"(&(objectClass=person)(memberOf=cn={groupName},{BaseDn}))";
var searchRequest = new SearchRequest(BaseDn, searchFilter, SearchScope.Subtree);
searchRequest.Attributes.AddRange(new[] { "cn", "mail", "givenName", "sn", "displayName", "memberOf", "department", "title", "telephoneNumber" });
var response = (SearchResponse)await Task.Run(() => connection.SendRequest(searchRequest));
var users = new List<LdapUser>();
foreach (SearchResultEntry entry in response.Entries)
{
var user = MapLdapEntryToUser(entry);
if (user != null)
users.Add(user);
}
return users;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting users in group {GroupName} from LDAP", groupName);
return Enumerable.Empty<LdapUser>();
}
}
private async Task<LdapConnection> CreateServiceConnectionAsync()
{
var connection = new LdapConnection(new LdapDirectoryIdentifier(LdapServer, LdapPort));
if (UseSSL)
{
connection.SessionOptions.SecureSocketLayer = true;
}
connection.Credential = new NetworkCredential(ServiceAccountDn, ServiceAccountPassword);
connection.AuthType = AuthType.Basic;
await Task.Run(() => connection.Bind());
return connection;
}
private async Task<LdapUser?> GetUserInfoAsync(LdapConnection connection, string username)
{
var searchFilter = $"(&(objectClass=person)(cn={username}))";
var searchRequest = new SearchRequest(BaseDn, searchFilter, SearchScope.Subtree);
searchRequest.Attributes.AddRange(new[] { "cn", "mail", "givenName", "sn", "displayName", "memberOf", "department", "title", "telephoneNumber" });
var response = (SearchResponse)await Task.Run(() => connection.SendRequest(searchRequest));
if (response.Entries.Count == 0)
return null;
return MapLdapEntryToUser(response.Entries[0]);
}
private LdapUser? MapLdapEntryToUser(SearchResultEntry entry)
{
try
{
var user = new LdapUser
{
Username = GetAttributeValue(entry, "cn") ?? string.Empty,
Email = GetAttributeValue(entry, "mail") ?? string.Empty,
FirstName = GetAttributeValue(entry, "givenName") ?? string.Empty,
LastName = GetAttributeValue(entry, "sn") ?? string.Empty,
DisplayName = GetAttributeValue(entry, "displayName") ?? string.Empty,
Department = GetAttributeValue(entry, "department"),
Title = GetAttributeValue(entry, "title"),
PhoneNumber = GetAttributeValue(entry, "telephoneNumber")
};
// Estrai i gruppi
if (entry.Attributes.Contains("memberOf"))
{
foreach (var group in entry.Attributes["memberOf"].GetValues(typeof(string)).Cast<string>())
{
// Estrai solo il nome del gruppo dal DN
var groupName = ExtractGroupNameFromDn(group);
if (!string.IsNullOrEmpty(groupName))
user.Groups.Add(groupName);
}
}
return user;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error mapping LDAP entry to user");
return null;
}
}
private string? GetAttributeValue(SearchResultEntry entry, string attributeName)
{
if (!entry.Attributes.Contains(attributeName))
return null;
var values = entry.Attributes[attributeName].GetValues(typeof(string));
return values.Length > 0 ? values[0] as string : null;
}
private string ExtractGroupNameFromDn(string groupDn)
{
// Estrai il nome del gruppo da un DN come "cn=GroupName,ou=Groups,dc=domain,dc=com"
var parts = groupDn.Split(',');
var cnPart = parts.FirstOrDefault(p => p.Trim().StartsWith("cn=", StringComparison.OrdinalIgnoreCase));
return cnPart?.Substring(3) ?? string.Empty;
}
}
}

View File

@@ -0,0 +1,81 @@
using IntegryControlPanel.Data;
using IntegryControlPanel.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
namespace IntegryControlPanel.Services
{
public class LdapSignInManager : SignInManager<ApplicationUser>
{
private readonly LdapUserManager _ldapUserManager;
private readonly ILogger<LdapSignInManager> _logger;
public LdapSignInManager(
UserManager<ApplicationUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<ApplicationUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<ApplicationUser> confirmation,
LdapUserManager ldapUserManager,
ILogger<LdapSignInManager> ldapLogger)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
_ldapUserManager = ldapUserManager;
_logger = ldapLogger;
}
public override async Task<SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
{
try
{
// Prima tenta l'autenticazione LDAP
var ldapUser = await _ldapUserManager.AuthenticateAsync(userName, password);
if (ldapUser != null)
{
_logger.LogInformation("LDAP authentication successful for user {Username}", userName);
// Accedi all'utente
await SignInAsync(ldapUser, isPersistent);
return SignInResult.Success;
}
_logger.LogWarning("LDAP authentication failed for user {Username}, trying local authentication", userName);
// Se l'autenticazione LDAP fallisce, prova con l'autenticazione locale
return await base.PasswordSignInAsync(userName, password, isPersistent, lockoutOnFailure);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during sign in for user {Username}", userName);
return SignInResult.Failed;
}
}
public async Task<SignInResult> LdapSignInAsync(string userName, string password, bool isPersistent)
{
try
{
var ldapUser = await _ldapUserManager.AuthenticateAsync(userName, password);
if (ldapUser == null)
{
_logger.LogWarning("LDAP authentication failed for user {Username}", userName);
return SignInResult.Failed;
}
await SignInAsync(ldapUser, isPersistent);
_logger.LogInformation("LDAP sign in successful for user {Username}", userName);
return SignInResult.Success;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during LDAP sign in for user {Username}", userName);
return SignInResult.Failed;
}
}
}
}

View File

@@ -0,0 +1,228 @@
using IntegryControlPanel.Data;
using IntegryControlPanel.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace IntegryControlPanel.Services
{
public class LdapUserManager : IDisposable
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILdapService _ldapService;
private readonly ILogger<LdapUserManager> _logger;
public LdapUserManager(
UserManager<ApplicationUser> userManager,
ILdapService ldapService,
ILogger<LdapUserManager> logger)
{
_userManager = userManager;
_ldapService = ldapService;
_logger = logger;
}
public async Task<ApplicationUser?> AuthenticateAsync(string username, string password)
{
try
{
// Prima tenta l'autenticazione LDAP
var ldapUser = await _ldapService.AuthenticateAsync(username, password);
if (ldapUser == null)
{
_logger.LogWarning("LDAP authentication failed for user {Username}", username);
return null;
}
// Cerca l'utente esistente nel database locale
var existingUser = await _userManager.Users
.FirstOrDefaultAsync(u => u.LdapUsername == username || u.UserName == username);
if (existingUser != null)
{
// Aggiorna le informazioni dell'utente esistente
await UpdateUserFromLdapAsync(existingUser, ldapUser);
return existingUser;
}
// Crea un nuovo utente se non esiste
var newUser = await CreateUserFromLdapAsync(ldapUser);
return newUser;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during LDAP authentication for user {Username}", username);
return null;
}
}
public async Task<ApplicationUser?> SyncUserFromLdapAsync(string username)
{
try
{
var ldapUser = await _ldapService.GetUserAsync(username);
if (ldapUser == null)
return null;
var existingUser = await _userManager.Users
.FirstOrDefaultAsync(u => u.LdapUsername == username || u.UserName == username);
if (existingUser != null)
{
await UpdateUserFromLdapAsync(existingUser, ldapUser);
return existingUser;
}
return await CreateUserFromLdapAsync(ldapUser);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error syncing user {Username} from LDAP", username);
return null;
}
}
private async Task<ApplicationUser> CreateUserFromLdapAsync(Models.LdapUser ldapUser)
{
var user = new ApplicationUser
{
UserName = ldapUser.Username,
Email = ldapUser.Email,
EmailConfirmed = true, // Gli utenti LDAP sono considerati verificati
FirstName = ldapUser.FirstName,
LastName = ldapUser.LastName,
DisplayName = ldapUser.DisplayName,
Department = ldapUser.Department,
Title = ldapUser.Title,
PhoneNumber = ldapUser.PhoneNumber,
IsLdapUser = true,
LdapUsername = ldapUser.Username,
LastLdapSync = DateTime.UtcNow
};
var result = await _userManager.CreateAsync(user);
if (!result.Succeeded)
{
var errors = string.Join(", ", result.Errors.Select(e => e.Description));
throw new InvalidOperationException($"Failed to create user: {errors}");
}
// Aggiungi ruoli basati sui gruppi LDAP se necessario
await AssignRolesFromLdapGroupsAsync(user, ldapUser.Groups);
_logger.LogInformation("Created new user {Username} from LDAP", ldapUser.Username);
return user;
}
private async Task UpdateUserFromLdapAsync(ApplicationUser user, Models.LdapUser ldapUser)
{
bool hasChanges = false;
if (user.Email != ldapUser.Email)
{
user.Email = ldapUser.Email;
hasChanges = true;
}
if (user.FirstName != ldapUser.FirstName)
{
user.FirstName = ldapUser.FirstName;
hasChanges = true;
}
if (user.LastName != ldapUser.LastName)
{
user.LastName = ldapUser.LastName;
hasChanges = true;
}
if (user.DisplayName != ldapUser.DisplayName)
{
user.DisplayName = ldapUser.DisplayName;
hasChanges = true;
}
if (user.Department != ldapUser.Department)
{
user.Department = ldapUser.Department;
hasChanges = true;
}
if (user.Title != ldapUser.Title)
{
user.Title = ldapUser.Title;
hasChanges = true;
}
if (user.PhoneNumber != ldapUser.PhoneNumber)
{
user.PhoneNumber = ldapUser.PhoneNumber;
hasChanges = true;
}
user.LastLdapSync = DateTime.UtcNow;
user.IsLdapUser = true;
user.LdapUsername = ldapUser.Username;
if (hasChanges)
{
var result = await _userManager.UpdateAsync(user);
if (!result.Succeeded)
{
var errors = string.Join(", ", result.Errors.Select(e => e.Description));
_logger.LogError("Failed to update user {Username}: {Errors}", user.UserName, errors);
}
else
{
_logger.LogInformation("Updated user {Username} from LDAP", user.UserName);
}
}
// Aggiorna ruoli basati sui gruppi LDAP
await AssignRolesFromLdapGroupsAsync(user, ldapUser.Groups);
}
private async Task AssignRolesFromLdapGroupsAsync(ApplicationUser user, List<string> ldapGroups)
{
// Implementa la logica di mappatura tra gruppi LDAP e ruoli dell'applicazione
// Esempio di mappatura:
var roleMapping = new Dictionary<string, string>
{
{ "Administrators", "Admin" },
{ "IT-Support", "ITSupport" },
{ "Users", "User" }
};
var currentRoles = await _userManager.GetRolesAsync(user);
var newRoles = new List<string>();
foreach (var ldapGroup in ldapGroups)
{
if (roleMapping.ContainsKey(ldapGroup))
{
newRoles.Add(roleMapping[ldapGroup]);
}
}
// Rimuovi ruoli che non dovrebbero pi<70> essere assegnati
var rolesToRemove = currentRoles.Except(newRoles).ToList();
if (rolesToRemove.Any())
{
await _userManager.RemoveFromRolesAsync(user, rolesToRemove);
_logger.LogInformation("Removed roles {Roles} from user {Username}", string.Join(", ", rolesToRemove), user.UserName);
}
// Aggiungi nuovi ruoli
var rolesToAdd = newRoles.Except(currentRoles).ToList();
if (rolesToAdd.Any())
{
await _userManager.AddToRolesAsync(user, rolesToAdd);
_logger.LogInformation("Added roles {Roles} to user {Username}", string.Join(", ", rolesToAdd), user.UserName);
}
}
public void Dispose()
{
// Non disporre di _userManager poich<63> <20> gestito dal DI container
}
}
}

View File

@@ -1,8 +1,17 @@
{
"LDAP": {
"Server": "192.168.2.208",
"Port": "389",
"BaseDN": "OU=Azure AD,DC=studio-ml,DC=local",
"ServiceAccountDN": "CN=Administrator,CN=Users,DC=studio-ml,DC=local",
"ServiceAccountPassword": "inpmiy",
"UseSSL": "false"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
"Microsoft.AspNetCore": "Warning",
"IntegryControlPanel.Services": "Debug"
}
}
}

View File

@@ -1,11 +1,21 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-IntegryControlPanel-c1120793-85cd-450e-893b-978f0af6aeab;Trusted_Connection=True;MultipleActiveResultSets=true"
"DefaultConnection": "Server=SERVERDB2019;Database=integry_control_panel_test;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"LDAP": {
"Server": "your-domain-controller.domain.com",
"Port": "389",
"BaseDN": "DC=domain,DC=com",
"ServiceAccountDN": "CN=ServiceAccount,CN=Users,DC=domain,DC=com",
"ServiceAccountPassword": "your-service-account-password",
"UseSSL": "false"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
"Microsoft.AspNetCore": "Warning",
"IntegryControlPanel.Services": "Information"
}
},
"AllowedHosts": "*"