Compare commits

...
Author SHA1 Message Date
9p4 7331e2ffe3 Merge branch 'discovery-no-https' of https://github.com/9p4/jellyfin-plugin-sso into discovery-no-https 2022-09-09 10:17:04 -04:00
9p4 cef4fe5784 feat: allow for HTTPS to be disabled in OpenID discovery (#76) 2022-09-09 10:16:05 -04:00
9p4 0d2952423d feat: allow for HTTPS to be disabled in OpenID discovery (#76) 2022-09-09 10:13:07 -04:00
9p4 98ddb9e352 Merge pull request #74 from 9p4/custom-username-claim
feat: allow configuration of preferred username claim
2022-08-23 21:12:59 -04:00
9p4 46eb00bf43 Merge branch 'main' into custom-username-claim 2022-08-23 19:50:01 -04:00
9p4 5723cd718d ci: change commit linter 2022-08-23 19:48:41 -04:00
9p4 96b14f7a5a ci: run commit name lint on every push and pr 2022-08-23 19:42:56 -04:00
9p4 320551bc18 ci: update hash for commit lint action 2022-08-23 19:39:47 -04:00
Sambhav Saggi 9f8626bdf7 Merge branch 'custom-username-claim' of https://github.com/9p4/jellyfin-plugin-sso into custom-username-claim 2022-08-23 19:38:21 -04:00
Sambhav Saggi d5925fc1de docs: add custom username claim api doc 2022-08-23 19:38:01 -04:00
9p4 facf45058f Merge branch 'main' into custom-username-claim 2022-08-23 19:32:16 -04:00
9p4 f3d0497801 ci: lint commits 2022-08-23 19:31:02 -04:00
Sambhav Saggi 672fce8189 fix: prevent null passwords when fallback is set to default provider 2022-08-23 19:22:02 -04:00
Sambhav Saggi c2f50e1e4c style: run prettier 2022-08-23 18:57:57 -04:00
Sambhav Saggi a90e0359e4 feat: add config page for preferred username config 2022-08-23 18:26:58 -04:00
Sambhav Saggi 332f62d76e feat: allow configuration of preferred username claim (#47) 2022-08-23 18:18:42 -04:00
9p4 47db5cb504 Merge pull request #71 from fredriklindberg/improve-get-request-base
fix: don't include port in redirect URL for 80 and 443
2022-08-23 18:12:19 -04:00
Matthew Strasiotto fe1fdad0b0 Merge pull request #72 from fredriklindberg/make-user-creation-more-robust 2022-08-20 21:07:53 +10:00
Fredrik Lindberg d06f680407 fix: make user creation more robust
Reverse logic of CreateCanonicalLinkAndUserIfNotExist() to first check if the user exists,
then create the canonical link. This allows seamless re-creation of users through SSO after
they have been deleted from Jellyfin but not properly de-linked.
2022-08-18 17:55:41 +02:00
Fredrik Lindberg 976a816d8c fix: don't include port in redirect URL for 80 and 443
Skip port number in redirect URLs during the following conditions
 - The request scheme is http and request port is 80
 - The request scheme is https and request port is 443

This creates redirect urls as https://server/path instead of https://server:443/path
2022-08-18 17:48:37 +02:00
Matthew Strasiotto f7ce40fe5e Merge pull request #69 from matthewstrasiotto/main
ci: 📌 change from shogo82148/actions-upload-release-asset to softprops/action-gh-release
2022-08-18 19:09:45 +10:00
Matthew Strasiotto 8666818dfa ci: 📌 change from shogo82148/actions-upload-release-asset to softprops/action-gh-release 2022-08-18 19:09:12 +10:00
Matthew Strasiotto c5e6bf96f9 Merge pull request #68 from matthewstrasiotto/main
ci: 👷 keep our own build workflow and reference that in publish.yml
2022-08-18 18:42:39 +10:00
Matthew Strasiotto fd59b83a0e ci: 👷 keep our own build workflow and reference that in publish.yml
Squash of
- ci: 👷 keep our own build workflow and reference that in publish.yml
- ci: 📌 pin actions/checkout,setup-dotnet odstr13/jellyfin-plugin-repo-manager to approved version

This will still fail because actions/upload-artifact isnt approved.

Getting kind of tilted trying to contribute to ci when the upstream CI settings fight me at every turn
2022-08-18 18:41:16 +10:00
7 changed files with 130 additions and 21 deletions
+39
View File
@@ -0,0 +1,39 @@
on:
workflow_call:
inputs:
dotnet-version:
required: false
default: "6.0.x"
description: "The .NET version to setup for the build"
type: string
dotnet-target:
required: false
default: "net6.0"
description: "The .NET target to set for JPRM"
type: string
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: "${{ inputs.dotnet-version }}"
- name: Build Jellyfin Plugin
uses: oddstr13/jellyfin-plugin-repository-manager@b9e92867a6aa279d611a5ea80cf61f6358838c39
id: jprm
with:
dotnet-target: "${{ inputs.dotnet-target }}"
- name: Upload Artifact
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
with:
name: build-artifact
retention-days: 30
if-no-files-found: error
path: ${{ steps.jprm.outputs.artifact }}
+11
View File
@@ -0,0 +1,11 @@
name: Lint Commit Messages
on: [pull_request, push]
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@4caf21aed4a778f940d0b17eb109942ef167bb27
+10 -5
View File
@@ -8,7 +8,7 @@ on:
jobs:
build:
uses: jellyfin/jellyfin-meta-plugins/.github/workflows/build.yaml@30fd723cc3bafc4457a4a85be3cdbee2f3e2013b
uses: ./.github/workflows/build.yml
with:
dotnet-version: "6.0.*"
dotnet-target: "net6.0"
@@ -28,11 +28,16 @@ jobs:
sha256sum ${file#./} >> ${file%.*}.sha256
done
ls -l
- name: Upload GitHub Release assets
uses: shogo82148/[email protected]
- name: Publish output artifacts
id: publish-assets
uses: softprops/action-gh-release@50195ba7f6f93d1ac97ba8332a178e008ad176aa
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./*
prerelease: false
fail_on_unmatched_files: true
tag_name: ${{ github.event.release.tag_name }}
files: ./*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
generate:
runs-on: ubuntu-latest
needs:
+3 -1
View File
@@ -192,10 +192,12 @@ These all require authorization. Append an API key to the end of the request: `c
- `enableFolderRoles`: boolean. Determines if role-based folder access should be used.
- `folderRoleMapping`: object in the format "role": string and "folders": array of strings. The user with this role will have access to the following folders if `enableFolderRoles` is enabled. To get the IDs of the folders, GET the `/Library/MediaFolders` URL with an API key. Look for the `Id` attribute.
- `roleClaim`: string. This is the value in the OpenID response to check for roles. For Keycloak, it is `realm_access.roles` by default. The first element is the claim type, the subsequent values are to parse the JSON of the claim value. Use a "\\." to denote a literal ".". This expects a list of strings from the OIDC server.
- `oidScopes` : array of strings. each containing an additional scope name to include in the OIDC request.
- `oidScopes` : array of strings. Each contains an additional scope name to include in the OIDC request.
- For some OIDC providers (For example, [authelia](https://github.com/9p4/jellyfin-plugin-sso/issues/23#issuecomment-1112237616)), additional scopes may be required in order to validate group membership in role claim.
- Leave empty to only request the default scopes.
- `defaultProvider`: string. The set provider then gets assigned to the user after they have logged in. If it is not set, nothing is changed. With this, a user can login with SSO but is still able to log in via other providers later. See the `Unregister` endpoint.
- `defaultUsernameClaim`: string. The provider will use the claim to create the users' usernames. If not set, it fallbacks to `preferred_username`.
- `requireHttps`: boolean. Determines whether the OpenID discovery endpoint requires HTTP. It is advised NOT to set this to false. If not set, it fallbacks to `true`.
- GET `OID/Del/PROVIDER_NAME`: This removes a configuration for OpenID for a given provider name.
- GET `OID/Get`: Lists the configurations currently available.
- GET `OID/States`: Lists currently active OpenID flows in progress.
+38 -15
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using IdentityModel.OidcClient;
@@ -13,6 +14,7 @@ using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Cryptography;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -34,6 +36,7 @@ public class SSOController : ControllerBase
private readonly ISessionManager _sessionManager;
private readonly IAuthorizationContext _authContext;
private readonly ILogger<SSOController> _logger;
private readonly ICryptoProvider _cryptoProvider;
private static readonly IDictionary<string, TimedAuthorizeState> StateManager = new Dictionary<string, TimedAuthorizeState>();
/// <summary>
@@ -43,11 +46,13 @@ public class SSOController : ControllerBase
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
/// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
public SSOController(ILogger<SSOController> logger, ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext)
/// <param name="cryptoProvider">Instance of the <see cref="ICryptoProvider"/> interface.</param>
public SSOController(ILogger<SSOController> logger, ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, ICryptoProvider cryptoProvider)
{
_sessionManager = sessionManager;
_userManager = userManager;
_authContext = authContext;
_cryptoProvider = cryptoProvider;
_logger = logger;
_logger.LogInformation("SSO Controller initialized");
}
@@ -85,6 +90,7 @@ public class SSOController : ControllerBase
Scope = string.Join(" ", config.OidScopes.Prepend("openid profile")),
};
options.Policy.Discovery.ValidateEndpoints = false; // For Google and other providers with different endpoints
options.Policy.Discovery.RequireHttps = config.RequireHttps || true;
var oidcClient = new OidcClient(options);
var currentState = StateManager[state].State;
var result = oidcClient.ProcessResponseAsync(Request.QueryString.Value, currentState).Result;
@@ -104,7 +110,7 @@ public class SSOController : ControllerBase
foreach (var claim in result.User.Claims)
{
if (claim.Type == "preferred_username")
if (claim.Type == (config.DefaultUsernameClaim ?? "preferred_username"))
{
StateManager[state].Username = claim.Value;
if (config.Roles.Length == 0)
@@ -186,7 +192,7 @@ public class SSOController : ControllerBase
}
}
// If the provider doesn't support preferred_username, then use sub
// If the provider doesn't support the preferred username claim, then use the sub claim
if (!StateManager[state].Valid)
{
foreach (var claim in result.User.Claims)
@@ -624,6 +630,22 @@ public class SSOController : ControllerBase
private async Task<Guid> CreateCanonicalLinkAndUserIfNotExist(string mode, string provider, string canonicalName)
{
User user = null;
user = _userManager.GetUserByName(canonicalName);
if (user == null)
{
_logger.LogInformation($"SSO user {canonicalName} doesn't exist, creating...");
user = await _userManager.CreateUserAsync(canonicalName).ConfigureAwait(false);
user.AuthenticationProviderId = GetType().FullName;
// https://jonathancrozier.com/blog/how-to-generate-a-cryptographically-secure-random-string-in-dot-net-with-c-sharp
user.Password = _cryptoProvider.CreatePasswordHash(Convert.ToBase64String(RandomNumberGenerator.GetBytes(64))).ToString();
// Make sure there aren't any trailing existing links
var links = GetCanonicalLinks(mode, provider);
links.Remove(canonicalName);
UpdateCanonicalLinkConfig(links, mode, provider);
}
Guid userId = Guid.Empty;
try
{
@@ -637,18 +659,7 @@ public class SSOController : ControllerBase
if (userId == Guid.Empty)
{
_logger.LogInformation("SSO user link doesn't exist, creating...");
User user = null;
user = _userManager.GetUserByName(canonicalName);
if (user == null)
{
_logger.LogInformation($"SSO user {canonicalName} doesn't exist, creating...");
user = await _userManager.CreateUserAsync(canonicalName).ConfigureAwait(false);
user.AuthenticationProviderId = GetType().FullName;
}
userId = user.Id;
CreateCanonicalLink(mode, provider, userId, canonicalName);
}
@@ -948,7 +959,19 @@ public class SSOController : ControllerBase
private string GetRequestBase()
{
return Request.Scheme + "://" + Request.Host + Request.PathBase;
int requestPort = Request.Host.Port ?? -1;
if ((requestPort == 80 && string.Equals(Request.Scheme, "http", StringComparison.OrdinalIgnoreCase)) || (requestPort == 443 && string.Equals(Request.Scheme, "https", StringComparison.OrdinalIgnoreCase)))
{
requestPort = -1;
}
return new UriBuilder
{
Scheme = Request.Scheme,
Host = Request.Host.Host,
Port = requestPort,
Path = Request.PathBase
}.ToString().TrimEnd('/');
}
private ContentResult ReturnError(int code, string message)
+10
View File
@@ -217,6 +217,16 @@ public class OidConfig
}
set => _canonicalLinks = value;
}
/// <summary>
/// Gets or sets the default username claim when creating new accounts.
/// </summary>
public string DefaultUsernameClaim { get; set; }
/// <summary>
/// Gets or sets a value indicating whether HTTPS in the discovery endpoint is required.
/// </summary>
public bool RequireHttps { get; set; }
}
/// <summary>
+19
View File
@@ -437,6 +437,25 @@
</div>
</div>
<div class="inputContainer">
<label
class="inputLabel inputLabelUnfocused"
for="DefaultUsernameClaim"
>Set default username claim:</label
>
<input
is="emby-input"
id="DefaultUsernameClaim"
type="text"
class="sso-text"
/>
<div class="fieldDescription">
The default username claim to use from OpenID by default. If
it is not set, it defaults to
<code>preferred_username</code>.
</div>
</div>
<button
id="SaveProvider"
is="emby-button"