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 8313f359b..eed8c6b38 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.Plex; using Ombi.Api.Plex.Models; @@ -49,19 +51,52 @@ namespace Ombi.Core.Authentication IPasswordHasher passwordHasher, IEnumerable> userValidators, IEnumerable> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger> logger, IPlexApi plexApi, - IEmbyApiFactory embyApi, ISettingsService embySettings, ISettingsService auth) + IEmbyApiFactory embyApi, ISettingsService embySettings, ISettingsService auth, + ILdapUserManager ldapUserManager, ISettingsService 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; 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) { @@ -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 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; + } } } \ No newline at end of file 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 4f987cea3..c404a8258 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 50189aa40..9db477e9b 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -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(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -218,6 +220,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 4a91052c7..28b3daa5e 100644 --- a/src/Ombi.Schedule/OmbiScheduler.cs +++ b/src/Ombi.Schedule/OmbiScheduler.cs @@ -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(nameof(INotificationService), "Notifications", null); } + private static async Task AddLdap(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(ILdapUserImporter), "LDAP", JobSettingsHelper.UserImporter(s)); + } } } \ No newline at end of file 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 17002a2a7..503fc3fa5 100644 --- a/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs +++ b/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs @@ -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 DefaultRoles { get; set; } = new List(); diff --git a/src/Ombi.Store/Entities/User.cs b/src/Ombi.Store/Entities/User.cs index b53af5a33..82ab25b45 100644 --- a/src/Ombi.Store/Entities/User.cs +++ b/src/Ombi.Store/Entities/User.cs @@ -34,5 +34,6 @@ namespace Ombi.Store.Entities PlexUser = 2, EmbyUser = 3, EmbyConnectUser = 4, + LdapUser = 5, } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index 03523ca2e..8e48e1e31 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -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; diff --git a/src/Ombi/ClientApp/src/app/services/job.service.ts b/src/Ombi/ClientApp/src/app/services/job.service.ts index 2e991d941..258b5564a 100644 --- a/src/Ombi/ClientApp/src/app/services/job.service.ts +++ b/src/Ombi/ClientApp/src/app/services/job.service.ts @@ -31,6 +31,10 @@ export class JobService extends ServiceHelpers { return this.http.post(`${this.url}embyUserImporter/`, {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 2d32bbcba..7e75d0396 100644 --- a/src/Ombi/ClientApp/src/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/src/app/services/settings.service.ts @@ -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(`${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 58ceb9c64..8244e7d56 100644 --- a/src/Ombi/ClientApp/src/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/src/app/settings/settings.module.ts @@ -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, diff --git a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html index 20b843139..b6aca3554 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 c8365eead..ba49b9ef0 100644 --- a/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html @@ -13,13 +13,13 @@ Import Plex Users - +
Import Plex Admin

Plex Users excluded from Import

- +
@@ -27,14 +27,20 @@
Import Emby Users
- +

Emby Users excluded from Import

- +
+ +
+
+ Import LDAP Users +
+

Default Roles

@@ -71,7 +77,7 @@
-
+
\ No newline at end of file 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 00483e4f2..d60cb6296 100644 --- a/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.ts @@ -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 { @@ -89,7 +91,7 @@ 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); - + if(this.settings.importEmbyUsers || this.settings.importPlexUsers) { this.enableImportButton = true; } @@ -112,9 +114,10 @@ export class UserManagementComponent implements OnInit { } public runImporter(): void { - + this.jobService.runPlexImporter().subscribe(); this.jobService.runEmbyImporter().subscribe(); + this.jobService.runLdapImporter().subscribe(); } 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 60ecb6438..482de17e1 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')} }}
@@ -73,10 +73,10 @@
Last Logged In - + {{u.lastLoggedIn | amLocal | amDateFormat: 'l LT'}} - + Not logged in yet! @@ -87,12 +87,13 @@ Local User Plex User - Emby User + Emby User + LDAP User - + Roles - +
{{claim.value}}
@@ -112,7 +113,7 @@
- + @@ -146,7 +147,7 @@ - + diff --git a/src/Ombi/Controllers/V1/JobController.cs b/src/Ombi/Controllers/V1/JobController.cs index 6ffbf8486..82fe6f3b8 100644 --- a/src/Ombi/Controllers/V1/JobController.cs +++ b/src/Ombi/Controllers/V1/JobController.cs @@ -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; } + /// + /// 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 32cafd904..d1d282b31 100644 --- a/src/Ombi/Controllers/V1/SettingsController.cs +++ b/src/Ombi/Controllers/V1/SettingsController.cs @@ -426,6 +426,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 d706434f8..79a191f47 100644 --- a/src/Ombi/Controllers/V1/TokenController.cs +++ b/src/Ombi/Controllers/V1/TokenController.cs @@ -48,22 +48,12 @@ 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? - user = await _userManager.FindByEmailAsync(model.Username); - - if (user == null) - { - return new UnauthorizedResult(); - } - - user.EmailLogin = true; + return new UnauthorizedResult(); } - // Verify Password if (await _userManager.CheckPasswordAsync(user, model.Password)) { @@ -184,17 +174,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); @@ -225,17 +209,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);