feat: add user management, refs NOISSUE
This commit is contained in:
		@@ -10,7 +10,8 @@ public class ConfigurationAD
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public string LDAPserver { get; set; } = string.Empty;
 | 
					    public string LDAPserver { get; set; } = string.Empty;
 | 
				
			||||||
    public string LDAPQueryBase { get; set; } = string.Empty;
 | 
					    public string LDAPQueryBase { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					    public string LDAPUserQueryBase { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public string Crew { get; set; } = string.Empty;
 | 
					    public string CrewGroup { get; set; } = string.Empty;
 | 
				
			||||||
    public string Managers { get; set; } = string.Empty;
 | 
					    public string ManagerGroup { get; set; } = string.Empty;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										4
									
								
								song_of_the_day/Auth/IAuthenticationService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								song_of_the_day/Auth/IAuthenticationService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					public interface IAuthenticationService  
 | 
				
			||||||
 | 
					{  
 | 
				
			||||||
 | 
					    bool Authenticate(string userName, string password);  
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								song_of_the_day/Auth/LdapAuthenticationService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								song_of_the_day/Auth/LdapAuthenticationService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					public class LdapAuthenticationService : IAuthenticationService
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private readonly IConfiguration _configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public LdapAuthenticationService(IConfiguration configuration)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _configuration = configuration;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public bool Authenticate(string username, string password)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var ldapInstance = LdapIntegration.Instance;
 | 
				
			||||||
 | 
					        return ldapInstance.TestLogin(username, password);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								song_of_the_day/Auth/PhoneClaimCodeProviderService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								song_of_the_day/Auth/PhoneClaimCodeProviderService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					public class PhoneClaimCodeProviderService
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private Dictionary<string, string> _phoneClaimCodes;
 | 
				
			||||||
 | 
					    private Dictionary<string, string> _phoneClaimNumbers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public PhoneClaimCodeProviderService()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _phoneClaimCodes = new Dictionary<string, string>();
 | 
				
			||||||
 | 
					        _phoneClaimNumbers = new Dictionary<string, string>();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static Random random = new Random();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static string RandomString(int length)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 | 
				
			||||||
 | 
					        return new string(Enumerable.Repeat(chars, length)
 | 
				
			||||||
 | 
					            .Select(s => s[random.Next(s.Length)]).ToArray());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void GenerateClaimCodeForUserAndNumber(string username, string phoneNumber)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var generatedCode = string.Empty;
 | 
				
			||||||
 | 
					        if (IsCodeGeneratedForUser(username))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            generatedCode = _phoneClaimCodes[username];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            generatedCode = RandomString(6);
 | 
				
			||||||
 | 
					            _phoneClaimCodes[username] = generatedCode;
 | 
				
			||||||
 | 
					            _phoneClaimNumbers[username] = phoneNumber;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SignalIntegration.Instance.SendMessageToUserAsync("Your phone number validation code is: " + generatedCode, phoneNumber);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public string ValidateClaimCodeForUser(string code, string username)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var result = false;
 | 
				
			||||||
 | 
					        result = _phoneClaimCodes[username] == code;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (result)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _phoneClaimCodes.Remove(username);
 | 
				
			||||||
 | 
					            var number = _phoneClaimNumbers[username];
 | 
				
			||||||
 | 
					            _phoneClaimNumbers.Remove(username);
 | 
				
			||||||
 | 
					            return number;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return string.Empty;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public bool IsCodeGeneratedForUser(string username)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return _phoneClaimCodes.ContainsKey(username);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -16,22 +16,21 @@ public class AppConfiguration
 | 
				
			|||||||
        this.SignalGroupId = Environment.GetEnvironmentVariable("SIGNAL_GROUP_ID") ?? "group.Wmk1UTVQTnh0Sjd6a0xiOGhnTnMzZlNkc2p2Q3c0SXJiQkU2eDlNU0hyTT0=";
 | 
					        this.SignalGroupId = Environment.GetEnvironmentVariable("SIGNAL_GROUP_ID") ?? "group.Wmk1UTVQTnh0Sjd6a0xiOGhnTnMzZlNkc2p2Q3c0SXJiQkU2eDlNU0hyTT0=";
 | 
				
			||||||
        this.WebUIBaseURL = Environment.GetEnvironmentVariable("WEB_BASE_URL") ?? "https://sotd.disi.dev/";
 | 
					        this.WebUIBaseURL = Environment.GetEnvironmentVariable("WEB_BASE_URL") ?? "https://sotd.disi.dev/";
 | 
				
			||||||
        this.UseBotTag = bool.Parse(Environment.GetEnvironmentVariable("USE_BOT_TAG") ?? "true");
 | 
					        this.UseBotTag = bool.Parse(Environment.GetEnvironmentVariable("USE_BOT_TAG") ?? "true");
 | 
				
			||||||
 | 
					        this.AverageDaysBetweenRequests = int.Parse(Environment.GetEnvironmentVariable("AVERAGE_DAYS_BETWEEN_REQUESTS") ?? "2");
 | 
				
			||||||
        var managersGroupName = Environment.GetEnvironmentVariable("LDAP_ADMINGROUP") ?? "admins";
 | 
					        var managersGroupName = Environment.GetEnvironmentVariable("LDAP_ADMINGROUP") ?? "admins";
 | 
				
			||||||
        var userGroupName = Environment.GetEnvironmentVariable("LDAP_USERGROUP") ?? "everyone";
 | 
					        var userGroupName = Environment.GetEnvironmentVariable("LDAP_USERGROUP") ?? "everybody";
 | 
				
			||||||
        this.LDAPConfig = new ConfigurationAD() {
 | 
					        this.LDAPConfig = new ConfigurationAD() {
 | 
				
			||||||
            Username = Environment.GetEnvironmentVariable("LDAP_BIND") ?? "cn=admin.dc=disi,dc=dev",
 | 
					            Username = Environment.GetEnvironmentVariable("LDAP_BIND") ?? "cn=admin,dc=disi,dc=dev",
 | 
				
			||||||
            Password = Environment.GetEnvironmentVariable("LDAP_PASS") ?? "adminPass2022!",
 | 
					            Password = Environment.GetEnvironmentVariable("LDAP_PASS") ?? "adminPass2022!",
 | 
				
			||||||
            Port = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LDAP_BIND")) ? int.Parse(Environment.GetEnvironmentVariable("LDAP_BIND")) : 389,
 | 
					            Port = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LDAP_BIND")) ? int.Parse(Environment.GetEnvironmentVariable("LDAP_BIND")) : 389,
 | 
				
			||||||
            LDAPserver = Environment.GetEnvironmentVariable("LDAP_URL") ?? "192.168.1.108",
 | 
					            LDAPserver = Environment.GetEnvironmentVariable("LDAP_URL") ?? "192.168.1.108",
 | 
				
			||||||
            LDAPQueryBase = Environment.GetEnvironmentVariable("LDAP_BASE") ?? "dc=disi,dc=dev",
 | 
					            LDAPQueryBase = Environment.GetEnvironmentVariable("LDAP_BASE") ?? "dc=disi,dc=dev",
 | 
				
			||||||
            Crew = $"cn={userGroupName},ou=groups,dc=disi,dc=dev",
 | 
					            LDAPUserQueryBase = Environment.GetEnvironmentVariable("LDAP_BASE") ?? "ou=people,dc=disi,dc=dev",
 | 
				
			||||||
            Managers = $"cn={managersGroupName},ou=groups,dc=disi,dc=dev"
 | 
					            CrewGroup = $"cn={userGroupName},ou=groups,dc=disi,dc=dev",
 | 
				
			||||||
 | 
					            ManagerGroup = $"cn={managersGroupName},ou=groups,dc=disi,dc=dev"
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public string Crew { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
    public string Managers { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public string SignalAPIEndpointUri
 | 
					    public string SignalAPIEndpointUri
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        get; private set;
 | 
					        get; private set;
 | 
				
			||||||
@@ -87,6 +86,11 @@ public class AppConfiguration
 | 
				
			|||||||
        get; private set;
 | 
					        get; private set;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public int AverageDaysBetweenRequests
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        get; private set;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ConfigurationAD LDAPConfig {
 | 
					    public ConfigurationAD LDAPConfig {
 | 
				
			||||||
        get; private set;
 | 
					        get; private set;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										40
									
								
								song_of_the_day/Controllers/AuthController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								song_of_the_day/Controllers/AuthController.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using System.Security.Claims;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authentication.Cookies;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authentication;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class AuthController : Controller
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    [HttpPost]
 | 
				
			||||||
 | 
					    public async Task<IActionResult> Login(string username, string password)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var ldapService = HttpContext.RequestServices.GetService<LdapAuthenticationService>();
 | 
				
			||||||
 | 
					        if (ldapService.Authenticate(username, password))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var claims = new[] { new Claim(ClaimTypes.Name, username) };
 | 
				
			||||||
 | 
					            var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
 | 
				
			||||||
 | 
					            await HttpContext.SignInAsync(new ClaimsPrincipal(identity));
 | 
				
			||||||
 | 
					            return RedirectToSamePageIfPossible();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ViewBag.Error = "Invalid credentials";
 | 
				
			||||||
 | 
					        return RedirectToSamePageIfPossible();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [HttpPost]
 | 
				
			||||||
 | 
					    public async Task<IActionResult> Logout()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        await HttpContext.SignOutAsync();
 | 
				
			||||||
 | 
					        return RedirectToSamePageIfPossible();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private IActionResult RedirectToSamePageIfPossible()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (Request.Headers.ContainsKey("Referer"))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return Redirect(Request.Headers["Referer"].ToString());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return RedirectToPage("/");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										142
									
								
								song_of_the_day/Data/Migrations/20250516161831_UpdateUserModel.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								song_of_the_day/Data/Migrations/20250516161831_UpdateUserModel.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
				
			|||||||
 | 
					// <auto-generated />
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 | 
				
			||||||
 | 
					using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace song_of_the_day.DataMigrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    [DbContext(typeof(DataContext))]
 | 
				
			||||||
 | 
					    [Migration("20250516161831_UpdateUserModel")]
 | 
				
			||||||
 | 
					    partial class UpdateUserModel
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					#pragma warning disable 612, 618
 | 
				
			||||||
 | 
					            modelBuilder
 | 
				
			||||||
 | 
					                .HasAnnotation("ProductVersion", "9.0.3")
 | 
				
			||||||
 | 
					                .HasAnnotation("Relational:MaxIdentifierLength", 63);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("Song", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<int>("SongId")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SongId"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Artist")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Url")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("SongId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("Songs");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("SongSuggestion", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime>("Date")
 | 
				
			||||||
 | 
					                        .HasColumnType("timestamp with time zone");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<int?>("SongId")
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<int?>("UserId")
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("SongId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("UserId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("SongSuggestions");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("SuggestionHelper", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Description")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Title")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("SuggestionHelpers");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("User", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<int>("UserId")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<bool>("AssociationInProgress")
 | 
				
			||||||
 | 
					                        .HasColumnType("boolean");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<bool>("IsIntroduced")
 | 
				
			||||||
 | 
					                        .HasColumnType("boolean");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("LdapUserName")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("NickName")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("SignalMemberId")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("UserId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("Users");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("SongSuggestion", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.HasOne("Song", "Song")
 | 
				
			||||||
 | 
					                        .WithMany()
 | 
				
			||||||
 | 
					                        .HasForeignKey("SongId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasOne("User", "User")
 | 
				
			||||||
 | 
					                        .WithMany()
 | 
				
			||||||
 | 
					                        .HasForeignKey("UserId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Navigation("Song");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Navigation("User");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					#pragma warning restore 612, 618
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,265 @@
 | 
				
			|||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace song_of_the_day.DataMigrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <inheritdoc />
 | 
				
			||||||
 | 
					    public partial class UpdateUserModel : Migration
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Up(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SongSuggestions_Songs_SongId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SongSuggestions_Users_UserId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "SignalMemberId",
 | 
				
			||||||
 | 
					                table: "Users",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "NickName",
 | 
				
			||||||
 | 
					                table: "Users",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Name",
 | 
				
			||||||
 | 
					                table: "Users",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<bool>(
 | 
				
			||||||
 | 
					                name: "AssociationInProgress",
 | 
				
			||||||
 | 
					                table: "Users",
 | 
				
			||||||
 | 
					                type: "boolean",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<string>(
 | 
				
			||||||
 | 
					                name: "LdapUserName",
 | 
				
			||||||
 | 
					                table: "Users",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Title",
 | 
				
			||||||
 | 
					                table: "SuggestionHelpers",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Description",
 | 
				
			||||||
 | 
					                table: "SuggestionHelpers",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<int>(
 | 
				
			||||||
 | 
					                name: "UserId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions",
 | 
				
			||||||
 | 
					                type: "integer",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(int),
 | 
				
			||||||
 | 
					                oldType: "integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<int>(
 | 
				
			||||||
 | 
					                name: "SongId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions",
 | 
				
			||||||
 | 
					                type: "integer",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(int),
 | 
				
			||||||
 | 
					                oldType: "integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Url",
 | 
				
			||||||
 | 
					                table: "Songs",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Name",
 | 
				
			||||||
 | 
					                table: "Songs",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Artist",
 | 
				
			||||||
 | 
					                table: "Songs",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SongSuggestions_Songs_SongId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions",
 | 
				
			||||||
 | 
					                column: "SongId",
 | 
				
			||||||
 | 
					                principalTable: "Songs",
 | 
				
			||||||
 | 
					                principalColumn: "SongId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SongSuggestions_Users_UserId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions",
 | 
				
			||||||
 | 
					                column: "UserId",
 | 
				
			||||||
 | 
					                principalTable: "Users",
 | 
				
			||||||
 | 
					                principalColumn: "UserId");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Down(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SongSuggestions_Songs_SongId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SongSuggestions_Users_UserId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "AssociationInProgress",
 | 
				
			||||||
 | 
					                table: "Users");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "LdapUserName",
 | 
				
			||||||
 | 
					                table: "Users");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "SignalMemberId",
 | 
				
			||||||
 | 
					                table: "Users",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: "",
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "NickName",
 | 
				
			||||||
 | 
					                table: "Users",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: "",
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Name",
 | 
				
			||||||
 | 
					                table: "Users",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: "",
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Title",
 | 
				
			||||||
 | 
					                table: "SuggestionHelpers",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: "",
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Description",
 | 
				
			||||||
 | 
					                table: "SuggestionHelpers",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: "",
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<int>(
 | 
				
			||||||
 | 
					                name: "UserId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions",
 | 
				
			||||||
 | 
					                type: "integer",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: 0,
 | 
				
			||||||
 | 
					                oldClrType: typeof(int),
 | 
				
			||||||
 | 
					                oldType: "integer",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<int>(
 | 
				
			||||||
 | 
					                name: "SongId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions",
 | 
				
			||||||
 | 
					                type: "integer",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: 0,
 | 
				
			||||||
 | 
					                oldClrType: typeof(int),
 | 
				
			||||||
 | 
					                oldType: "integer",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Url",
 | 
				
			||||||
 | 
					                table: "Songs",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: "",
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Name",
 | 
				
			||||||
 | 
					                table: "Songs",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: "",
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AlterColumn<string>(
 | 
				
			||||||
 | 
					                name: "Artist",
 | 
				
			||||||
 | 
					                table: "Songs",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: "",
 | 
				
			||||||
 | 
					                oldClrType: typeof(string),
 | 
				
			||||||
 | 
					                oldType: "text",
 | 
				
			||||||
 | 
					                oldNullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SongSuggestions_Songs_SongId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions",
 | 
				
			||||||
 | 
					                column: "SongId",
 | 
				
			||||||
 | 
					                principalTable: "Songs",
 | 
				
			||||||
 | 
					                principalColumn: "SongId",
 | 
				
			||||||
 | 
					                onDelete: ReferentialAction.Cascade);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SongSuggestions_Users_UserId",
 | 
				
			||||||
 | 
					                table: "SongSuggestions",
 | 
				
			||||||
 | 
					                column: "UserId",
 | 
				
			||||||
 | 
					                principalTable: "Users",
 | 
				
			||||||
 | 
					                principalColumn: "UserId",
 | 
				
			||||||
 | 
					                onDelete: ReferentialAction.Cascade);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -30,15 +30,12 @@ namespace song_of_the_day.DataMigrations
 | 
				
			|||||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SongId"));
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SongId"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Artist")
 | 
					                    b.Property<string>("Artist")
 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Name")
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Url")
 | 
					                    b.Property<string>("Url")
 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("SongId");
 | 
					                    b.HasKey("SongId");
 | 
				
			||||||
@@ -57,10 +54,10 @@ namespace song_of_the_day.DataMigrations
 | 
				
			|||||||
                    b.Property<DateTime>("Date")
 | 
					                    b.Property<DateTime>("Date")
 | 
				
			||||||
                        .HasColumnType("timestamp with time zone");
 | 
					                        .HasColumnType("timestamp with time zone");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("SongId")
 | 
					                    b.Property<int?>("SongId")
 | 
				
			||||||
                        .HasColumnType("integer");
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					                    b.Property<int?>("UserId")
 | 
				
			||||||
                        .HasColumnType("integer");
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("Id");
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
@@ -81,11 +78,9 @@ namespace song_of_the_day.DataMigrations
 | 
				
			|||||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Description")
 | 
					                    b.Property<string>("Description")
 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Title")
 | 
					                    b.Property<string>("Title")
 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("Id");
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
@@ -101,19 +96,22 @@ namespace song_of_the_day.DataMigrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<bool>("AssociationInProgress")
 | 
				
			||||||
 | 
					                        .HasColumnType("boolean");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<bool>("IsIntroduced")
 | 
					                    b.Property<bool>("IsIntroduced")
 | 
				
			||||||
                        .HasColumnType("boolean");
 | 
					                        .HasColumnType("boolean");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("LdapUserName")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Name")
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("NickName")
 | 
					                    b.Property<string>("NickName")
 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("SignalMemberId")
 | 
					                    b.Property<string>("SignalMemberId")
 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("UserId");
 | 
					                    b.HasKey("UserId");
 | 
				
			||||||
@@ -125,15 +123,11 @@ namespace song_of_the_day.DataMigrations
 | 
				
			|||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("Song", "Song")
 | 
					                    b.HasOne("Song", "Song")
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithMany()
 | 
				
			||||||
                        .HasForeignKey("SongId")
 | 
					                        .HasForeignKey("SongId");
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasOne("User", "User")
 | 
					                    b.HasOne("User", "User")
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithMany()
 | 
				
			||||||
                        .HasForeignKey("UserId")
 | 
					                        .HasForeignKey("UserId");
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Navigation("Song");
 | 
					                    b.Navigation("Song");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,4 +7,6 @@ public class User
 | 
				
			|||||||
    public string? Name { get; set; }
 | 
					    public string? Name { get; set; }
 | 
				
			||||||
    public string? NickName { get; set; }
 | 
					    public string? NickName { get; set; }
 | 
				
			||||||
    public bool IsIntroduced { get; set; }
 | 
					    public bool IsIntroduced { get; set; }
 | 
				
			||||||
 | 
					    public bool AssociationInProgress { get; set; }
 | 
				
			||||||
 | 
					    public string? LdapUserName { get; set; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								song_of_the_day/LDAPIntegration/Data/LdapUser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								song_of_the_day/LDAPIntegration/Data/LdapUser.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					public class LdapUser
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public string? UserId { get; set; }
 | 
				
			||||||
 | 
					    public string? FirstName { get; set; }
 | 
				
			||||||
 | 
					    public string? LastName { get; set; }
 | 
				
			||||||
 | 
					    public string? Email { get; set; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										124
									
								
								song_of_the_day/LDAPIntegration/LdapIntegration.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								song_of_the_day/LDAPIntegration/LdapIntegration.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
				
			|||||||
 | 
					using System.Collections;
 | 
				
			||||||
 | 
					using System.ComponentModel;
 | 
				
			||||||
 | 
					using song_of_the_day;
 | 
				
			||||||
 | 
					using System.DirectoryServices.Protocols;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class LdapIntegration
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static LdapIntegration? Instance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private readonly string[] attributesToQuery = new string[]
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "uid",
 | 
				
			||||||
 | 
					            "givenName",
 | 
				
			||||||
 | 
					            "sn",
 | 
				
			||||||
 | 
					            "mail"
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public LdapIntegration(string uri, int port, string adminBind, string adminPass)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.Uri = uri;
 | 
				
			||||||
 | 
					        this.Port = port;
 | 
				
			||||||
 | 
					        this.AdminBind = adminBind;
 | 
				
			||||||
 | 
					        this.AdminPass = adminPass;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private string Uri { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private int Port { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private string AdminBind { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private string AdminPass { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public bool TestLogin(string username, string password)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var userList = this.SearchInADAsUser(
 | 
				
			||||||
 | 
					                AppConfiguration.Instance.LDAPConfig.LDAPQueryBase,
 | 
				
			||||||
 | 
					                $"(uid={username})",
 | 
				
			||||||
 | 
					                SearchScope.Subtree,
 | 
				
			||||||
 | 
					                username,
 | 
				
			||||||
 | 
					                password);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (LdapException ex)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (ex.Message.Contains("credential is invalid"))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            throw;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public List<LdapUser> SearchInAD(
 | 
				
			||||||
 | 
					        string targetOU,
 | 
				
			||||||
 | 
					        string query,
 | 
				
			||||||
 | 
					        SearchScope scope
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // search as admin
 | 
				
			||||||
 | 
					        return this.SearchInADAsUser(targetOU, query, scope, this.AdminBind, this.AdminPass);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public List<LdapUser> SearchInADAsUser(
 | 
				
			||||||
 | 
					        string targetOU,
 | 
				
			||||||
 | 
					        string query,
 | 
				
			||||||
 | 
					        SearchScope scope,
 | 
				
			||||||
 | 
					        string userName,
 | 
				
			||||||
 | 
					        string userPass
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // on Windows the authentication type is Negotiate, so there is no need to prepend
 | 
				
			||||||
 | 
					        // AD user login with domain. On other platforms at the moment only
 | 
				
			||||||
 | 
					        // Basic authentication is supported
 | 
				
			||||||
 | 
					        var authType = AuthType.Basic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var user = userName.StartsWith("cn=") || userName.StartsWith("uid=") ? userName : "uid=" + userName + "," + AppConfiguration.Instance.LDAPConfig.LDAPUserQueryBase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //var connection = new LdapConnection(ldapServer)
 | 
				
			||||||
 | 
					        var connection = new LdapConnection(
 | 
				
			||||||
 | 
					            new LdapDirectoryIdentifier(this.Uri, this.Port)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            AuthType = authType,
 | 
				
			||||||
 | 
					            Credential = new(user, userPass)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // the default one is v2 (at least in that version), and it is unknown if v3
 | 
				
			||||||
 | 
					        // is actually needed, but at least Synology LDAP works only with v3,
 | 
				
			||||||
 | 
					        // and since our Exchange doesn't complain, let it be v3
 | 
				
			||||||
 | 
					        connection.SessionOptions.ProtocolVersion = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // this is for connecting via LDAPS (636 port). It should be working,
 | 
				
			||||||
 | 
					        // according to https://github.com/dotnet/runtime/issues/43890,
 | 
				
			||||||
 | 
					        // but it doesn't (at least with Synology DSM LDAP), although perhaps
 | 
				
			||||||
 | 
					        // for a different reason
 | 
				
			||||||
 | 
					        //connection.SessionOptions.SecureSocketLayer = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.Bind();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var request = new SearchRequest(targetOU, query, scope, attributesToQuery);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var response = (System.DirectoryServices.Protocols.SearchResponse)connection.SendRequest(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var userList = new List<LdapUser>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach(SearchResultEntry result in response.Entries)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            userList.Add(new LdapUser() {
 | 
				
			||||||
 | 
					                UserId = result.Attributes["uid"][0].ToString(),
 | 
				
			||||||
 | 
					                FirstName = result.Attributes["givenName"][0].ToString(),
 | 
				
			||||||
 | 
					                LastName = result.Attributes["sn"][0].ToString(),
 | 
				
			||||||
 | 
					                Email = result.Attributes["mail"][0].ToString(),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.Dispose();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return userList;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,15 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					@{
 | 
				
			||||||
 | 
					    bool DoesUserHaveClaimedPhoneNumber()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        using (var dci = DataContext.Instance)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = dci.Users.Where(u => u.LdapUserName == User.Identity.Name);
 | 
				
			||||||
 | 
					            return user.Any();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
<html lang="en">
 | 
					<html lang="en">
 | 
				
			||||||
<head>
 | 
					<head>
 | 
				
			||||||
    <meta charset="utf-8" />
 | 
					    <meta charset="utf-8" />
 | 
				
			||||||
@@ -26,9 +37,18 @@
 | 
				
			|||||||
                        <li class="nav-item">
 | 
					                        <li class="nav-item">
 | 
				
			||||||
                            <a class="nav-link text-dark" asp-area="" asp-page="/SuggestionHelpers">Suggestion Helpers</a>
 | 
					                            <a class="nav-link text-dark" asp-area="" asp-page="/SuggestionHelpers">Suggestion Helpers</a>
 | 
				
			||||||
                        </li>
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        @if (this.User.Identity.IsAuthenticated && !DoesUserHaveClaimedPhoneNumber())
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            <li class="nav-item">
 | 
				
			||||||
 | 
					                                <a class="nav-link text-dark" asp-area="" asp-page="/UnclaimedPhoneNumbers">Unclaimed Phone Numbers</a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    </ul>
 | 
					                    </ul>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="container" style="min-height: auto; width: 400px;">
 | 
				
			||||||
 | 
					                <partial name="_LoginView" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
        </nav>
 | 
					        </nav>
 | 
				
			||||||
    </header>
 | 
					    </header>
 | 
				
			||||||
    <div class="container">
 | 
					    <div class="container">
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								song_of_the_day/Pages/Shared/_LoginView.cshtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								song_of_the_day/Pages/Shared/_LoginView.cshtml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					@using Microsoft.AspNetCore.Authentication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="loginform">
 | 
				
			||||||
 | 
					    @if (!this.User.Identity.IsAuthenticated)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        <form method="post" action="Auth/Login">
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <label for="username">Username:</label>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <input name="username" type="text" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <label for="password">Password:</label>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <input name="password" type="password" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <input name="submit" type="submit" value="Login" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        <form method="post" action="Auth/Logout">
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                Welcome, @User.Identity.Name!
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <input name="submit" type="submit" value="Logout" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										38
									
								
								song_of_the_day/Pages/UnclaimedPhoneNumbers.cshtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								song_of_the_day/Pages/UnclaimedPhoneNumbers.cshtml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					@page
 | 
				
			||||||
 | 
					@model UnclaimedPhoneNumbersModel
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    ViewData["Title"] = "Unclaimed Phone Numbers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var codeService = HttpContext.RequestServices.GetService<PhoneClaimCodeProviderService>();
 | 
				
			||||||
 | 
					    var codeGenerated = codeService.IsCodeGeneratedForUser(User.Identity.Name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="text-left">
 | 
				
			||||||
 | 
					    <table>
 | 
				
			||||||
 | 
					        <tr>
 | 
				
			||||||
 | 
					            <th>Phone Number</th>
 | 
				
			||||||
 | 
					            <th>Claim</th>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        @foreach (var user in @Model.UnclaimedUsers)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var phone = user.SignalMemberId; var userId = user.UserId;
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>@phone</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                    <form method="post">
 | 
				
			||||||
 | 
					                        <input name="userIndex" value="@userId" type="hidden" /> 
 | 
				
			||||||
 | 
					                        <input type="submit" title="Claim" value="Claim" disabled="@codeGenerated" />
 | 
				
			||||||
 | 
					                    </form>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </table>   
 | 
				
			||||||
 | 
					    @if(codeGenerated)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        <form method="post">
 | 
				
			||||||
 | 
					            <label for="code">Verification code:</label>
 | 
				
			||||||
 | 
					            <input type="text" id="code" name="code" />
 | 
				
			||||||
 | 
					            <input type="submit" title="Verify" value="Verify" asp-page-handler="SubmitCode" />
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    } 
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										64
									
								
								song_of_the_day/Pages/UnclaimedPhoneNumbers.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								song_of_the_day/Pages/UnclaimedPhoneNumbers.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
				
			||||||
 | 
					using Microsoft.VisualBasic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace sotd.Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class UnclaimedPhoneNumbersModel : PageModel
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private readonly ILogger<UserModel> _logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public UnclaimedPhoneNumbersModel(ILogger<UserModel> logger)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _logger = logger;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public int userId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [BindProperty]
 | 
				
			||||||
 | 
					    public List<User> UnclaimedUsers { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void OnGet()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        using (var dci = DataContext.Instance)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            this.UnclaimedUsers = dci.Users.Where(u => string.IsNullOrEmpty(u.LdapUserName)).ToList();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void OnPost(int userIndex)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        using (var dci = DataContext.Instance)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = dci.Users.Find(userIndex);
 | 
				
			||||||
 | 
					            var claimCodeService = HttpContext.RequestServices.GetService<PhoneClaimCodeProviderService>();
 | 
				
			||||||
 | 
					            claimCodeService.GenerateClaimCodeForUserAndNumber(HttpContext.User.Identity.Name, user.SignalMemberId);
 | 
				
			||||||
 | 
					            this.UnclaimedUsers = dci.Users.Where(u => string.IsNullOrEmpty(u.LdapUserName)).ToList();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public IActionResult OnPostSubmitCode(string code)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var claimCodeService = HttpContext.RequestServices.GetService<PhoneClaimCodeProviderService>();
 | 
				
			||||||
 | 
					        var validatedNumber = claimCodeService.ValidateClaimCodeForUser(code, HttpContext.User.Identity.Name);
 | 
				
			||||||
 | 
					        if (!string.IsNullOrEmpty(validatedNumber))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            using (var dci = DataContext.Instance)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var user = dci.Users.Where(u => u.SignalMemberId == validatedNumber).FirstOrDefault();
 | 
				
			||||||
 | 
					                if (user == default(User))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    throw new Exception("User with specified phone number not found!");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                user.LdapUserName = HttpContext.User.Identity.Name;
 | 
				
			||||||
 | 
					                dci.SaveChanges();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("Invalid code provided!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return RedirectToPage("/");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,11 +5,17 @@ using Microsoft.EntityFrameworkCore;
 | 
				
			|||||||
using Microsoft.AspNetCore.Authentication;
 | 
					using Microsoft.AspNetCore.Authentication;
 | 
				
			||||||
using Microsoft.AspNetCore.Authentication.Cookies;
 | 
					using Microsoft.AspNetCore.Authentication.Cookies;
 | 
				
			||||||
using System.DirectoryServices.Protocols;
 | 
					using System.DirectoryServices.Protocols;
 | 
				
			||||||
 | 
					using System.Runtime.CompilerServices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SignalIntegration.Instance = new SignalIntegration(AppConfiguration.Instance.SignalAPIEndpointUri,
 | 
					SignalIntegration.Instance = new SignalIntegration(AppConfiguration.Instance.SignalAPIEndpointUri,
 | 
				
			||||||
                                                    int.Parse(AppConfiguration.Instance.SignalAPIEndpointPort),
 | 
					                                                    int.Parse(AppConfiguration.Instance.SignalAPIEndpointPort),
 | 
				
			||||||
                                                    AppConfiguration.Instance.HostPhoneNumber);
 | 
					                                                    AppConfiguration.Instance.HostPhoneNumber);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LdapIntegration.Instance = new LdapIntegration(AppConfiguration.Instance.LDAPConfig.LDAPserver,
 | 
				
			||||||
 | 
					                                                    AppConfiguration.Instance.LDAPConfig.Port,
 | 
				
			||||||
 | 
					                                                    AppConfiguration.Instance.LDAPConfig.Username,
 | 
				
			||||||
 | 
					                                                    AppConfiguration.Instance.LDAPConfig.Password);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Console.WriteLine("Setting up user check timer");
 | 
					Console.WriteLine("Setting up user check timer");
 | 
				
			||||||
@@ -34,7 +40,9 @@ userCheckTimer.OnOccurence += async (s, ea) =>
 | 
				
			|||||||
                Name = newUserContact.Name,
 | 
					                Name = newUserContact.Name,
 | 
				
			||||||
                SignalMemberId = memberId,
 | 
					                SignalMemberId = memberId,
 | 
				
			||||||
                NickName = string.Empty,
 | 
					                NickName = string.Empty,
 | 
				
			||||||
                IsIntroduced = false
 | 
					                IsIntroduced = false,
 | 
				
			||||||
 | 
					                LdapUserName = string.Empty,
 | 
				
			||||||
 | 
					                AssociationInProgress = false,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            dci.Users.Add(newUser);
 | 
					            dci.Users.Add(newUser);
 | 
				
			||||||
            needsSaving = true;
 | 
					            needsSaving = true;
 | 
				
			||||||
@@ -47,7 +55,7 @@ userCheckTimer.OnOccurence += async (s, ea) =>
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    await dci.DisposeAsync();
 | 
					    await dci.DisposeAsync();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
//userCheckTimer.Start();
 | 
					userCheckTimer.Start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Console.WriteLine("Setting up user intro timer");
 | 
					Console.WriteLine("Setting up user intro timer");
 | 
				
			||||||
var userIntroTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false);
 | 
					var userIntroTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false);
 | 
				
			||||||
@@ -69,13 +77,23 @@ userIntroTimer.OnOccurence += async (s, ea) =>
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    await dci.DisposeAsync();
 | 
					    await dci.DisposeAsync();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
//userIntroTimer.Start();
 | 
					userIntroTimer.Start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Console.WriteLine("Setting up pick of the day timer");
 | 
					Console.WriteLine("Setting up pick of the day timer");
 | 
				
			||||||
var pickOfTheDayTimer = new CronTimer("0 8 * * *", "Europe/Vienna", includingSeconds: false);
 | 
					var pickOfTheDayTimer = new CronTimer("0 8 * * *", "Europe/Vienna", includingSeconds: false);
 | 
				
			||||||
pickOfTheDayTimer.OnOccurence += async (s, ea) =>
 | 
					pickOfTheDayTimer.OnOccurence += async (s, ea) =>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    var rand = new Random();
 | 
				
			||||||
 | 
					    var num = rand.NextInt64();
 | 
				
			||||||
 | 
					    var mod = num % AppConfiguration.Instance.AverageDaysBetweenRequests;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mod > 0)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Console.WriteLine("Skipping pick of the day today!");
 | 
				
			||||||
 | 
					        return;   
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var dci = DataContext.Instance;
 | 
					    var dci = DataContext.Instance;
 | 
				
			||||||
    var luckyUser = await dci.Users.ElementAtAsync((new Random()).Next(await dci.Users.CountAsync()));
 | 
					    var luckyUser = await dci.Users.ElementAtAsync((new Random()).Next(await dci.Users.CountAsync()));
 | 
				
			||||||
    var userName = string.IsNullOrEmpty(luckyUser.NickName) ? luckyUser.Name : luckyUser.NickName;
 | 
					    var userName = string.IsNullOrEmpty(luckyUser.NickName) ? luckyUser.Name : luckyUser.NickName;
 | 
				
			||||||
@@ -85,88 +103,57 @@ pickOfTheDayTimer.OnOccurence += async (s, ea) =>
 | 
				
			|||||||
    SignalIntegration.Instance.SendMessageToUserAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*", luckyUser.SignalMemberId);
 | 
					    SignalIntegration.Instance.SendMessageToUserAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*", luckyUser.SignalMemberId);
 | 
				
			||||||
    SignalIntegration.Instance.SendMessageToUserAsync($"For now please just share your suggestion with the group - in the future I might ask you to share directly with me or via the website to help me keep track of past suggestions!", luckyUser.SignalMemberId);
 | 
					    SignalIntegration.Instance.SendMessageToUserAsync($"For now please just share your suggestion with the group - in the future I might ask you to share directly with me or via the website to help me keep track of past suggestions!", luckyUser.SignalMemberId);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
//pickOfTheDayTimer.Start();
 | 
					pickOfTheDayTimer.Start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var connection = new LdapConnection(AppConfiguration.Instance.LDAPConfig.LDAPserver)
 | 
					var startUserAssociationProcess = (User userToAssociate) =>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    Credential = new(
 | 
					    SignalIntegration.Instance.SendMessageToUserAsync($"Hi, I see you are not associated with any website user yet.", userToAssociate.SignalMemberId);
 | 
				
			||||||
        AppConfiguration.Instance.LDAPConfig.Username,
 | 
					    SignalIntegration.Instance.SendMessageToUserAsync($"If you haven't yet, please navigate to https://users.disi.dev to create a new account.", userToAssociate.SignalMemberId);
 | 
				
			||||||
        AppConfiguration.Instance.LDAPConfig.Password
 | 
					    SignalIntegration.Instance.SendMessageToUserAsync($"Once you have done so, go to https://sotd.disi.dev, login, navigate to \"Unclaimed Phone Numbers\" and click on the \"Claim\" button to start the claim process.", userToAssociate.SignalMemberId);
 | 
				
			||||||
    )
 | 
					    SignalIntegration.Instance.SendMessageToUserAsync($"With a future update you will be required to submit songs via your user account - at that point you will be skipped during the selection process if you have not yet claimed your phone number!", userToAssociate.SignalMemberId);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var attributesToQuery = new string[]
 | 
					Console.WriteLine("Setting up LdapAssociation timer");
 | 
				
			||||||
 | 
					var ldapAssociationTimer = new CronTimer("*/10 * * * *", "Europe/Vienna", includingSeconds: false);
 | 
				
			||||||
 | 
					ldapAssociationTimer.OnOccurence += async (s, ea) =>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "objectGUID",
 | 
					    var dci = DataContext.Instance;
 | 
				
			||||||
    "sAMAccountName",
 | 
					    var nonAssociatedUsers = dci.Users.Where(u => string.IsNullOrEmpty(u.LdapUserName) && !u.AssociationInProgress);
 | 
				
			||||||
    "displayName",
 | 
					    var needsSaving = false;
 | 
				
			||||||
    "mail",
 | 
					    foreach (var user in nonAssociatedUsers)
 | 
				
			||||||
    "whenCreated"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SearchResponse SearchInAD(
 | 
					 | 
				
			||||||
    string ldapServer,
 | 
					 | 
				
			||||||
    int ldapPort,
 | 
					 | 
				
			||||||
    string domainForAD,
 | 
					 | 
				
			||||||
    string username,
 | 
					 | 
				
			||||||
    string password,
 | 
					 | 
				
			||||||
    string targetOU,
 | 
					 | 
				
			||||||
    string query,
 | 
					 | 
				
			||||||
    SearchScope scope,
 | 
					 | 
				
			||||||
    params string[] attributeList
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    // on Windows the authentication type is Negotiate, so there is no need to prepend
 | 
					 | 
				
			||||||
    // AD user login with domain. On other platforms at the moment only
 | 
					 | 
				
			||||||
    // Basic authentication is supported
 | 
					 | 
				
			||||||
    var authType = AuthType.Basic;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //var connection = new LdapConnection(ldapServer)
 | 
					 | 
				
			||||||
    var connection = new LdapConnection(
 | 
					 | 
				
			||||||
        new LdapDirectoryIdentifier(ldapServer, ldapPort)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        AuthType = authType,
 | 
					        user.AssociationInProgress = true;
 | 
				
			||||||
        Credential = new(username, password)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    // the default one is v2 (at least in that version), and it is unknown if v3
 | 
					 | 
				
			||||||
    // is actually needed, but at least Synology LDAP works only with v3,
 | 
					 | 
				
			||||||
    // and since our Exchange doesn't complain, let it be v3
 | 
					 | 
				
			||||||
    connection.SessionOptions.ProtocolVersion = 3;
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    // this is for connecting via LDAPS (636 port). It should be working,
 | 
					        startUserAssociationProcess(user);
 | 
				
			||||||
    // according to https://github.com/dotnet/runtime/issues/43890,
 | 
					        user.IsIntroduced = true;
 | 
				
			||||||
    // but it doesn't (at least with Synology DSM LDAP), although perhaps
 | 
					        needsSaving = true;
 | 
				
			||||||
    // for a different reason
 | 
					    }
 | 
				
			||||||
    //connection.SessionOptions.SecureSocketLayer = true;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    connection.Bind();
 | 
					    if (needsSaving)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        await dci.SaveChangesAsync();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await dci.DisposeAsync();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					ldapAssociationTimer.Start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var request = new SearchRequest(targetOU, query, scope, attributeList);
 | 
					var searchResults = LdapIntegration.Instance.SearchInAD(
 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (SearchResponse)connection.SendRequest(request);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var searchResults = SearchInAD(
 | 
					 | 
				
			||||||
    AppConfiguration.Instance.LDAPConfig.LDAPserver,
 | 
					 | 
				
			||||||
    AppConfiguration.Instance.LDAPConfig.Port,
 | 
					 | 
				
			||||||
    AppConfiguration.Instance.LDAPConfig.Username,
 | 
					 | 
				
			||||||
    AppConfiguration.Instance.LDAPConfig.Password,
 | 
					 | 
				
			||||||
    AppConfiguration.Instance.LDAPConfig.LDAPQueryBase,
 | 
					    AppConfiguration.Instance.LDAPConfig.LDAPQueryBase,
 | 
				
			||||||
    new StringBuilder("(&")
 | 
					    $"(memberOf={AppConfiguration.Instance.LDAPConfig.CrewGroup})",
 | 
				
			||||||
        .Append("(objectCategory=person)")
 | 
					    SearchScope.Subtree
 | 
				
			||||||
        .Append("(objectClass=user)")
 | 
					 | 
				
			||||||
        .Append($"(memberOf={_configurationAD.Crew})")
 | 
					 | 
				
			||||||
        .Append("(!(userAccountControl:1.2.840.113556.1.4.803:=2))")
 | 
					 | 
				
			||||||
        .Append(")")
 | 
					 | 
				
			||||||
        .ToString(),
 | 
					 | 
				
			||||||
    SearchScope.Subtree,
 | 
					 | 
				
			||||||
    attributesToQuery
 | 
					 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add services to the container.
 | 
					// Add services to the container.
 | 
				
			||||||
builder.Services.AddRazorPages();
 | 
					builder.Services.AddRazorPages();
 | 
				
			||||||
builder.Services.AddOpenApi();
 | 
					builder.Services.AddOpenApi();
 | 
				
			||||||
 | 
					builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
 | 
				
			||||||
 | 
					    .AddCookie(options =>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        options.LoginPath = "/Auth/Login";
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.Services.AddSingleton<LdapAuthenticationService>();
 | 
				
			||||||
 | 
					builder.Services.AddSingleton<PhoneClaimCodeProviderService>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -188,5 +175,15 @@ app.UseAuthorization();
 | 
				
			|||||||
app.MapStaticAssets();
 | 
					app.MapStaticAssets();
 | 
				
			||||||
app.MapRazorPages()
 | 
					app.MapRazorPages()
 | 
				
			||||||
   .WithStaticAssets();
 | 
					   .WithStaticAssets();
 | 
				
			||||||
 | 
					app.MapControllerRoute(
 | 
				
			||||||
 | 
					    name: "login",
 | 
				
			||||||
 | 
					    pattern: "{controller=Auth}/{action=Login}"
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					app.MapControllerRoute(
 | 
				
			||||||
 | 
					    name: "logout",
 | 
				
			||||||
 | 
					    pattern: "{controller=Auth}/{action=Logout}"
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					app.MapGet("/debug/routes", (IEnumerable<EndpointDataSource> endpointSources) =>
 | 
				
			||||||
 | 
					        string.Join("\n", endpointSources.SelectMany(source => source.Endpoints)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.Run();
 | 
					app.Run();
 | 
				
			||||||
		Reference in New Issue
	
	Block a user