Files
2026-08-18 18:30:47 -07:00

200 lines
7.2 KiB
C#

namespace Jellyfin.Plugin.OIDC_Auth;
/// <summary>
/// Plugin Configuration.
/// </summary>
public class PluginConfiguration : MediaBrowser.Model.Plugins.BasePluginConfiguration {
/// <summary>
/// Initializes a new instance of the <see cref="PluginConfiguration" /> class.
/// </summary>
public PluginConfiguration() {
this.Configs = new SerializableDictionary<string, Config>();
}
/// <summary>
/// Gets or sets the OpenID configurations available.
/// </summary>
[System.Xml.Serialization.XmlElementAttribute("Configs")]
public SerializableDictionary<string, Config> Configs { get; set; }
}
/// <summary>
/// The configuration required for a OpenID flow.
/// </summary>
[System.Xml.Serialization.XmlRootAttribute("PluginConfiguration")]
public class Config {
/// <summary>
/// Initializes a new instance of the <see cref="Config"/> class.
/// </summary>
public Config() {
this.Enabled = false;
this.Scopes = ["profile", "groups"];
this.UsernameClaim = "preferred_username";
this.RoleClaim = "groups";
this.DataClaims = [];
this.DataClaimPrefix = "jellyfin-";
this.UserRole = "jellyfin";
this.AdminRole = "jellyfin-admin";
this.AvatarUrlClaim = "picture";
this.AuthenticationTimeout = 5;
this.DefaultProvider = "Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider";
this.ValidateIssuerName = true;
this.LoadProfile = true;
this.CanonicalLinks = new SerializableDictionary<string, System.Guid>();
this.ReverseCanonicalLinks = new SerializableDictionary<System.Guid, string>();
}
/// <summary>
/// Gets or sets a value indicating whether the provider is enabled.
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Gets or sets the OpenID well-known information endpoint.
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// Gets or sets OpenID client ID.
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// Gets or sets OpenID shared secret.
/// </summary>
public string ClientSecret { get; set; }
/// <summary>
/// Gets or Sets additional Scopes to request access to in the authorization request.
/// </summary>
public string[] Scopes { get; set; }
/// <summary>
/// Gets or sets the default username claim when creating new accounts.
/// </summary>
public string UsernameClaim { get; set; }
/// <summary>
/// Gets or sets the claim containing the URL of the user avatar.
/// </summary>
public string AvatarUrlClaim { get; set; }
/// <summary>
/// Gets or sets the claim to check roles against. Separated by "."s.
/// </summary>
public string RoleClaim { get; set; }
/// <summary>
/// Gets or sets the roles that are checked to determine whether the user is an administrator.
/// </summary>
public string AdminRole { get; set; }
/// <summary>
/// Gets or sets what roles are checked to determine whether the user is allowed to use Jellyfin.
/// </summary>
public string UserRole { get; set; }
/// <summary>
/// Gets or sets what claims are used to configure the user.
/// </summary>
public string[] DataClaims { get; set; }
/// <summary>
/// Gets or sets what claims are used to configure the user.
/// </summary>
public string DataClaimPrefix { get; set; }
/// <summary>
/// Gets or sets a value indicating whether RBAC is enabled.
/// </summary>
public bool EnableAuthorization { get; set; }
/// <summary>
/// Gets or sets the default provider the user after logging in with OIDC.
/// </summary>
public string DefaultProvider { get; set; }
/// <summary>
/// Gets or sets the redirect port override.
/// </summary>
public int? PortOverride { get; set; }
/// <summary>
/// Gets or sets a mapping of canonical names from the provider to jellyfin user ids.
/// </summary>
[System.Xml.Serialization.XmlElementAttribute("CanonicalLinks")]
public SerializableDictionary<string, System.Guid> CanonicalLinks { get; set; }
/// <summary>
/// Gets or sets a mapping of canonical names from the provider to jellyfin user ids.
/// </summary>
[System.Xml.Serialization.XmlElementAttribute("ReverseCanonicalLinks")]
public SerializableDictionary<System.Guid, string> ReverseCanonicalLinks { get; set; }
/// <summary>
/// Gets or sets a value indicating whether pushed authorization is required.
/// </summary>
public bool PushedAuthorization { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the OpenID endpoints are validated.
/// </summary>
public bool ValidateEndpoints { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the OpenID issuer name is validated.
/// </summary>
public bool ValidateIssuerName { get; set; }
/// <summary>
/// Gets or sets Amount of time in minutes to wait for a user to finish OIDC Authentication.
/// </summary>
public int AuthenticationTimeout { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the UserInfo endpoint is used to get profile data.
/// Data from UserInfo endpoint is merged with ID Token.
/// </summary>
public bool LoadProfile { get; set; }
/// <summary>
/// Creates a link for a user. Refuses to change a user that already has a link.
/// </summary>
/// <param name="jellyfinUserId">The user ID within jellyfin to link to the provider.</param>
/// <param name="providerUserId">The user ID from this provider.</param>
/// <returns>Whether this API endpoint succeeded.</returns>
public (bool, string) CreateCanonicalLink(System.Guid jellyfinUserId, string providerUserId) {
System.Guid existingUser;
string existingLink;
if (this.CanonicalLinks.TryGetValue(providerUserId, out existingUser)) {
return (existingUser == jellyfinUserId, "OIDC User is already linked in this provider");
}
if (this.ReverseCanonicalLinks.TryGetValue(jellyfinUserId, out existingLink)) {
return (existingLink == providerUserId, "JellyFin User is already linked in this provider");
}
this.CanonicalLinks[providerUserId] = jellyfinUserId;
this.ReverseCanonicalLinks[jellyfinUserId] = providerUserId;
OIDCPlugin.Instance.UpdateConfiguration(OIDCPlugin.Instance.Configuration);
return (true, string.Empty);
}
/// <summary>
/// Fixes missing ReverseCanonicalLinks.
/// </summary>
public void FixCanonicalLinks() {
System.Collections.Generic.HashSet<System.Guid> found = [];
System.Collections.Generic.HashSet<string> toRemove = [];
foreach (var link in this.CanonicalLinks) {
if (found.Contains(link.Value)) {
toRemove.Add(link.Key);
}
found.Add(link.Value);
this.ReverseCanonicalLinks.TryAdd(link.Value, link.Key);
}
foreach (var duplicate in toRemove) {
this.CanonicalLinks.Remove(duplicate);
}
}
}