diff --git a/src/Ombi.Core/Authentication/LdapUserManager.cs b/src/Ombi.Core/Authentication/LdapUserManager.cs new file mode 100644 index 000000000..09a475b10 --- /dev/null +++ b/src/Ombi.Core/Authentication/LdapUserManager.cs @@ -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 +{ + /// + /// Ldap Authentication Provider + /// + public class LdapUserManager : ILdapUserManager + { + public LdapUserManager(ILogger logger, ISettingsService ldapSettings) + { + _ldapSettingsService = ldapSettings; + _logger = logger; + } + + private readonly ISettingsService _ldapSettingsService; + private readonly ILogger _logger; + + public async Task GetSettings() + { + return await _ldapSettingsService.GetSettingsAsync(); + } + + public async Task 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 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 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 GetLdapUsers() + { + var settings = await GetSettings(); + + using var ldapClient = await BindLdapConnection(settings.BindUserDn, settings.BindUserPassword); + + return await SearchLdapUsers(ldapClient); + } + + public async Task 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; + } + + /// + /// Authenticate user against the ldap server. + /// + /// Username to authenticate. + /// Password to authenticate. + public async Task 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; + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Authentication/OmbiUserManager.cs b/src/Ombi.Core/Authentication/OmbiUserManager.cs index 87f82c1de..85b4b5ba3 100644 --- a/src/Ombi.Core/Authentication/OmbiUserManager.cs +++ b/src/Ombi.Core/Authentication/OmbiUserManager.cs @@ -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.Jellyfin; using Ombi.Api.Plex; @@ -50,9 +52,14 @@ namespace Ombi.Core.Authentication IPasswordHasher passwordHasher, IEnumerable> userValidators, IEnumerable> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger> logger, IPlexApi plexApi, +<<<<<<< HEAD IEmbyApiFactory embyApi, ISettingsService embySettings, IJellyfinApiFactory jellyfinApi, ISettingsService jellyfinSettings, ISettingsService auth) +======= + IEmbyApiFactory embyApi, ISettingsService embySettings, ISettingsService auth, + ILdapUserManager ldapUserManager, ISettingsService userManagementSettings) +>>>>>>> 691c70804f203fab858b1079a1cf3d5e4adbf322 : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) { _plexApi = plexApi; @@ -61,14 +68,49 @@ namespace Ombi.Core.Authentication _embySettings = embySettings; _jellyfinSettings = jellyfinSettings; _authSettings = auth; + _ldapUserManager = ldapUserManager; + _userManagementSettings = userManagementSettings; } private readonly IPlexApi _plexApi; private readonly IEmbyApiFactory _embyApi; +<<<<<<< HEAD private readonly IJellyfinApiFactory _jellyfinApi; +======= + private readonly ILdapUserManager _ldapUserManager; +>>>>>>> 691c70804f203fab858b1079a1cf3d5e4adbf322 private readonly ISettingsService _embySettings; private readonly ISettingsService _jellyfinSettings; private readonly ISettingsService _authSettings; + private readonly ISettingsService _userManagementSettings; + + public async Task 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 CheckPasswordAsync(OmbiUser user, string password) { @@ -90,9 +132,15 @@ namespace Ombi.Core.Authentication { return await CheckEmbyPasswordAsync(user, password); } +<<<<<<< HEAD if (user.UserType == UserType.JellyfinUser) { return await CheckJellyfinPasswordAsync(user, password); +======= + if (user.UserType == UserType.LdapUser) + { + return await CheckLdapPasswordAsync(user, password); +>>>>>>> 691c70804f203fab858b1079a1cf3d5e4adbf322 } return false; } @@ -227,5 +275,41 @@ namespace Ombi.Core.Authentication } return false; } + private async Task CheckLdapPasswordAsync(OmbiUser user, string password) + { + var ldapSettings = await _ldapUserManager.GetSettings(); + if (!ldapSettings.IsEnabled) + { + return false; + } + + return await _ldapUserManager.Authenticate(user, password); + } + + public async Task 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; + } } } diff --git a/src/Ombi.Core/ILdapUserManager.cs b/src/Ombi.Core/ILdapUserManager.cs new file mode 100644 index 000000000..bf9ce7654 --- /dev/null +++ b/src/Ombi.Core/ILdapUserManager.cs @@ -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 GetSettings(); + + Task Authenticate(OmbiUser user, string password); + + Task GetLdapUsers(); + + Task LocateLdapUser(string username); + + Task LdapEntryToOmbiUser(LdapEntry entry); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index 8fa0f976f..f84517aaa 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index ef9c9230c..173107a9d 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -52,6 +52,7 @@ using Ombi.Schedule.Jobs.Jellyfin; 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; @@ -101,6 +102,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -231,6 +233,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Ombi.Schedule/Jobs/Ldap/ILdapUserImporter.cs b/src/Ombi.Schedule/Jobs/Ldap/ILdapUserImporter.cs new file mode 100644 index 000000000..1680aa18e --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ldap/ILdapUserImporter.cs @@ -0,0 +1,7 @@ + +namespace Ombi.Schedule.Jobs.Ldap +{ + public interface ILdapUserImporter : IBaseJob + { + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ldap/LdapUserImporter.cs b/src/Ombi.Schedule/Jobs/Ldap/LdapUserImporter.cs new file mode 100644 index 000000000..4c2114c0f --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ldap/LdapUserImporter.cs @@ -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, ISettingsService ums, IHubContext notification) + { + _userManager = userManager; + _ldapUserManager = ldapUserManager; + _ldapSettings = ldapSettings; + _userManagementSettings = ums; + _notification = notification; + } + + private readonly OmbiUserManager _userManager; + private readonly ILdapUserManager _ldapUserManager; + private readonly ISettingsService _ldapSettings; + private readonly ISettingsService _userManagementSettings; + private readonly IHubContext _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); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/OmbiScheduler.cs b/src/Ombi.Schedule/OmbiScheduler.cs index 6c54895d6..baabab1c0 100644 --- a/src/Ombi.Schedule/OmbiScheduler.cs +++ b/src/Ombi.Schedule/OmbiScheduler.cs @@ -8,6 +8,7 @@ using Ombi.Schedule.Jobs; using Ombi.Schedule.Jobs.Couchpotato; using Ombi.Schedule.Jobs.Emby; using Ombi.Schedule.Jobs.Jellyfin; +using Ombi.Schedule.Jobs.Ldap; using Ombi.Schedule.Jobs.Lidarr; using Ombi.Schedule.Jobs.Ombi; using Ombi.Schedule.Jobs.Plex; @@ -56,6 +57,7 @@ namespace Ombi.Schedule await AddDvrApps(s); await AddSystem(s); await AddNotifications(s); + await AddLdap(s); // Run Quartz await OmbiQuartz.Start(); @@ -113,5 +115,9 @@ namespace Ombi.Schedule { await OmbiQuartz.Instance.AddJob(nameof(INotificationService), "Notifications", null); } + private static async Task AddLdap(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(ILdapUserImporter), "LDAP", JobSettingsHelper.UserImporter(s)); + } } } diff --git a/src/Ombi.Settings/Settings/Models/LdapSettings.cs b/src/Ombi.Settings/Settings/Models/LdapSettings.cs new file mode 100644 index 000000000..3cf4b25c1 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/LdapSettings.cs @@ -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; + } + + /// + /// Gets or sets whether LDAP is enabled. + /// + public bool IsEnabled { get; set; } + + /// + /// Gets or sets whether users should be automatically created at login. + /// + public bool CreateUsersAtLogin { get; set; } + + /// + /// Gets or sets the ldap server ip or url. + /// + public string Hostname { get; set; } + + /// + /// Gets or sets the ldap base search dn. + /// + public string BaseDn { get; set; } + + /// + /// Gets or sets the ldap port. + /// + public int Port { get; set; } + + /// + /// Gets or sets the ldap username attribute. + /// + public string UsernameAttribute { get; set; } + + /// + /// Gets or sets the ldap user search filter. + /// + public string SearchFilter { get; set; } + + /// + /// Gets or sets the ldap bind user dn. + /// + public string BindUserDn { get; set; } + + /// + /// Gets or sets the ldap bind user password. + /// + public string BindUserPassword { get; set; } + + /// + /// Gets or sets a value indicating whether to use ssl when connecting to the ldap server. + /// + public bool UseSsl { get; set; } + + /// + /// Gets or sets a value indicating whether to use StartTls when connecting to the ldap server. + /// + public bool UseStartTls { get; set; } + + /// + /// Gets or sets a value indicating whether to skip ssl verification. + /// + public bool SkipSslVerify { get; set; } + } +} diff --git a/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs b/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs index ff6cff278..375f2e9ef 100644 --- a/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs +++ b/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs @@ -8,6 +8,7 @@ namespace Ombi.Settings.Settings.Models public bool ImportPlexUsers { get; set; } public bool ImportEmbyUsers { get; set; } public bool ImportJellyfinUsers { get; set; } + public bool ImportLdapUsers { get; set; } public int MovieRequestLimit { get; set; } public int EpisodeRequestLimit { get; set; } public string DefaultStreamingCountry { get; set; } = "US"; diff --git a/src/Ombi.Store/Entities/User.cs b/src/Ombi.Store/Entities/User.cs index 0a3d54970..942d4919e 100644 --- a/src/Ombi.Store/Entities/User.cs +++ b/src/Ombi.Store/Entities/User.cs @@ -35,5 +35,6 @@ namespace Ombi.Store.Entities EmbyUser = 3, EmbyConnectUser = 4, JellyfinUser = 5, + LdapUser = 6, } } diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index c5b29adff..336062193 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -194,6 +194,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; @@ -206,6 +221,7 @@ export interface IUserManagementSettings extends ISettings { importPlexAdmin: boolean; importEmbyUsers: boolean; importJellyfinUsers: boolean; + importLdapUsers: boolean; defaultRoles: string[]; movieRequestLimit: number; episodeRequestLimit: number; diff --git a/src/Ombi/ClientApp/src/app/services/job.service.ts b/src/Ombi/ClientApp/src/app/services/job.service.ts index 24b5e9a52..c28e18ddd 100644 --- a/src/Ombi/ClientApp/src/app/services/job.service.ts +++ b/src/Ombi/ClientApp/src/app/services/job.service.ts @@ -34,6 +34,10 @@ export class JobService extends ServiceHelpers { public runJellyfinImporter(): Observable { return this.http.post(`${this.url}jellyfinUserImporter/`, {headers: this.headers}); } + + public runLdapImporter(): Observable { + return this.http.post(`${this.url}ldapUserImporter/`, {headers: this.headers}); + } public runPlexCacher(): Observable { return this.http.post(`${this.url}plexcontentcacher/`, {headers: this.headers}); diff --git a/src/Ombi/ClientApp/src/app/services/settings.service.ts b/src/Ombi/ClientApp/src/app/services/settings.service.ts index 88a578be3..6801f7093 100644 --- a/src/Ombi/ClientApp/src/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/src/app/services/settings.service.ts @@ -39,6 +39,7 @@ import { IVoteSettings, ITwilioSettings, IWebhookNotificationSettings, + ILdapSettings, } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @@ -133,6 +134,14 @@ export class SettingsService extends ServiceHelpers { return this.http.post(`${this.url}/Authentication`, JSON.stringify(settings), {headers: this.headers}); } + public getLdap(): Observable { + return this.http.get(`${this.url}/ldap`, {headers: this.headers}); + } + + public saveLdap(settings: ILdapSettings): Observable { + return this.http.post(`${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 { return this.http.get(`${this.url}/LandingPage`, {headers: this.headers}); diff --git a/src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.html b/src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.html new file mode 100644 index 000000000..5545410d5 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.html @@ -0,0 +1,78 @@ + +
+ LDAP Settings +
+
+ + LDAP Enabled + + + + Create Users at Login + +
+ +
+ + Hostname + + + + Port + + + + Base DN + + + + + Use SSL + + + Use StartTLS + + + Skip SSL Verification + +
+ +
+ + Bind User DN + + + + Bind User Password + + +
+ +
+ + Username Attribute + + + + + Search Filter + + +
+ +
+ +
+
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.scss b/src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.scss new file mode 100644 index 000000000..e45108db2 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.scss @@ -0,0 +1,9 @@ +.small-middle-container { + margin: auto; + width: 95%; + margin-top: 10px; +} + +mat-checkbox { + margin-right: 10px; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.ts b/src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.ts new file mode 100644 index 000000000..b37b04457 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/ldap/ldap.component.ts @@ -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"); + } + }); + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/settings.module.ts b/src/Ombi/ClientApp/src/app/settings/settings.module.ts index b7f4536b5..869b920e7 100644 --- a/src/Ombi/ClientApp/src/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/src/app/settings/settings.module.ts @@ -48,6 +48,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"; @@ -97,6 +98,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] }, @@ -158,6 +160,7 @@ const routes: Routes = [ TelegramComponent, IssuesComponent, AuthenticationComponent, + LdapComponent, MobileComponent, MassEmailComponent, NewsletterComponent, diff --git a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html index bd6949ebb..825d5847f 100644 --- a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html +++ b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html @@ -7,6 +7,7 @@ + diff --git a/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html index 20bd9a970..bf4a6cb63 100644 --- a/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html @@ -45,7 +45,12 @@ + +
+
+ Import LDAP Users +
@@ -92,7 +97,7 @@
-
+
diff --git a/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.ts b/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.ts index 7b2a5e3e0..b9817a470 100644 --- a/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.ts @@ -12,7 +12,11 @@ export class UserManagementComponent implements OnInit { public plexEnabled: boolean; public embyEnabled: boolean; +<<<<<<< HEAD public jellyfinEnabled: boolean; +======= + public ldapEnabled: boolean; +>>>>>>> 691c70804f203fab858b1079a1cf3d5e4adbf322 public settings: IUserManagementSettings; public claims: ICheckbox[]; @@ -45,7 +49,11 @@ export class UserManagementComponent implements OnInit { this.settingsService.getUserManagementSettings().subscribe(x => { this.settings = x; +<<<<<<< HEAD if(x.importEmbyUsers || x.importJellyfinUsers || x.importPlexUsers) { +======= + if(x.importEmbyUsers || x.importPlexUsers || x.importLdapUsers) { +>>>>>>> 691c70804f203fab858b1079a1cf3d5e4adbf322 this.enableImportButton = true; } @@ -100,7 +108,11 @@ export class UserManagementComponent implements OnInit { }); this.settingsService.getPlex().subscribe(x => this.plexEnabled = x.enable); this.settingsService.getEmby().subscribe(x => this.embyEnabled = x.enable); +<<<<<<< HEAD this.settingsService.getJellyfin().subscribe(x => this.jellyfinEnabled = x.enable); +======= + this.settingsService.getLdap().subscribe(x => this.ldapEnabled = x.isEnabled); +>>>>>>> 691c70804f203fab858b1079a1cf3d5e4adbf322 } public submit(): void { @@ -110,9 +122,14 @@ export class UserManagementComponent implements OnInit { this.settings.defaultRoles = enabledClaims.map((claim) => claim.value); this.settings.bannedPlexUserIds = this.bannedPlexUsers.map((u) => u.id); this.settings.bannedEmbyUserIds = this.bannedEmbyUsers.map((u) => u.id); +<<<<<<< HEAD this.settings.bannedJellyfinUserIds = this.bannedJellyfinUsers.map((u) => u.id); if(this.settings.importEmbyUsers || this.settings.importJellyfinUsers || this.settings.importPlexUsers) { +======= + + if(this.settings.importEmbyUsers || this.settings.importPlexUsers) { +>>>>>>> 691c70804f203fab858b1079a1cf3d5e4adbf322 this.enableImportButton = true; } @@ -138,10 +155,14 @@ export class UserManagementComponent implements OnInit { } public runImporter(): void { - + this.jobService.runPlexImporter().subscribe(); this.jobService.runEmbyImporter().subscribe(); +<<<<<<< HEAD this.jobService.runJellyfinImporter().subscribe(); +======= + this.jobService.runLdapImporter().subscribe(); +>>>>>>> 691c70804f203fab858b1079a1cf3d5e4adbf322 } private filter(query: string, users: IUsersModel[]): IUsersModel[] { diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html index 1dcbf29c0..53b2bd262 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html @@ -31,12 +31,12 @@ Username {{element.userName}} - + Alias {{element.alias}} - + Email {{element.emailAddress}} @@ -44,7 +44,7 @@ Requests Remaining - +
{{'UserManagment.MovieRemaining' | translate: {remaining: u.movieRequestQuota.remaining, total: u.movieRequestLimit} }}
@@ -59,7 +59,7 @@ Next Request Due - +
{{'UserManagment.MovieDue' | translate: {date: (u.movieRequestQuota.nextRequest | amLocal | amDateFormat: 'l LT')} }}
@@ -90,6 +90,7 @@ Emby User Emby Connect User Jellyfin User + LDAP User
@@ -136,6 +137,7 @@ +<<<<<<< HEAD Movie Request Limit @@ -159,6 +161,23 @@ +======= +
+ +
+ +
+
+ +
+ +
+ +
+
+ + +>>>>>>> 691c70804f203fab858b1079a1cf3d5e4adbf322 diff --git a/src/Ombi/Controllers/V1/JobController.cs b/src/Ombi/Controllers/V1/JobController.cs index e182e1239..31b529aaf 100644 --- a/src/Ombi/Controllers/V1/JobController.cs +++ b/src/Ombi/Controllers/V1/JobController.cs @@ -11,6 +11,7 @@ using Ombi.Schedule.Jobs.Ombi; using Ombi.Schedule.Jobs.Plex; using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Schedule.Jobs.Radarr; +using Ombi.Schedule.Jobs.Ldap; using Quartz; namespace Ombi.Controllers.V1 @@ -115,6 +116,16 @@ namespace Ombi.Controllers.V1 return true; } + /// Runs the LDAP User importer + /// + /// + [HttpPost("ldapuserimporter")] + public async Task LdapUserImporter() + { + await OmbiQuartz.TriggerJob(nameof(ILdapUserImporter), "LDAP"); + return true; + } + /// /// Runs the Plex Content Cacher /// diff --git a/src/Ombi/Controllers/V1/SettingsController.cs b/src/Ombi/Controllers/V1/SettingsController.cs index 9e7343472..7fbd370a2 100644 --- a/src/Ombi/Controllers/V1/SettingsController.cs +++ b/src/Ombi/Controllers/V1/SettingsController.cs @@ -467,6 +467,28 @@ namespace Ombi.Controllers.V1 return await Get(); } + /// + /// Save the LDAP settings. + /// + /// The settings. + /// + [HttpPost("ldap")] + public async Task LdapSettings([FromBody] LdapSettings settings) + { + return await Save(settings); + } + + /// + /// Gets the LDAP Settings. + /// + /// + [HttpGet("ldap")] + [AllowAnonymous] + public async Task LdapSettings() + { + return await Get(); + } + /// /// Save the Radarr settings. /// diff --git a/src/Ombi/Controllers/V1/TokenController.cs b/src/Ombi/Controllers/V1/TokenController.cs index f9ea57a5c..43f7003cd 100644 --- a/src/Ombi/Controllers/V1/TokenController.cs +++ b/src/Ombi/Controllers/V1/TokenController.cs @@ -51,8 +51,7 @@ namespace Ombi.Controllers.V1 { if (!model.UsePlexOAuth) { - var user = await _userManager.FindByNameAsync(model.Username); - + var user = await _userManager.FindUser(model.Username); if (user == null) { // Could this be an email login? @@ -67,7 +66,6 @@ namespace Ombi.Controllers.V1 user.EmailLogin = true; } - // Verify Password if (await _userManager.CheckPasswordAsync(user, model.Password)) { @@ -187,17 +185,11 @@ namespace Ombi.Controllers.V1 var account = await _plexOAuthManager.GetAccount(accessToken); // Get the ombi user - var user = await _userManager.FindByNameAsync(account.user.username); + var user = await _userManager.FindUser(account.user.username); if (user == null) { - // Could this be an email login? - user = await _userManager.FindByEmailAsync(account.user.email); - - if (user == null) - { - return new UnauthorizedResult(); - } + return new UnauthorizedResult(); } return await CreateToken(true, user); @@ -228,17 +220,11 @@ namespace Ombi.Controllers.V1 [HttpPost("requirePassword")] public async Task DoesUserRequireAPassword([FromBody] UserAuthModel model) { - var user = await _userManager.FindByNameAsync(model.Username); + var user = await _userManager.FindUser(model.Username); if (user == null) { - // Could this be an email login? - user = await _userManager.FindByEmailAsync(model.Username); - - if (user == null) - { - return true; - } + return true; } var requires = await _userManager.RequiresPassword(user);