Skip to content

Commit 02ce311

Browse files
committed
Added a new field to manage Jellyfin administrator privilege
1 parent abaecae commit 02ce311

10 files changed

+93
-123
lines changed

.github/dependabot.yml

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ updates:
1313
commit-message:
1414
prefix: chore
1515
include: scope
16+
ignore:
17+
- dependency-name: "Jellyfin.Controller"
18+
update-types: ["version-update:semver-patch"]
1619

1720
# Fetch and update latest `github-actions` pkgs
1821
- package-ecosystem: github-actions

.vscode/launch.json

-26
This file was deleted.

.vscode/tasks.json

-41
This file was deleted.

Authelia-Auth.Tests/AuthenticatorTests.cs

+6-4
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ public class AuthenticatorTests
99
[Fact(Skip = "skip")]
1010
public async void AuthenticatorTest()
1111
{
12-
var config = new PluginConfiguration();
13-
config.JellyfinUrl = "http://jellyfin";
14-
config.AutheliaServer = "http://authelia";
12+
var config = new PluginConfiguration
13+
{
14+
JellyfinUrl = "http://jellyfin",
15+
AutheliaServer = "http://authelia"
16+
};
1517

1618
var authenticator = new Authenticator();
1719
var result = await authenticator.Authenticate(config, "test", "test");
18-
Assert.Equal("test", result.DisplayName);
20+
Assert.Equal("test", result.AuthenticationResult.DisplayName);
1921
}
2022
}

Authelia-Auth/Authelia-Auth.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<PropertyGroup>
44
<TargetFramework>net6.0</TargetFramework>
55
<RootNamespace>Jellyfin.Plugin.Authelia_Auth</RootNamespace>
6-
<AssemblyVersion>1.0.12.0</AssemblyVersion>
7-
<FileVersion>1.0.12.0</FileVersion>
6+
<AssemblyVersion>1.0.13.0</AssemblyVersion>
7+
<FileVersion>1.0.13.0</FileVersion>
88
<GenerateDocumentationFile>true</GenerateDocumentationFile>
99
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
1010
<ProduceReferenceAssemblyInOutDir>true</ProduceReferenceAssemblyInOutDir>

Authelia-Auth/AutheliaAuthenticationProviderPlugin.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Security.Cryptography;
33
using System.Threading.Tasks;
44
using Jellyfin.Data.Entities;
5+
using Jellyfin.Data.Enums;
56
using MediaBrowser.Common;
67
using MediaBrowser.Controller.Authentication;
78
using MediaBrowser.Controller.Library;
@@ -56,7 +57,7 @@ public async Task<ProviderAuthenticationResult> Authenticate(string username, st
5657

5758
var auth = await new Authenticator().Authenticate(config, username, password);
5859

59-
User user = null;
60+
User user;
6061
try
6162
{
6263
user = userManager.GetUserByName(username);
@@ -76,7 +77,13 @@ public async Task<ProviderAuthenticationResult> Authenticate(string username, st
7677
user.Password = _cryptoProvider.CreatePasswordHash(Convert.ToBase64String(RandomNumberGenerator.GetBytes(64))).ToString();
7778
}
7879

79-
return auth;
80+
// Only manage admin permissions if the admin group is set in config
81+
if (!string.IsNullOrWhiteSpace(config.AutheliaAdminGroup))
82+
{
83+
user.SetPermission(PermissionKind.IsAdministrator, auth.IsAdmin);
84+
}
85+
86+
return auth.AuthenticationResult;
8087
}
8188

8289
/// <inheritdoc />

Authelia-Auth/Authenticator.cs

+31-28
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
using System;
2+
using System.Linq;
23
using System.Net;
34
using System.Net.Http;
4-
using System.Net.Http.Json;
55
using System.Security.Cryptography.X509Certificates;
66
using System.Text;
77
using System.Text.Json.Nodes;
8-
using System.Text.Json.Serialization;
98
using System.Threading.Tasks;
109
using Jellyfin.Plugin.Authelia_Auth.Config;
1110
using MediaBrowser.Controller.Authentication;
@@ -15,27 +14,19 @@ namespace Jellyfin.Plugin.Authelia_Auth
1514
#pragma warning disable SA1649
1615
#pragma warning disable SA1402
1716
/// <summary>
18-
/// Response data field.
17+
/// AutheliaUser is ProviderAuthenticationResult enriched with group information.
1918
/// </summary>
20-
public class UserInfoResponseData
19+
public class AutheliaUser
2120
{
2221
/// <summary>
23-
/// Gets users full name.
22+
/// Gets ProviderAuthenticationResult.
2423
/// </summary>
25-
[JsonPropertyName("display_name")]
26-
public string DisplayName { get; init; }
27-
}
24+
public ProviderAuthenticationResult AuthenticationResult { get; init; }
2825

29-
/// <summary>
30-
/// User info response.
31-
/// </summary>
32-
public class UserInfoResponse
33-
{
3426
/// <summary>
35-
/// Gets user info response data.
27+
/// Gets a value indicating whether a user has admin privileges .
3628
/// </summary>
37-
[JsonPropertyName("data")]
38-
public UserInfoResponseData Data { get; init; }
29+
public bool IsAdmin { get; init; }
3930
}
4031
#pragma warning restore SA1649
4132
#pragma warning restore SA1402
@@ -53,7 +44,7 @@ public class Authenticator
5344
/// <param name="password">Password to authenticate.</param>
5445
/// <returns>A <see cref="ProviderAuthenticationResult"/> with the authentication result.</returns>
5546
/// <exception cref="AuthenticationException">Exception when failing to authenticate.</exception>
56-
public async Task<ProviderAuthenticationResult> Authenticate(PluginConfiguration config, string username, string password)
47+
public async Task<AutheliaUser> Authenticate(PluginConfiguration config, string username, string password)
5748
{
5849
var cookieContainer = new CookieContainer();
5950
using var handler = new HttpClientHandler()
@@ -100,22 +91,34 @@ public async Task<ProviderAuthenticationResult> Authenticate(PluginConfiguration
10091
{
10192
throw new AuthenticationException("User doesn't have access to this service.");
10293
}
103-
}
10494

105-
try
106-
{
107-
var userInfoResponse = await client.GetFromJsonAsync<UserInfoResponse>("/api/user/info");
95+
var isAdmin = false;
96+
var displayName = string.Empty;
97+
98+
if (accessResponse.Headers.TryGetValues("Remote-Groups", out var groups))
99+
{
100+
isAdmin = groups.FirstOrDefault().Split(",").Any(e => e == config.AutheliaAdminGroup);
101+
}
108102

109-
return new ProviderAuthenticationResult
103+
if (accessResponse.Headers.TryGetValues("Remote-Name", out var names))
110104
{
111-
Username = username,
112-
DisplayName = userInfoResponse.Data.DisplayName,
105+
displayName = names.First();
106+
}
107+
else
108+
{
109+
throw new AuthenticationException("Authelia didn't return a Remote-Name header.");
110+
}
111+
112+
return new AutheliaUser
113+
{
114+
AuthenticationResult = new ProviderAuthenticationResult
115+
{
116+
Username = username,
117+
DisplayName = displayName,
118+
},
119+
IsAdmin = isAdmin
113120
};
114121
}
115-
catch
116-
{
117-
throw new AuthenticationException("Invalid username or password.");
118-
}
119122
}
120123
}
121124
}

Authelia-Auth/Config/PluginConfiguration.cs

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public PluginConfiguration()
1515
AutheliaServer = "http://authelia";
1616
JellyfinUrl = "http://jellyfin";
1717
AutheliaRootCa = string.Empty;
18+
AutheliaAdminGroup = string.Empty;
1819
CreateUserIfNotExists = true;
1920
}
2021

@@ -28,6 +29,11 @@ public PluginConfiguration()
2829
/// </summary>
2930
public string AutheliaRootCa { get; set; }
3031

32+
/// <summary>
33+
/// Gets or sets the Authelia group name for admin users.
34+
/// </summary>
35+
public string AutheliaAdminGroup { get; set; }
36+
3137
/// <summary>
3238
/// Gets or sets a value indicating whether a user will be created on successful authentication if it does not exist in Jellyfin.
3339
/// </summary>

Authelia-Auth/Config/configPage.html

+32-15
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<div class="sectionTitleContainer flex align-items-center">
1919
<h2 class="sectionTitle">Authelia Settings:</h2>
2020
</div>
21-
<p><i>Note:</i> Making changes to this configuration requires a restart of Jellyfin.</p>
21+
<p><i>Note:</i> Making changes to this configuration does not require restarting Jellyfin.</p>
2222
<div
2323
class="verticalSection"
2424
title="Authelia Settings"
@@ -27,7 +27,7 @@ <h2 class="sectionTitle">Authelia Settings:</h2>
2727
<input
2828
is="emby-input"
2929
type="text"
30-
id="txtAutheliaServer"
30+
id="AutheliaServer"
3131
required
3232
placeholder="authelia"
3333
label="Authelia Server:"
@@ -41,22 +41,19 @@ <h2 class="sectionTitle">Authelia Settings:</h2>
4141
is="emby-textarea"
4242
class="emby-textarea"
4343
type="text"
44-
id="txtAutheliaRootCa"
44+
id="AutheliaRootCa"
4545
placeholder="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
4646
rows="3"
4747
></textarea>
4848
</label>
49-
<div class="fieldDescription">
50-
<div>The root CA for the Authelia server you wish to use for Authentication.</div>
51-
<div>Leave this field empty unless you use a self-signed certificate for Authelia.</div>
52-
</div>
49+
<div class="fieldDescription">The root CA for the Authelia server you wish to use for Authentication. Leave this field empty unless you use a self-signed certificate for Authelia.</div>
5350
</div>
5451
<div class="checkboxContainer checkboxContainer-withDescription">
5552
<label>
5653
<input
5754
is="emby-checkbox"
5855
type="checkbox"
59-
id="boolCreateUserIfNotExists"
56+
id="CreateUserIfNotExists"
6057
/>
6158
<span>Create a new user on successful login</span>
6259
</label>
@@ -66,7 +63,23 @@ <h2 class="sectionTitle">Authelia Settings:</h2>
6663
<input
6764
is="emby-input"
6865
type="text"
69-
id="txtJellyfinUrl"
66+
id="AutheliaAdminGroup"
67+
label="Authelia admin group name:"
68+
/>
69+
<div class="fieldDescription">
70+
<p>If not blank, this plugin will actively manage users administrator permissions in Jellyfin. On every successful log in this plugin will check if a user is a member of this group in Authelia:</p>
71+
<ul>
72+
<li>If a user is a member of this group, they will be given administrator permissions in Jellyfin.</li>
73+
<li>If a user is not a member of this group, the administrator permission will be revoked in Jellyfin.</li>
74+
<li>If left blank, this plugin will not alter administrator permissions in Jellyfin.</li>
75+
</ul>
76+
</div>
77+
</div>
78+
<div class="inputContainer">
79+
<input
80+
is="emby-input"
81+
type="text"
82+
id="JellyfinUrl"
7083
required
7184
placeholder="jellyfin"
7285
label="Jellyfin Url:"
@@ -118,22 +131,27 @@ <h2 class="sectionTitle">Authelia Settings:</h2>
118131
.emby-textarea + .fieldDescription {
119132
margin-top: 0.25em;
120133
}
134+
.fieldDescription > p {
135+
margin-top: 0;
136+
}
121137
</style>
122138

123139
<script type="text/javascript">
124140
var AutheliaConfigurationPage = {
125141
pluginUniqueId: "6bb8dbba-2aaa-4b19-9da4-f3bbb6c44091",
126-
AutheliaServer: document.querySelector("#txtAutheliaServer"),
127-
AutheliaRootCa: document.querySelector("#txtAutheliaRootCa"),
128-
CreateUserIfNotExists: document.querySelector("#boolCreateUserIfNotExists"),
129-
JellyfinUrl: document.querySelector("#txtJellyfinUrl"),
142+
AutheliaServer: document.querySelector("#AutheliaServer"),
143+
AutheliaRootCa: document.querySelector("#AutheliaRootCa"),
144+
CreateUserIfNotExists: document.querySelector("#CreateUserIfNotExists"),
145+
AutheliaAdminGroup: document.querySelector("#AutheliaAdminGroup"),
146+
JellyfinUrl: document.querySelector("#JellyfinUrl"),
130147
};
131148

132149
document.querySelector(".esqConfigurationPage").addEventListener("pageshow", function () {
133150
window.ApiClient.getPluginConfiguration(AutheliaConfigurationPage.pluginUniqueId).then(function (config) {
134151
AutheliaConfigurationPage.AutheliaServer.value = config.AutheliaServer;
135152
AutheliaConfigurationPage.AutheliaRootCa.value = config.AutheliaRootCa;
136153
AutheliaConfigurationPage.CreateUserIfNotExists.checked = config.CreateUserIfNotExists;
154+
AutheliaConfigurationPage.AutheliaAdminGroup.value = config.AutheliaAdminGroup;
137155
AutheliaConfigurationPage.JellyfinUrl.value = config.JellyfinUrl;
138156

139157
AutheliaConfigurationPage.AutheliaRootCa.placeholder = AutheliaConfigurationPage.AutheliaRootCa.placeholder.replace(/\\n/g, "\n");
@@ -145,13 +163,12 @@ <h2 class="sectionTitle">Authelia Settings:</h2>
145163
e.preventDefault();
146164
Dashboard.showLoadingMsg();
147165

148-
console.log("calling api????");
149166
window.ApiClient.getPluginConfiguration(AutheliaConfigurationPage.pluginUniqueId).then(function (config) {
150167
config.AutheliaServer = AutheliaConfigurationPage.AutheliaServer.value;
151168
config.AutheliaRootCa = AutheliaConfigurationPage.AutheliaRootCa.value;
152169
config.CreateUserIfNotExists = AutheliaConfigurationPage.CreateUserIfNotExists.checked;
170+
config.AutheliaAdminGroup = AutheliaConfigurationPage.AutheliaAdminGroup.value;
153171
config.JellyfinUrl = AutheliaConfigurationPage.JellyfinUrl.value;
154-
console.log("????");
155172
window.ApiClient.updatePluginConfiguration(AutheliaConfigurationPage.pluginUniqueId, config).then(Dashboard.processPluginConfigurationUpdateResult);
156173
});
157174

build.yaml

+4-5
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ category: "Authentication"
1515
artifacts:
1616
- "Authelia-Auth.dll"
1717
changelog: |2-
18-
### Fixed
19-
- Latest version didn't work with Jellyfin 10.8.0
20-
2118
### Added
22-
- A new configuration param for a custom root CA certificate
23-
- A new configuration param to enable/disable user creation on login
19+
- Added a new field to manage Jellyfin administrator privilege for users authorized with this plugin
20+
21+
### Changed
22+
- Simplified authentication by 1 HTTP call, extracting full name from `Remote-Name` header instead of an additional call to `/api/user/info`

0 commit comments

Comments
 (0)