Added AvatarFormatUrl to programatically set avatar on SSO Connect with OIDC

This commit is contained in:
Evann Regnault
2024-08-04 03:00:06 +02:00
parent b8e56cefab
commit 95d2c36e2c
3 changed files with 88 additions and 5 deletions
+65 -5
View File
@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Net.Mime; using System.Net.Mime;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -12,14 +14,15 @@ using Jellyfin.Plugin.SSO_Auth.Config;
using Jellyfin.Plugin.SSO_Auth.Helpers; using Jellyfin.Plugin.SSO_Auth.Helpers;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Cryptography;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -38,6 +41,8 @@ public class SSOController : ControllerBase
private readonly IAuthorizationContext _authContext; private readonly IAuthorizationContext _authContext;
private readonly ILogger<SSOController> _logger; private readonly ILogger<SSOController> _logger;
private readonly ICryptoProvider _cryptoProvider; private readonly ICryptoProvider _cryptoProvider;
private readonly IProviderManager _providerManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
private static readonly IDictionary<string, TimedAuthorizeState> StateManager = new Dictionary<string, TimedAuthorizeState>(); private static readonly IDictionary<string, TimedAuthorizeState> StateManager = new Dictionary<string, TimedAuthorizeState>();
/// <summary> /// <summary>
@@ -48,13 +53,24 @@ public class SSOController : ControllerBase
/// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="cryptoProvider">Instance of the <see cref="ICryptoProvider"/> interface.</param> /// <param name="cryptoProvider">Instance of the <see cref="ICryptoProvider"/> interface.</param>
public SSOController(ILogger<SSOController> logger, ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, ICryptoProvider cryptoProvider) /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
public SSOController(
ILogger<SSOController> logger,
ISessionManager sessionManager,
IUserManager userManager,
IAuthorizationContext authContext,
ICryptoProvider cryptoProvider,
IProviderManager providerManager,
IServerConfigurationManager serverConfigurationManager)
{ {
_sessionManager = sessionManager; _sessionManager = sessionManager;
_userManager = userManager; _userManager = userManager;
_authContext = authContext; _authContext = authContext;
_cryptoProvider = cryptoProvider; _cryptoProvider = cryptoProvider;
_logger = logger; _logger = logger;
_providerManager = providerManager;
_serverConfigurationManager = serverConfigurationManager;
_logger.LogInformation("SSO Controller initialized"); _logger.LogInformation("SSO Controller initialized");
} }
@@ -117,6 +133,13 @@ public class SSOController : ControllerBase
StateManager[state].EnableLiveTv = config.EnableLiveTv; StateManager[state].EnableLiveTv = config.EnableLiveTv;
StateManager[state].EnableLiveTvManagement = config.EnableLiveTvManagement; StateManager[state].EnableLiveTvManagement = config.EnableLiveTvManagement;
if (config.AvatarUrlFormat is not null)
{
StateManager[state].AvatarURL = result.User.Claims.Aggregate(
config.AvatarUrlFormat,
(s, claim) => s.Contains($"@{{{claim.Type}}}") ? s.Replace($"@{{{claim.Type}}}", claim.Value) : s);
}
foreach (var claim in result.User.Claims) foreach (var claim in result.User.Claims)
{ {
if (claim.Type == (config.DefaultUsernameClaim?.Trim() ?? "preferred_username")) if (claim.Type == (config.DefaultUsernameClaim?.Trim() ?? "preferred_username"))
@@ -421,7 +444,7 @@ public class SSOController : ControllerBase
{ {
Guid userId = await CreateCanonicalLinkAndUserIfNotExist("oid", provider, kvp.Value.Username); Guid userId = await CreateCanonicalLinkAndUserIfNotExist("oid", provider, kvp.Value.Username);
var authenticationResult = await Authenticate(userId, kvp.Value.Admin, config.EnableAuthorization, config.EnableAllFolders, kvp.Value.Folders.ToArray(), kvp.Value.EnableLiveTv, kvp.Value.EnableLiveTvManagement, response, config.DefaultProvider?.Trim()) var authenticationResult = await Authenticate(userId, kvp.Value.Admin, config.EnableAuthorization, config.EnableAllFolders, kvp.Value.Folders.ToArray(), kvp.Value.EnableLiveTv, kvp.Value.EnableLiveTvManagement, response, config.DefaultProvider?.Trim(), kvp.Value.AvatarURL)
.ConfigureAwait(false); .ConfigureAwait(false);
return Ok(authenticationResult); return Ok(authenticationResult);
} }
@@ -686,7 +709,7 @@ public class SSOController : ControllerBase
Guid userId = await CreateCanonicalLinkAndUserIfNotExist("saml", provider, samlResponse.GetNameID()); Guid userId = await CreateCanonicalLinkAndUserIfNotExist("saml", provider, samlResponse.GetNameID());
var authenticationResult = await Authenticate(userId, isAdmin, config.EnableAuthorization, config.EnableAllFolders, folders.ToArray(), liveTv, liveTvManagement, response, config.DefaultProvider?.Trim()) var authenticationResult = await Authenticate(userId, isAdmin, config.EnableAuthorization, config.EnableAllFolders, folders.ToArray(), liveTv, liveTvManagement, response, config.DefaultProvider?.Trim(), null)
.ConfigureAwait(false); .ConfigureAwait(false);
return Ok(authenticationResult); return Ok(authenticationResult);
} }
@@ -1020,7 +1043,8 @@ public class SSOController : ControllerBase
/// <param name="enableLiveTvAdmin">Determines whether live TV can be managed by this user.</param> /// <param name="enableLiveTvAdmin">Determines whether live TV can be managed by this user.</param>
/// <param name="authResponse">The client information to authenticate the user with.</param> /// <param name="authResponse">The client information to authenticate the user with.</param>
/// <param name="defaultProvider">The default provider of the user to be set after logging in.</param> /// <param name="defaultProvider">The default provider of the user to be set after logging in.</param>
private async Task<AuthenticationResult> Authenticate(Guid userId, bool isAdmin, bool enableAuthorization, bool enableAllFolders, string[] enabledFolders, bool enableLiveTv, bool enableLiveTvAdmin, AuthResponse authResponse, string defaultProvider) /// <param name="avatarUrl">The new avatar url for the user.</param>
private async Task<AuthenticationResult> Authenticate(Guid userId, bool isAdmin, bool enableAuthorization, bool enableAllFolders, string[] enabledFolders, bool enableLiveTv, bool enableLiveTvAdmin, AuthResponse authResponse, string defaultProvider, string avatarUrl)
{ {
User user = _userManager.GetUserById(userId); User user = _userManager.GetUserById(userId);
if (enableAuthorization) if (enableAuthorization)
@@ -1033,6 +1057,36 @@ public class SSOController : ControllerBase
} }
} }
if (avatarUrl is not null)
{
try
{
using var client = new HttpClient();
var extension = avatarUrl.Split(".").Last();
var stream = await client.GetStreamAsync(avatarUrl);
if (user != null)
{
var userDataPath =
Path.Combine(
_serverConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath,
user.Username);
if (user.ProfileImage is not null)
{
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
}
user.ProfileImage = new ImageInfo(Path.Combine(userDataPath, "profile" + extension));
await _providerManager.SaveImage(stream, "image/" + extension, user.ProfileImage.Path)
.ConfigureAwait(false);
}
}
catch (Exception e)
{
_logger.LogError(e.Message);
}
}
user.SetPermission(PermissionKind.EnableLiveTvAccess, enableLiveTv); user.SetPermission(PermissionKind.EnableLiveTvAccess, enableLiveTv);
user.SetPermission(PermissionKind.EnableLiveTvManagement, enableLiveTvAdmin); user.SetPermission(PermissionKind.EnableLiveTvManagement, enableLiveTvAdmin);
@@ -1150,6 +1204,7 @@ public class TimedAuthorizeState
IsLinking = false; IsLinking = false;
EnableLiveTv = false; EnableLiveTv = false;
EnableLiveTvManagement = false; EnableLiveTvManagement = false;
AvatarURL = null;
} }
/// <summary> /// <summary>
@@ -1197,4 +1252,9 @@ public class TimedAuthorizeState
/// Gets or sets a value indicating whether the user is allowed to manage live TV. /// Gets or sets a value indicating whether the user is allowed to manage live TV.
/// </summary> /// </summary>
public bool EnableLiveTvManagement { get; set; } public bool EnableLiveTvManagement { get; set; }
/// <summary>
/// Gets or set the user avatar url.
/// </summary>
public string AvatarURL { get; set; }
} }
+5
View File
@@ -293,6 +293,11 @@ public class OidConfig
/// </summary> /// </summary>
public string DefaultUsernameClaim { get; set; } public string DefaultUsernameClaim { get; set; }
/// <summary>
/// Gets or sets the URL format of the new user avatar.
/// </summary>
public string AvatarUrlFormat { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether HTTPS in the discovery endpoint is required. /// Gets or sets a value indicating whether HTTPS in the discovery endpoint is required.
/// </summary> /// </summary>
+18
View File
@@ -559,6 +559,24 @@
</div> </div>
</div> </div>
<div class="inputContainer">
<label
class="inputLabel inputLabelUnfocused"
for="AvatarUrlFormat"
>Set avatar url format</label
>
<input
is="emby-input"
id="AvatarUrlFormat"
type="text"
class="sso-text"
/>
<div class="fieldDescription">
The url of the avatar with sso variable format:
example : <code>https://example.com/@{user_id}.png</code>
</div>
</div>
<div class="checkboxContainer"> <div class="checkboxContainer">
<label> <label>
<input <input