Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add User Settings Tests #101

Merged
merged 10 commits into from
Dec 5, 2021
131 changes: 128 additions & 3 deletions LDAP-Auth/Api/LdapController.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mime;
using System.Text.RegularExpressions;
using Jellyfin.Plugin.LDAP_Auth.Api.Models;
using MediaBrowser.Common;
using MediaBrowser.Controller.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Novell.Directory.Ldap;

namespace Jellyfin.Plugin.LDAP_Auth.Api
{
Expand All @@ -15,6 +22,17 @@ namespace Jellyfin.Plugin.LDAP_Auth.Api
[Produces(MediaTypeNames.Application.Json)]
public class LdapController : ControllerBase
{
private readonly LdapAuthenticationProviderPlugin _ldapAuthenticationProvider;

/// <summary>
/// Initializes a new instance of the <see cref="LdapController"/> class.
/// </summary>
/// <param name="appHost">The application host to get the LDAP Authentication Provider from.</param>
public LdapController(IApplicationHost appHost)
{
_ldapAuthenticationProvider = appHost.GetExports<LdapAuthenticationProviderPlugin>(false).First();
}

/// <summary>
/// Tests the server connection and bind settings.
/// </summary>
Expand All @@ -25,7 +43,7 @@ public class LdapController : ControllerBase
/// <response code="400">Body is missing required data.</response>
/// <param name="body">The request body.</param>
/// <returns>
/// A <see cref="OkResult"/> containing the connection results if able to test,
/// An <see cref="OkResult"/> containing the connection results if able to test,
/// or a <see cref="BadRequestResult"/> if the request body is missing data.
/// </returns>
[HttpPost("TestServerBind")]
Expand All @@ -44,9 +62,116 @@ public IActionResult TestServerBind([FromBody] ServerConnectionInfo body)
configuration.LdapBaseDn = body.LdapBaseDn;
LdapPlugin.Instance.UpdateConfiguration(configuration);

var result = LdapAuthenticationProviderPlugin.TestServerBind();
return Ok(_ldapAuthenticationProvider.TestServerBind());
}

/// <summary>
/// Tests the LDAP user and admin filters.
/// </summary>
/// <remarks>
/// Accepts server connection configuration as JSON body.
/// </remarks>
/// <response code="200">Filters were queried.</response>
/// <response code="400">Body is missing required data or filter is invalid.</response>
/// <response code="401">Failed to connect to LDAP server.</response>
/// <param name="body">The request body.</param>
/// <returns>
/// An <see cref="OkResult"/> containing the connection results if able to test,
/// an <see cref="UnauthorizedResult"/> if unable to connect to the LDAP server,
/// or a <see cref="BadRequestResult"/> if the request body is missing data or filter is invalid.
/// </returns>
[HttpPost("TestLdapFilters")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public IActionResult TestLdapFilters([FromBody] UserFilterInfo body)
{
var configuration = LdapPlugin.Instance.Configuration;
configuration.LdapSearchFilter = body.LdapSearchFilter;
configuration.LdapAdminFilter = body.LdapAdminFilter;
LdapPlugin.Instance.UpdateConfiguration(configuration);

var usersComplete = false;
try
{
var response = new LdapFilterResponse();

var users = _ldapAuthenticationProvider.GetFilteredUsers(configuration.LdapSearchFilter).ToHashSet();
response.Users = users.Count;
usersComplete = true;

HashSet<string> admins = new HashSet<string>();
if (!string.IsNullOrEmpty(configuration.LdapAdminFilter) && !string.Equals(configuration.LdapAdminFilter, "_disabled_", StringComparison.Ordinal))
{
admins = _ldapAuthenticationProvider.GetFilteredUsers(configuration.LdapAdminFilter).ToHashSet();
}

response.Admins = admins.Count;
response.IsSubset = admins.IsSubsetOf(users);

return Ok(response);
}
catch (AuthenticationException e)
{
return Unauthorized(new LdapTestErrorResponse(e.Message));
}
catch (LdapException e)
{
var filterLabel = usersComplete ? "Admin Filter: " : "User Filter: ";

var filterMessage = Regex.Match(e.ToString(), @"LdapLocalException: (?<message>.*) \(\d+\) Filter Error");
if (filterMessage.Success)
{
return BadRequest(new LdapTestErrorResponse(filterLabel + filterMessage.Groups["message"].Value));
}

return BadRequest(new LdapTestErrorResponse(filterLabel + e.Message));
}
}

/// <summary>
/// Saves the LDAP search attributes and optionally tests a query string.
/// </summary>
/// <remarks>
/// Accepts search attributes and test query as JSON body.
/// </remarks>
/// <response code="200">No test requested or test completed.</response>
/// <response code="400">Body is missing required data.</response>
/// <response code="401">Failed to connect to LDAP server or user filter is invalid.</response>
/// <param name="body">The request body.</param>
/// <returns>
/// An <see cref="OkResult"/> containing the test results,
/// an <see cref="UnauthorizedResult"/> if unable to connect to the LDAP server or user filter is invalid,
/// or a <see cref="BadRequestResult"/> if the request body is missing data.
/// </returns>
[HttpPost("LdapUserSearch")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public IActionResult LdapUserSearch([FromBody] UserSearchAttributes body)
{
var configuration = LdapPlugin.Instance.Configuration;
configuration.LdapSearchAttributes = body.LdapSearchAttributes;
configuration.EnableCaseInsensitiveUsername = body.EnableCaseInsensitiveUsername;
LdapPlugin.Instance.UpdateConfiguration(configuration);

var response = new UserSearchResponse();
if (string.IsNullOrEmpty(body.TestSearchUsername))
{
return Ok(response);
}

try
{
var user = _ldapAuthenticationProvider.LocateLdapUser(body.TestSearchUsername);
response.LocatedDn = user?.Dn;
}
catch (AuthenticationException e)
{
return Unauthorized(new LdapTestErrorResponse(e.Message));
}

return Ok(result);
return Ok(response);
}
}
}
23 changes: 23 additions & 0 deletions LDAP-Auth/Api/Models/LdapFilterResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Jellyfin.Plugin.LDAP_Auth.Api.Models
{
/// <summary>
/// Response for querying LDAP filters.
/// </summary>
public class LdapFilterResponse
{
/// <summary>
/// Gets or sets the number of users found by the user filter.
/// </summary>
public int Users { get; set; }

/// <summary>
/// Gets or sets the number of users found by the admin filter.
/// </summary>
public int Admins { get; set; }

/// <summary>
/// Gets or sets a value indicating whether admins is a subset of users.
/// </summary>
public bool IsSubset { get; set; }
}
}
22 changes: 22 additions & 0 deletions LDAP-Auth/Api/Models/LdapTestErrorResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Jellyfin.Plugin.LDAP_Auth.Api.Models
{
/// <summary>
/// Error response to pass message to client when LDAP testing fails.
/// </summary>
public class LdapTestErrorResponse
{
/// <summary>
/// Initializes a new instance of the <see cref="LdapTestErrorResponse"/> class.
/// </summary>
/// <param name="message">The error message.</param>
public LdapTestErrorResponse(string message)
{
Message = message;
}

/// <summary>
/// Gets the error message.
/// </summary>
public string Message { get; }
}
}
33 changes: 33 additions & 0 deletions LDAP-Auth/Api/Models/ServerTestResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Jellyfin.Plugin.LDAP_Auth.Api.Models
{
/// <summary>
/// Response for testing server connection.
/// </summary>
public class ServerTestResponse
{
/// <summary>
/// Gets or sets the connect result.
/// </summary>
public string Connect { get; set; }

/// <summary>
/// Gets or sets the Start TLS result.
/// </summary>
public string StartTls { get; set; }

/// <summary>
/// Gets or sets the bind result.
/// </summary>
public string Bind { get; set; }

/// <summary>
/// Gets or sets the Base DN search result.
/// </summary>
public string BaseSearch { get; set; }

/// <summary>
/// Gets or sets the error message.
/// </summary>
public string Error { get; set; }
}
}
22 changes: 22 additions & 0 deletions LDAP-Auth/Api/Models/UserFilterInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using Jellyfin.Plugin.LDAP_Auth.Config;

namespace Jellyfin.Plugin.LDAP_Auth.Api.Models
{
/// <summary>
/// A subset of <see cref="PluginConfiguration"/> containing just the settings for filtering users.
/// </summary>
public class UserFilterInfo
{
/// <summary>
/// Gets or sets the ldap user search filter.
/// </summary>
[Required]
public string LdapSearchFilter { get; set; }

/// <summary>
/// Gets or sets the ldap admin search filter.
/// </summary>
public string LdapAdminFilter { get; set; }
}
}
28 changes: 28 additions & 0 deletions LDAP-Auth/Api/Models/UserSearchAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
using Jellyfin.Plugin.LDAP_Auth.Config;

namespace Jellyfin.Plugin.LDAP_Auth.Api.Models
{
/// <summary>
/// A subset of <see cref="PluginConfiguration"/> containing just the settings for searching for users,
/// as well as an optional string to test the search with.
/// </summary>
public class UserSearchAttributes
{
/// <summary>
/// Gets or sets the ldap search attributes.
/// </summary>
[Required]
public string LdapSearchAttributes { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to use case insensitive username comparison.
/// </summary>
public bool EnableCaseInsensitiveUsername { get; set; }

/// <summary>
/// Gets or sets the username to search for as a test.
/// </summary>
public string TestSearchUsername { get; set; }
}
}
13 changes: 13 additions & 0 deletions LDAP-Auth/Api/Models/UserSearchResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Jellyfin.Plugin.LDAP_Auth.Api.Models
{
/// <summary>
/// Response for querying for user.
/// </summary>
public class UserSearchResponse
{
/// <summary>
/// Gets or sets the located user DN.
/// </summary>
public string LocatedDn { get; set; }
}
}
Loading