mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-24 06:55:23 -07:00
WIP LDAP authentication and LDAP importer
This commit is contained in:
parent
ff17117f89
commit
691c70804f
25 changed files with 703 additions and 48 deletions
168
src/Ombi.Core/Authentication/LdapUserManager.cs
Normal file
168
src/Ombi.Core/Authentication/LdapUserManager.cs
Normal file
|
@ -0,0 +1,168 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Novell.Directory.Ldap;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Ldap Authentication Provider
|
||||
/// </summary>
|
||||
public class LdapUserManager : ILdapUserManager
|
||||
{
|
||||
public LdapUserManager(ILogger<LdapUserManager> logger, ISettingsService<LdapSettings> ldapSettings)
|
||||
{
|
||||
_ldapSettingsService = ldapSettings;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private readonly ISettingsService<LdapSettings> _ldapSettingsService;
|
||||
private readonly ILogger<LdapUserManager> _logger;
|
||||
|
||||
public async Task<LdapSettings> GetSettings()
|
||||
{
|
||||
return await _ldapSettingsService.GetSettingsAsync();
|
||||
}
|
||||
|
||||
public async Task<OmbiUser> LdapEntryToOmbiUser(LdapEntry entry)
|
||||
{
|
||||
var settings = await GetSettings();
|
||||
var userName = GetLdapAttribute(entry, settings.UsernameAttribute).StringValue;
|
||||
|
||||
return new OmbiUser
|
||||
{
|
||||
UserType = UserType.LdapUser,
|
||||
ProviderUserId = entry.Dn,
|
||||
UserName = userName
|
||||
};
|
||||
}
|
||||
|
||||
private LdapAttribute GetLdapAttribute(LdapEntry userEntry, string attr)
|
||||
{
|
||||
try
|
||||
{
|
||||
return userEntry.GetAttribute(attr);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<LdapConnection> BindLdapConnection(string username, string password)
|
||||
{
|
||||
var settings = await GetSettings();
|
||||
|
||||
var ldapClient = new LdapConnection { SecureSocketLayer = settings.UseSsl };
|
||||
try
|
||||
{
|
||||
if (settings.SkipSslVerify)
|
||||
{
|
||||
ldapClient.UserDefinedServerCertValidationDelegate += LdapClient_UserDefinedServerCertValidationDelegate;
|
||||
}
|
||||
|
||||
ldapClient.Connect(settings.Hostname, settings.Port);
|
||||
if (settings.UseStartTls)
|
||||
{
|
||||
ldapClient.StartTls();
|
||||
}
|
||||
|
||||
ldapClient.Bind(username, password);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to Connect or Bind to server");
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ldapClient.UserDefinedServerCertValidationDelegate -= LdapClient_UserDefinedServerCertValidationDelegate;
|
||||
}
|
||||
|
||||
if (!ldapClient.Bound)
|
||||
{
|
||||
ldapClient.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
return ldapClient;
|
||||
}
|
||||
|
||||
private async Task<ILdapSearchResults> SearchLdapUsers(LdapConnection ldapClient)
|
||||
{
|
||||
var settings = await GetSettings();
|
||||
|
||||
string[] searchAttributes = { settings.UsernameAttribute };
|
||||
|
||||
_logger.LogDebug("Search: {1} {2} @ {3}", settings.BaseDn, settings.SearchFilter, settings.Hostname);
|
||||
return ldapClient.Search(settings.BaseDn, LdapConnection.ScopeSub, settings.SearchFilter, searchAttributes, false);
|
||||
}
|
||||
|
||||
public async Task<ILdapSearchResults> GetLdapUsers()
|
||||
{
|
||||
var settings = await GetSettings();
|
||||
|
||||
using var ldapClient = await BindLdapConnection(settings.BindUserDn, settings.BindUserPassword);
|
||||
|
||||
return await SearchLdapUsers(ldapClient);
|
||||
}
|
||||
|
||||
public async Task<LdapEntry> LocateLdapUser(string username)
|
||||
{
|
||||
var settings = await GetSettings();
|
||||
|
||||
using var ldapClient = await BindLdapConnection(settings.BindUserDn, settings.BindUserPassword);
|
||||
var ldapUsers = await SearchLdapUsers(ldapClient);
|
||||
if (ldapUsers == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
while (ldapUsers.HasMore())
|
||||
{
|
||||
var currentUser = ldapUsers.Next();
|
||||
var foundUsername = GetLdapAttribute(currentUser, settings.UsernameAttribute)?.StringValue;
|
||||
if (foundUsername == username)
|
||||
{
|
||||
return currentUser;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticate user against the ldap server.
|
||||
/// </summary>
|
||||
/// <param name="user">Username to authenticate.</param>
|
||||
/// <param name="password">Password to authenticate.</param>
|
||||
public async Task<bool> Authenticate(OmbiUser user, string password)
|
||||
{
|
||||
var ldapUser = await LocateLdapUser(user.UserName);
|
||||
|
||||
if (ldapUser == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var ldapClient = await BindLdapConnection(ldapUser.Dn, password);
|
||||
return (bool)ldapClient?.Bound;
|
||||
} catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool LdapClient_UserDefinedServerCertValidationDelegate(
|
||||
object sender,
|
||||
System.Security.Cryptography.X509Certificates.X509Certificate certificate,
|
||||
System.Security.Cryptography.X509Certificates.X509Chain chain,
|
||||
System.Net.Security.SslPolicyErrors sslPolicyErrors)
|
||||
=> true;
|
||||
}
|
||||
}
|
|
@ -26,12 +26,14 @@
|
|||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Novell.Directory.Ldap;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.Plex;
|
||||
using Ombi.Api.Plex.Models;
|
||||
|
@ -49,19 +51,52 @@ namespace Ombi.Core.Authentication
|
|||
IPasswordHasher<OmbiUser> passwordHasher, IEnumerable<IUserValidator<OmbiUser>> userValidators,
|
||||
IEnumerable<IPasswordValidator<OmbiUser>> passwordValidators, ILookupNormalizer keyNormalizer,
|
||||
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<OmbiUser>> logger, IPlexApi plexApi,
|
||||
IEmbyApiFactory embyApi, ISettingsService<EmbySettings> embySettings, ISettingsService<AuthenticationSettings> auth)
|
||||
IEmbyApiFactory embyApi, ISettingsService<EmbySettings> embySettings, ISettingsService<AuthenticationSettings> auth,
|
||||
ILdapUserManager ldapUserManager, ISettingsService<UserManagementSettings> userManagementSettings)
|
||||
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
|
||||
{
|
||||
_plexApi = plexApi;
|
||||
_embyApi = embyApi;
|
||||
_embySettings = embySettings;
|
||||
_authSettings = auth;
|
||||
_ldapUserManager = ldapUserManager;
|
||||
_userManagementSettings = userManagementSettings;
|
||||
}
|
||||
|
||||
private readonly IPlexApi _plexApi;
|
||||
private readonly IEmbyApiFactory _embyApi;
|
||||
private readonly ILdapUserManager _ldapUserManager;
|
||||
private readonly ISettingsService<EmbySettings> _embySettings;
|
||||
private readonly ISettingsService<AuthenticationSettings> _authSettings;
|
||||
private readonly ISettingsService<UserManagementSettings> _userManagementSettings;
|
||||
|
||||
public async Task<OmbiUser> FindUser(string userName)
|
||||
{
|
||||
var user = await FindByNameAsync(userName);
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
user = await FindByEmailAsync(userName);
|
||||
if (user != null)
|
||||
{
|
||||
user.EmailLogin = true;
|
||||
return user;
|
||||
}
|
||||
|
||||
var ldapSettings = await _ldapUserManager.GetSettings();
|
||||
if (!(ldapSettings.IsEnabled && ldapSettings.CreateUsersAtLogin))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var ldapUser = await _ldapUserManager.LocateLdapUser(userName);
|
||||
if (ldapUser == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await CreateOmbiUserFromLdapEntry(ldapUser);
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPasswordAsync(OmbiUser user, string password)
|
||||
{
|
||||
|
@ -83,6 +118,10 @@ namespace Ombi.Core.Authentication
|
|||
{
|
||||
return await CheckEmbyPasswordAsync(user, password);
|
||||
}
|
||||
if (user.UserType == UserType.LdapUser)
|
||||
{
|
||||
return await CheckLdapPasswordAsync(user, password);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -185,5 +224,42 @@ namespace Ombi.Core.Authentication
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> CheckLdapPasswordAsync(OmbiUser user, string password)
|
||||
{
|
||||
var ldapSettings = await _ldapUserManager.GetSettings();
|
||||
if (!ldapSettings.IsEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return await _ldapUserManager.Authenticate(user, password);
|
||||
}
|
||||
|
||||
public async Task<OmbiUser> CreateOmbiUserFromLdapEntry(LdapEntry entry)
|
||||
{
|
||||
var newUser = await _ldapUserManager.LdapEntryToOmbiUser(entry);
|
||||
var userManagementSettings = await _userManagementSettings.GetSettingsAsync();
|
||||
|
||||
var result = await CreateAsync(newUser);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
foreach (var identityError in result.Errors)
|
||||
{
|
||||
Logger.LogError(LoggingEvents.Authentication, identityError.Description);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (userManagementSettings.DefaultRoles.Any())
|
||||
{
|
||||
foreach (var defaultRole in userManagementSettings.DefaultRoles)
|
||||
{
|
||||
await AddToRoleAsync(newUser, defaultRole);
|
||||
}
|
||||
}
|
||||
|
||||
return newUser;
|
||||
}
|
||||
}
|
||||
}
|
20
src/Ombi.Core/ILdapUserManager.cs
Normal file
20
src/Ombi.Core/ILdapUserManager.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Threading.Tasks;
|
||||
using Novell.Directory.Ldap;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Authentication
|
||||
{
|
||||
public interface ILdapUserManager
|
||||
{
|
||||
Task<LdapSettings> GetSettings();
|
||||
|
||||
Task<bool> Authenticate(OmbiUser user, string password);
|
||||
|
||||
Task<ILdapSearchResults> GetLdapUsers();
|
||||
|
||||
Task<LdapEntry> LocateLdapUser(string username);
|
||||
|
||||
Task<OmbiUser> LdapEntryToOmbiUser(LdapEntry entry);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.8" />
|
||||
<PackageReference Include="MusicBrainzAPI" Version="2.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="3.3.1" />
|
||||
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -50,6 +50,7 @@ using Ombi.Schedule.Jobs.Emby;
|
|||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Schedule.Jobs.Plex;
|
||||
using Ombi.Schedule.Jobs.Sonarr;
|
||||
using Ombi.Schedule.Jobs.Ldap;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
using Ombi.Updater;
|
||||
using Ombi.Api.Telegram;
|
||||
|
@ -98,6 +99,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IMusicSender, MusicSender>();
|
||||
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
||||
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
|
||||
services.AddTransient<ILdapUserManager, LdapUserManager>();
|
||||
services.AddTransient<IVoteEngine, VoteEngine>();
|
||||
services.AddTransient<IDemoMovieSearchEngine, DemoMovieSearchEngine>();
|
||||
services.AddTransient<IDemoTvSearchEngine, DemoTvSearchEngine>();
|
||||
|
@ -218,6 +220,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IOmbiAutomaticUpdater, OmbiAutomaticUpdater>();
|
||||
services.AddTransient<IPlexUserImporter, PlexUserImporter>();
|
||||
services.AddTransient<IEmbyUserImporter, EmbyUserImporter>();
|
||||
services.AddTransient<ILdapUserImporter, LdapUserImporter>();
|
||||
services.AddTransient<IWelcomeEmail, WelcomeEmail>();
|
||||
services.AddTransient<ICouchPotatoSync, CouchPotatoSync>();
|
||||
services.AddTransient<IProcessProvider, ProcessProvider>();
|
||||
|
|
7
src/Ombi.Schedule/Jobs/Ldap/ILdapUserImporter.cs
Normal file
7
src/Ombi.Schedule/Jobs/Ldap/ILdapUserImporter.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
namespace Ombi.Schedule.Jobs.Ldap
|
||||
{
|
||||
public interface ILdapUserImporter : IBaseJob
|
||||
{
|
||||
}
|
||||
}
|
87
src/Ombi.Schedule/Jobs/Ldap/LdapUserImporter.cs
Normal file
87
src/Ombi.Schedule/Jobs/Ldap/LdapUserImporter.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Hubs;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Quartz;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ldap
|
||||
{
|
||||
public class LdapUserImporter : ILdapUserImporter
|
||||
{
|
||||
public LdapUserImporter(OmbiUserManager userManager, ILdapUserManager ldapUserManager,
|
||||
ISettingsService<LdapSettings> ldapSettings, ISettingsService<UserManagementSettings> ums, IHubContext<NotificationHub> notification)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_ldapUserManager = ldapUserManager;
|
||||
_ldapSettings = ldapSettings;
|
||||
_userManagementSettings = ums;
|
||||
_notification = notification;
|
||||
}
|
||||
|
||||
private readonly OmbiUserManager _userManager;
|
||||
private readonly ILdapUserManager _ldapUserManager;
|
||||
private readonly ISettingsService<LdapSettings> _ldapSettings;
|
||||
private readonly ISettingsService<UserManagementSettings> _userManagementSettings;
|
||||
private readonly IHubContext<NotificationHub> _notification;
|
||||
|
||||
public async Task Execute(IJobExecutionContext job)
|
||||
{
|
||||
var userManagementSettings = await _userManagementSettings.GetSettingsAsync();
|
||||
if (!userManagementSettings.ImportLdapUsers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var settings = await _ldapSettings.GetSettingsAsync();
|
||||
if (!settings.IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "LDAP User Importer Started");
|
||||
var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.LdapUser).ToListAsync();
|
||||
|
||||
var allLdapUsers = await _ldapUserManager.GetLdapUsers();
|
||||
|
||||
while (allLdapUsers.HasMore())
|
||||
{
|
||||
var currentUser = allLdapUsers.Next();
|
||||
var existingEmbyUser = allUsers.FirstOrDefault(x => x.ProviderUserId == currentUser.Dn);
|
||||
if (existingEmbyUser != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
await _userManager.CreateOmbiUserFromLdapEntry(currentUser);
|
||||
}
|
||||
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
.SendAsync(NotificationHub.NotificationEvent, "LDAP User Importer Finished");
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_userManager?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ using Ombi.Helpers;
|
|||
using Ombi.Schedule.Jobs;
|
||||
using Ombi.Schedule.Jobs.Couchpotato;
|
||||
using Ombi.Schedule.Jobs.Emby;
|
||||
using Ombi.Schedule.Jobs.Ldap;
|
||||
using Ombi.Schedule.Jobs.Lidarr;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Schedule.Jobs.Plex;
|
||||
|
@ -54,6 +55,7 @@ namespace Ombi.Schedule
|
|||
await AddDvrApps(s);
|
||||
await AddSystem(s);
|
||||
await AddNotifications(s);
|
||||
await AddLdap(s);
|
||||
|
||||
// Run Quartz
|
||||
await OmbiQuartz.Start();
|
||||
|
@ -102,5 +104,9 @@ namespace Ombi.Schedule
|
|||
{
|
||||
await OmbiQuartz.Instance.AddJob<INotificationService>(nameof(INotificationService), "Notifications", null);
|
||||
}
|
||||
private static async Task AddLdap(JobSettings s)
|
||||
{
|
||||
await OmbiQuartz.Instance.AddJob<ILdapUserImporter>(nameof(ILdapUserImporter), "LDAP", JobSettingsHelper.UserImporter(s));
|
||||
}
|
||||
}
|
||||
}
|
81
src/Ombi.Settings/Settings/Models/LdapSettings.cs
Normal file
81
src/Ombi.Settings/Settings/Models/LdapSettings.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
namespace Ombi.Settings.Settings.Models
|
||||
{
|
||||
public class LdapSettings : Settings
|
||||
{
|
||||
public LdapSettings()
|
||||
{
|
||||
IsEnabled = false;
|
||||
CreateUsersAtLogin = true;
|
||||
Hostname = "ldap-server.example.tld";
|
||||
BaseDn = "o=domains,dc=example,dc=tld";
|
||||
Port = 389;
|
||||
UsernameAttribute = "uid";
|
||||
SearchFilter = "(memberOf=cn=Users,dc=example,dc=tld)";
|
||||
BindUserDn = "cn=BindUser,dc=example,dc=tld";
|
||||
BindUserPassword = "password";
|
||||
UseSsl = true;
|
||||
UseStartTls = false;
|
||||
SkipSslVerify = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether LDAP is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether users should be automatically created at login.
|
||||
/// </summary>
|
||||
public bool CreateUsersAtLogin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ldap server ip or url.
|
||||
/// </summary>
|
||||
public string Hostname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ldap base search dn.
|
||||
/// </summary>
|
||||
public string BaseDn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ldap port.
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ldap username attribute.
|
||||
/// </summary>
|
||||
public string UsernameAttribute { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ldap user search filter.
|
||||
/// </summary>
|
||||
public string SearchFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ldap bind user dn.
|
||||
/// </summary>
|
||||
public string BindUserDn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ldap bind user password.
|
||||
/// </summary>
|
||||
public string BindUserPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to use ssl when connecting to the ldap server.
|
||||
/// </summary>
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to use StartTls when connecting to the ldap server.
|
||||
/// </summary>
|
||||
public bool UseStartTls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to skip ssl verification.
|
||||
/// </summary>
|
||||
public bool SkipSslVerify { get; set; }
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ namespace Ombi.Settings.Settings.Models
|
|||
public bool ImportPlexAdmin { get; set; }
|
||||
public bool ImportPlexUsers { get; set; }
|
||||
public bool ImportEmbyUsers { get; set; }
|
||||
public bool ImportLdapUsers { get; set; }
|
||||
public int MovieRequestLimit { get; set; }
|
||||
public int EpisodeRequestLimit { get; set; }
|
||||
public List<string> DefaultRoles { get; set; } = new List<string>();
|
||||
|
|
|
@ -34,5 +34,6 @@ namespace Ombi.Store.Entities
|
|||
PlexUser = 2,
|
||||
EmbyUser = 3,
|
||||
EmbyConnectUser = 4,
|
||||
LdapUser = 5,
|
||||
}
|
||||
}
|
|
@ -173,6 +173,21 @@ export interface IAuthenticationSettings extends ISettings {
|
|||
enableOAuth: boolean;
|
||||
}
|
||||
|
||||
export interface ILdapSettings extends ISettings {
|
||||
isEnabled: boolean;
|
||||
hostname: string;
|
||||
port: number;
|
||||
baseDn: string;
|
||||
useSsl: boolean;
|
||||
useStartTls: boolean;
|
||||
skipSslVerify: boolean;
|
||||
bindUserDn: string;
|
||||
bindUserPassword: string;
|
||||
usernameAttribute: string;
|
||||
searchFilter: string;
|
||||
createUsersAtLogin: boolean;
|
||||
}
|
||||
|
||||
export interface ICustomPage extends ISettings {
|
||||
enabled: boolean;
|
||||
fontAwesomeIcon: string;
|
||||
|
@ -184,6 +199,7 @@ export interface IUserManagementSettings extends ISettings {
|
|||
importPlexUsers: boolean;
|
||||
importPlexAdmin: boolean;
|
||||
importEmbyUsers: boolean;
|
||||
importLdapUsers: boolean;
|
||||
defaultRoles: string[];
|
||||
movieRequestLimit: number;
|
||||
episodeRequestLimit: number;
|
||||
|
|
|
@ -31,6 +31,10 @@ export class JobService extends ServiceHelpers {
|
|||
return this.http.post<boolean>(`${this.url}embyUserImporter/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public runLdapImporter(): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}ldapUserImporter/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public runPlexCacher(): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}plexcontentcacher/`, {headers: this.headers});
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
IVoteSettings,
|
||||
ITwilioSettings,
|
||||
IWebhookNotificationSettings,
|
||||
ILdapSettings,
|
||||
} from "../interfaces";
|
||||
|
||||
import { ServiceHelpers } from "./service.helpers";
|
||||
|
@ -124,6 +125,14 @@ export class SettingsService extends ServiceHelpers {
|
|||
return this.http.post<boolean>(`${this.url}/Authentication`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
|
||||
public getLdap(): Observable<ILdapSettings> {
|
||||
return this.http.get<ILdapSettings>(`${this.url}/ldap`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public saveLdap(settings: ILdapSettings): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}/ldap`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
|
||||
// Using http since we need it not to be authenticated to get the landing page settings
|
||||
public getLandingPage(): Observable<ILandingPageSettings> {
|
||||
return this.http.get<ILandingPageSettings>(`${this.url}/LandingPage`, {headers: this.headers});
|
||||
|
|
78
src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.html
Normal file
78
src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.html
Normal file
|
@ -0,0 +1,78 @@
|
|||
<settings-menu></settings-menu>
|
||||
<fieldset *ngIf="form" class="small-middle-container">
|
||||
<legend>LDAP Settings</legend>
|
||||
<form
|
||||
[formGroup]="form"
|
||||
(ngSubmit)="onSubmit(form)"
|
||||
class="md-form-field"
|
||||
>
|
||||
<div class="form-group">
|
||||
<mat-checkbox formControlName="isEnabled">
|
||||
LDAP Enabled
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-checkbox formControlName="createUsersAtLogin">
|
||||
Create Users at Login
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Hostname</mat-label>
|
||||
<input matInput required formControlName="hostname">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Port</mat-label>
|
||||
<input matInput type="number" required formControlName="port">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Base DN</mat-label>
|
||||
<input matInput required formControlName="baseDn">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox formControlName="useSsl">
|
||||
Use SSL
|
||||
</mat-checkbox>
|
||||
<mat-checkbox formControlName="useStartTls">
|
||||
Use StartTLS
|
||||
</mat-checkbox>
|
||||
<mat-checkbox formControlName="skipSslVerify">
|
||||
Skip SSL Verification
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Bind User DN</mat-label>
|
||||
<input matInput required formControlName="bindUserDn">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Bind User Password</mat-label>
|
||||
<input matInput required type="password" formControlName="bindUserPassword">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Username Attribute</mat-label>
|
||||
<input matInput required formControlName="usernameAttribute">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Search Filter</mat-label>
|
||||
<input matInput required formControlName="searchFilter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button
|
||||
mat-raised-button
|
||||
type="submit"
|
||||
color="primary"
|
||||
[disabled]="form.invalid"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
|
@ -0,0 +1,9 @@
|
|||
.small-middle-container {
|
||||
margin: auto;
|
||||
width: 95%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
mat-checkbox {
|
||||
margin-right: 10px;
|
||||
}
|
62
src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.ts
Normal file
62
src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup } from "@angular/forms";
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { NotificationService } from "../../services/notification.service";
|
||||
import { SettingsService } from "../../services/settings.service";
|
||||
|
||||
|
||||
@Component({
|
||||
templateUrl: "./ldap.component.html",
|
||||
styleUrls: ["./ldap.component.scss"],
|
||||
})
|
||||
export class LdapComponent implements OnInit, OnDestroy {
|
||||
|
||||
public form: FormGroup;
|
||||
|
||||
private sub: Subscription;
|
||||
|
||||
constructor(
|
||||
private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private formBuilder: FormBuilder
|
||||
) {}
|
||||
|
||||
public ngOnInit() {
|
||||
this.sub = this.settingsService.getLdap().subscribe(ldapSettings => {
|
||||
this.form = this.formBuilder.group({
|
||||
isEnabled: [ldapSettings.isEnabled],
|
||||
hostname: [ldapSettings.hostname],
|
||||
port: [ldapSettings.port],
|
||||
baseDn: [ldapSettings.baseDn],
|
||||
useSsl: [ldapSettings.useSsl],
|
||||
useStartTls: [ldapSettings.useStartTls],
|
||||
skipSslVerify: [ldapSettings.skipSslVerify],
|
||||
bindUserDn: [ldapSettings.bindUserDn],
|
||||
bindUserPassword: [ldapSettings.bindUserPassword],
|
||||
usernameAttribute: [ldapSettings.usernameAttribute],
|
||||
searchFilter: [ldapSettings.searchFilter],
|
||||
createUsersAtLogin: [ldapSettings.createUsersAtLogin],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
|
||||
public onSubmit(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
}
|
||||
|
||||
this.settingsService.saveLdap(form.value).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successfully saved LDAP settings");
|
||||
} else {
|
||||
this.notificationService.success("There was an error when saving LDAP settings");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ import { UpdateComponent } from "./update/update.component";
|
|||
import { UserManagementComponent } from "./usermanagement/usermanagement.component";
|
||||
import { VoteComponent } from "./vote/vote.component";
|
||||
import { WikiComponent } from "./wiki.component";
|
||||
import { LdapComponent } from "./ldap/ldap.component";
|
||||
|
||||
import { SettingsMenuComponent } from "./settingsmenu.component";
|
||||
|
||||
|
@ -95,6 +96,7 @@ const routes: Routes = [
|
|||
{ path: "SickRage", component: SickRageComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Issues", component: IssuesComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Authentication", component: AuthenticationComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Ldap", component: LdapComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Mobile", component: MobileComponent, canActivate: [AuthGuard] },
|
||||
{ path: "MassEmail", component: MassEmailComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] },
|
||||
|
@ -155,6 +157,7 @@ const routes: Routes = [
|
|||
TelegramComponent,
|
||||
IssuesComponent,
|
||||
AuthenticationComponent,
|
||||
LdapComponent,
|
||||
MobileComponent,
|
||||
MassEmailComponent,
|
||||
NewsletterComponent,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<button mat-menu-item [routerLink]="['/Settings/Issues']">Issues</button>
|
||||
<button mat-menu-item [routerLink]="['/Settings/UserManagement']">User Management</button>
|
||||
<button mat-menu-item [routerLink]="['/Settings/Authentication']">Authentication</button>
|
||||
<button mat-menu-item [routerLink]="['/Settings/Ldap']">Ldap</button>
|
||||
<button mat-menu-item [routerLink]="['/Settings/Vote']">Vote</button>
|
||||
<button mat-menu-item [routerLink]="['/Settings/TheMovieDb']">The Movie Database</button>
|
||||
</mat-menu>
|
||||
|
|
|
@ -35,6 +35,12 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div *ngIf="ldapEnabled">
|
||||
<div class="form-group">
|
||||
<mat-checkbox id="importLdapUsers" [(ngModel)]="settings.importLdapUsers">Import LDAP Users</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>Default Roles</h4>
|
||||
|
|
|
@ -12,6 +12,7 @@ export class UserManagementComponent implements OnInit {
|
|||
|
||||
public plexEnabled: boolean;
|
||||
public embyEnabled: boolean;
|
||||
public ldapEnabled: boolean;
|
||||
public settings: IUserManagementSettings;
|
||||
public claims: ICheckbox[];
|
||||
|
||||
|
@ -37,7 +38,7 @@ export class UserManagementComponent implements OnInit {
|
|||
this.settingsService.getUserManagementSettings().subscribe(x => {
|
||||
this.settings = x;
|
||||
|
||||
if(x.importEmbyUsers || x.importPlexUsers) {
|
||||
if(x.importEmbyUsers || x.importPlexUsers || x.importLdapUsers) {
|
||||
this.enableImportButton = true;
|
||||
}
|
||||
|
||||
|
@ -80,6 +81,7 @@ export class UserManagementComponent implements OnInit {
|
|||
});
|
||||
this.settingsService.getPlex().subscribe(x => this.plexEnabled = x.enable);
|
||||
this.settingsService.getEmby().subscribe(x => this.embyEnabled = x.enable);
|
||||
this.settingsService.getLdap().subscribe(x => this.ldapEnabled = x.isEnabled);
|
||||
}
|
||||
|
||||
public submit(): void {
|
||||
|
@ -115,6 +117,7 @@ export class UserManagementComponent implements OnInit {
|
|||
|
||||
this.jobService.runPlexImporter().subscribe();
|
||||
this.jobService.runEmbyImporter().subscribe();
|
||||
this.jobService.runLdapImporter().subscribe();
|
||||
}
|
||||
|
||||
private filter(query: string, users: IUsersModel[]): IUsersModel[] {
|
||||
|
|
|
@ -87,7 +87,8 @@
|
|||
<td mat-cell *matCellDef="let u">
|
||||
<span *ngIf="u.userType === 1">Local User</span>
|
||||
<span *ngIf="u.userType === 2">Plex User</span>
|
||||
<span *ngIf="u.userType === 3">Emby User</span> </td>
|
||||
<span *ngIf="u.userType === 3">Emby User</span>
|
||||
<span *ngIf="u.userType === 5">LDAP User</span> </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="roles">
|
||||
|
|
|
@ -9,6 +9,7 @@ using Ombi.Schedule.Jobs.Emby;
|
|||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Schedule.Jobs.Plex;
|
||||
using Ombi.Schedule.Jobs.Radarr;
|
||||
using Ombi.Schedule.Jobs.Ldap;
|
||||
using Quartz;
|
||||
|
||||
namespace Ombi.Controllers.V1
|
||||
|
@ -102,6 +103,17 @@ namespace Ombi.Controllers.V1
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the LDAP User importer
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("ldapuserimporter")]
|
||||
public async Task<bool> LdapUserImporter()
|
||||
{
|
||||
await OmbiQuartz.TriggerJob(nameof(ILdapUserImporter), "LDAP");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the Plex Content Cacher
|
||||
/// </summary>
|
||||
|
|
|
@ -426,6 +426,28 @@ namespace Ombi.Controllers.V1
|
|||
return await Get<AuthenticationSettings>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the LDAP settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("ldap")]
|
||||
public async Task<bool> LdapSettings([FromBody] LdapSettings settings)
|
||||
{
|
||||
return await Save(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LDAP Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("ldap")]
|
||||
[AllowAnonymous]
|
||||
public async Task<LdapSettings> LdapSettings()
|
||||
{
|
||||
return await Get<LdapSettings>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the Radarr settings.
|
||||
/// </summary>
|
||||
|
|
|
@ -48,22 +48,12 @@ namespace Ombi.Controllers.V1
|
|||
{
|
||||
if (!model.UsePlexOAuth)
|
||||
{
|
||||
var user = await _userManager.FindByNameAsync(model.Username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
// Could this be an email login?
|
||||
user = await _userManager.FindByEmailAsync(model.Username);
|
||||
|
||||
var user = await _userManager.FindUser(model.Username);
|
||||
if (user == null)
|
||||
{
|
||||
return new UnauthorizedResult();
|
||||
}
|
||||
|
||||
user.EmailLogin = true;
|
||||
}
|
||||
|
||||
|
||||
// Verify Password
|
||||
if (await _userManager.CheckPasswordAsync(user, model.Password))
|
||||
{
|
||||
|
@ -184,18 +174,12 @@ namespace Ombi.Controllers.V1
|
|||
var account = await _plexOAuthManager.GetAccount(accessToken);
|
||||
|
||||
// Get the ombi user
|
||||
var user = await _userManager.FindByNameAsync(account.user.username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
// Could this be an email login?
|
||||
user = await _userManager.FindByEmailAsync(account.user.email);
|
||||
var user = await _userManager.FindUser(account.user.username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return new UnauthorizedResult();
|
||||
}
|
||||
}
|
||||
|
||||
return await CreateToken(true, user);
|
||||
}
|
||||
|
@ -225,18 +209,12 @@ namespace Ombi.Controllers.V1
|
|||
[HttpPost("requirePassword")]
|
||||
public async Task<bool> DoesUserRequireAPassword([FromBody] UserAuthModel model)
|
||||
{
|
||||
var user = await _userManager.FindByNameAsync(model.Username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
// Could this be an email login?
|
||||
user = await _userManager.FindByEmailAsync(model.Username);
|
||||
var user = await _userManager.FindUser(model.Username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var requires = await _userManager.RequiresPassword(user);
|
||||
return requires;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue