Compare commits

...

25 Commits

Author SHA1 Message Date
Ersei Saggi 0476f817a6 v.3.5.2.1 2023-10-18 01:11:26 -04:00
Ersei Saggi 3bb241b6d2 Clarify docs 2023-10-18 01:11:10 -04:00
Ersei Saggi cb26ba9852 Add yet another null check in SAML 2023-10-18 01:09:33 -04:00
Ersei Saggi 1b6761bf73 fix: add another null check in SAML for the default provider 2023-09-01 15:59:13 -04:00
Ersei Saggi eeabc3cc46 Merge branch 'main' of github.com:9p4/jellyfin-plugin-sso 2023-08-29 13:06:15 -04:00
Ersei Saggi e65c5add95 fix: a few more null checks 2023-08-29 13:06:02 -04:00
Nathan Easton fce636bddd Added a section in the authentik provider (#137)
Add note about error processing request from issue #136
2023-08-22 13:29:50 -04:00
Ersei Saggi 94d1c7a95a Prepare for 3.5.2.0 2023-08-07 13:15:04 -04:00
Ersei Saggi a1a355591c docs: add Nix flakes blurb 2023-08-07 12:26:51 -04:00
Ersei Saggi 71802d954c docs: fix SAML paths in docs 2023-08-07 12:24:04 -04:00
Ersei Saggi f37e8dd3bb fix: handle empty and invalid strings in the schemeOverride 2023-08-07 12:14:52 -04:00
Ersei Saggi 9b667b3175 feat: allow overriding redirect scheme 2023-08-07 12:09:51 -04:00
Ersei Saggi 2c3fc4d8e1 Prepare for 3.5.1.1 2023-08-07 08:42:10 -04:00
Ersei Saggi f897704100 fix: change iframe URL to point to the web UI instead of the root 2023-08-07 08:23:34 -04:00
Ersei Saggi d0b9b10a36 fix: remove the commit lint check (it's really useless) 2023-08-06 16:36:24 -04:00
Ersei Saggi 172f22e19f release: prepare for v3.5.1.0 2023-08-06 16:34:57 -04:00
Ersei Saggi 5777c8038e fix: improve final redirect (#109) 2023-08-06 16:29:44 -04:00
Ersei Saggi 95776b427b style: fix configPage style (prettier) 2023-08-06 16:27:01 -04:00
Ersei Saggi b82a3867e4 docs: detail Google requiring disabling of OpenID endpoint validation 2023-08-06 16:24:05 -04:00
Ersei Saggi 657f90ef13 fix: make SAML paths better; improve and add OID discovery defaults 2023-08-06 16:19:53 -04:00
Ersei Saggi dd01e729b8 docs: clarify plugin resetting permissions 2023-08-06 14:47:56 -04:00
Ersei Saggi f2452103bf style: fix README.md style 2023-08-06 14:44:23 -04:00
Ersei Saggi 076cb1b893 docs: document how to set login disclaimer (#16) 2023-08-06 14:43:09 -04:00
Ersei Saggi defab9c4e8 feat: clarify redirect paths (#84) 2023-08-06 14:32:05 -04:00
Ersei Saggi 2ead846ed8 fix: Remove funding 2023-08-05 18:58:04 -04:00
10 changed files with 178 additions and 62 deletions
-13
View File
@@ -1,13 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: a055
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
-11
View File
@@ -1,11 +0,0 @@
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
+51 -12
View File
@@ -91,6 +91,35 @@ The nightly build may have new features unavailable in other builds, but **be wa
## Examples
### Creating A Login Button On The Main Page
In the Jellyfin administration UI, under "General", there is a "Branding" section. In that section, add the following code in the "Login disclaimer" block (replacing `PROVIDER_NAME` and the domain):
```html
<a
href="https://jellyfin.example.com/sso/OID/start/PROVIDER_NAME"
class="raised cancel block emby-button"
>Sign in with SSO</a
>
```
Then, add the following code in the "Custom CSS code" section:
```css
a.raised.emby-button {
padding: 0.9em 1em;
color: inherit !important;
}
.disclaimerContainer {
display: block;
}
```
![screenshot of the configuration page with the same code](img/custom-button.png)
For more information, refer to [issue #16](https://github.com/9p4/jellyfin-plugin-sso/issues/16).
### SAML
Example for adding a SAML configuration with the API using [curl](https://curl.se/):
@@ -104,9 +133,9 @@ The SAML provider must have the following configuration (I am using Keycloak, an
- Sign Documents on
- Sign Assertions off
- Client Signature Required off
- Redirect URI: [https://myjellyfin.example.com/sso/SAML/p/PROVIDER_NAME](https://myjellyfin.example.com/sso/OID/p/PROVIDER_NAME)
- Redirect URI: [https://myjellyfin.example.com/sso/SAML/post/PROVIDER_NAME](https://myjellyfin.example.com/sso/SAML/start/PROVIDER_NAME)
- Base URL: [https://myjellyfin.example.com](https://myjellyfin.example.com)
- Master SAML processing URL: [https://myjellyfin.example.com/sso/SAML/p/PROVIDER_NAME](https://myjellyfin.example.com/sso/SAML/p/PROVIDER_NAME)
- Master SAML processing URL: [https://myjellyfin.example.com/sso/SAML/start/PROVIDER_NAME](https://myjellyfin.example.com/sso/SAML/start/PROVIDER_NAME)
Make sure that `clientid` is replaced with the actual client ID and `PROVIDER_NAME` is replaced with the chosen provider name!
@@ -120,7 +149,7 @@ The OpenID provider must have the following configuration (again, I am using Key
- Access Type: Confidential
- Standard Flow Enabled
- Redirect URI: [https://myjellyfin.example.com/sso/OID/r/PROVIDER_NAME](https://myjellyfin.example.com/sso/OID/r/PROVIDER_NAME)
- Redirect URI: [https://myjellyfin.example.com/sso/OID/redirect/PROVIDER_NAME](https://myjellyfin.example.com/sso/OID/redirect/PROVIDER_NAME)
- Base URL: [https://myjellyfin.example.com](https://myjellyfin.example.com)
Make sure that `clientid` is replaced with the actual client ID and `PROVIDER_NAME` is replaced with the chosen provider name!
@@ -133,8 +162,8 @@ The API is all done from a base URL of `/sso/`
#### Flow
- POST `SAML/p/PROVIDER_NAME`: This is the SAML POST endpoint. It accepts a form response from the SAML provider and returns HTML and JavaScript for the client to login with a given provider name.
- GET `SAML/p/PROVIDER_NAME`: This is the SAML initiator: it will begin the authorization flow for SAML with a given provider name.
- POST `SAML/start/PROVIDER_NAME`: This is the SAML POST endpoint. It accepts a form response from the SAML provider and returns HTML and JavaScript for the client to login with a given provider name.
- GET `SAML/start/PROVIDER_NAME`: This is the SAML initiator: it will begin the authorization flow for SAML with a given provider name.
- POST `SAML/Auth/PROVIDER_NAME`: This is the SAML client-side API: the HTML and JavaScript client will call this endpoint to receive Jellyfin credentials given a provider name. Post format is in JSON with the following keys:
- `deviceId`: string. Device ID.
- `deviceName`: string. Device name.
@@ -151,7 +180,7 @@ These all require authorization. Append an API key to the end of the request: `c
- `samlClientId`: string. The SAML client ID.
- `samlCertificate`: string. The base64 encoded SAML certificate.
- `enabled`: boolean. Determines if the provider is enabled or not.
- `enableAuthorization`: boolean: Determines if the plugin sets permissions for the user. If false, the user will start with no permissions and an administrator will add permissions. The permissions of existing users will not be rewritten on subsequent logins.
- `enableAuthorization`: boolean: Determines if the plugin sets permissions for the user. If false, the user will start with no permissions and an administrator will add permissions. If disabled, then the permissions of users will not be modified and the Jellyfin defaults will be used instead.
- `enableAllFolders`: boolean. Determines if the client logging in is allowed access to all folders.
- `enabledFolders`: array of strings. If `enableAllFolders` is set to false, then this will be used to determine what folders the users who log in through this provider are allowed to use.
- `roles`: array of strings. This validates the SAML response against the `Role` attribute. If a user has any of these roles, then the user is authenticated. Leave blank to disable role checking.
@@ -164,6 +193,7 @@ These all require authorization. Append an API key to the end of the request: `c
- `enableLiveTv`: boolean. Whether to allow Live TV by default. This applies even if `enableLiveTvRoles` is enabled.
- `enableLiveTvManagement`: boolean. Whether to allow Live TV management by default. This applies even if `enableLiveTvRoles` is enabled.
- `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.
- `schemeOverride`: string. Sets the scheme for URLs used. Can be useful if the plugin refuses to use HTTPS URLs.
- GET `SAML/Del/PROVIDER_NAME`: This removes a configuration for SAML for a given provider name.
- GET `SAML/Get`: Lists the configurations currently available.
@@ -171,8 +201,8 @@ These all require authorization. Append an API key to the end of the request: `c
#### Flow
- GET `OID/r/PROVIDER_NAME`: This is the OpenID callback path. This will return HTML and JavaScript for the client to login with a given provider name.
- GET `OID/p/PROVIDER_NAME`: This is the OpenID initiator: it will begin the authorization flow for OpenID with a given provider name.
- GET `OID/redirect/PROVIDER_NAME`: This is the OpenID callback path. This will return HTML and JavaScript for the client to login with a given provider name.
- GET `OID/start/PROVIDER_NAME`: This is the OpenID initiator: it will begin the authorization flow for OpenID with a given provider name.
- POST `OID/Auth/PROVIDER_NAME`: This is the OpenID client-side API: the HTML and JavaScript client will call this endpoint to receive Jellyfin credentials for a given provider name. Post format is in JSON with the following keys:
- `deviceId`: string. Device ID.
- `deviceName`: string. Device name.
@@ -189,7 +219,7 @@ These all require authorization. Append an API key to the end of the request: `c
- `oidClientId`: string. The OpenID client ID.
- `oidSecret`: string. The OpenID secret.
- `enabled`: boolean. Determines if the provider is enabled or not.
- `enableAuthorization`: boolean: Determines if the plugin sets permissions for the user. If false, the user will start with no permissions and an administrator will add permissions. The permissions of existing users will not be rewritten on subsequent logins.
- `enableAuthorization`: boolean: Determines if the plugin sets permissions for the user. If false, the user will start with no permissions and an administrator will add permissions. If disabled, then the permissions of users will not be modified and the Jellyfin defaults will be used instead.
- `enableAllFolders`: boolean. Determines if the client logging in is allowed access to all folders.
- `enabledFolders`: array of strings. If `enableAllFolders` is set to false, then this will be used to determine what folders the users who log in through this provider are allowed to use.
- `roles`: array of strings. This validates the OpenID response against the claim set in `roleClaim`. If a user has any of these roles, then the user is authenticated. Leave blank to disable role checking. This currently only works for Keycloak (to my knowledge).
@@ -207,7 +237,10 @@ These all require authorization. Append an API key to the end of the request: `c
- 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`.
- `disableHttps`: boolean. Determines whether the OpenID discovery endpoint requires HTTPS.
- `doNotValidateEndpoints`: boolean. Determines whether the OpenID discovery process will validate endpoints. This may be required for Google.
- `doNotValidateIssuerName`: boolean. Determines whether the OpenID discovery process will validate the OpenID issuer name.
- `schemeOverride`: string. Sets the scheme for URLs used. Can be useful if the plugin refuses to use HTTPS URLs.
- 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.
@@ -218,18 +251,24 @@ These all require authorization. Append an API key to the end of the request: `c
## Limitations
There is no GUI to sign in. You have to make it yourself! The buttons should redirect to something like this: [https://myjellyfin.example.com/sso/SAML/p/clientid](https://myjellyfin.example.com/sso/SAML/p/clientid) replacing `clientid` with the provider client ID and `SAML` with the auth scheme (either `SAML` or `OID`).
Logging in with an SSO account that has the same username as an existing Jellyfin account will override the permissions for the user. Use caution when overriding the administrator account!
~~There is no GUI to sign in. You have to make it yourself! The buttons should redirect to something like this: [https://myjellyfin.example.com/sso/SAML/start/clientid](https://myjellyfin.example.com/sso/SAML/start/clientid) replacing `clientid` with the provider client ID and `SAML` with the auth scheme (either `SAML` or `OID`).~~
~~Furthermore, there is no functional admin page (yet). PRs for this are welcome. In the meantime, you have to interact with the API to add or remove configurations.~~ Added by [strazto](https://github.com/strazto) in PR [#18](https://github.com/9p4/jellyfin-plugin-sso/pull/18) and [#27](https://github.com/9p4/jellyfin-plugin-sso/pull/27).
There is also no logout callback. Logging out of Jellyfin will log you out of Jellyfin only, instead of the SSO provider as well.
~~This only supports Jellyfin on it's own domain (for now). This is because I'm using string concatenation for generating some URLs. A PR is welcome to patch this.~~ Fixed in [PR #1](https://github.com/9p4/jellyfin-plugin-sso/pull/1).
~~This only supports Jellyfin on its own domain (for now). This is because I'm using string concatenation for generating some URLs. A PR is welcome to patch this.~~ Fixed in [PR #1](https://github.com/9p4/jellyfin-plugin-sso/pull/1).
**This only works on the web UI**. ~~The user must open the Jellyfin web UI BEFORE using the SSO program to populate some values in the localStorage.~~ Fixed by implementing a comment by [Pfuenzle](https://github.com/Pfuenzle) in [Issue #5](https://github.com/9p4/jellyfin-plugin-sso/issues/5#issuecomment-1041864820).
# Contributing
## Dependencies
This project uses Nix flakes to manage development dependencies. Run `nix develop` to use the same toolchain versions.
## Building
This is built with .NET 6.0. Build with `dotnet publish .` for the debug release in the `SSO-Auth` directory. Copy over the `IdentityModel.OidcClient.dll`, the `IdentityModel.dll` and the `SSO-Auth.dll` files in the `/bin/Debug/net6.0/publish` directory to a new folder in your Jellyfin configuration: `config/plugins/sso`.
+30 -16
View File
@@ -65,6 +65,7 @@ public class SSOController : ControllerBase
/// <returns>A webpage that will complete the client-side flow.</returns>
// Actually a GET: https://github.com/IdentityModel/IdentityModel.OidcClient/issues/325
[HttpGet("OID/r/{provider}")]
[HttpGet("OID/redirect/{provider}")]
public async Task<ActionResult> OidPost(
[FromRoute] string provider,
[FromQuery] string state) // Although this is a GET function, this function is called `Post` for consistency with SAML
@@ -81,16 +82,18 @@ public class SSOController : ControllerBase
if (config.Enabled)
{
var scopes = config.OidScopes == null ? new string[2] : config.OidScopes;
var options = new OidcClientOptions
{
Authority = config.OidEndpoint?.Trim(),
ClientId = config.OidClientId?.Trim(),
ClientSecret = config.OidSecret?.Trim(),
RedirectUri = GetRequestBase() + "/sso/OID/r/" + provider,
Scope = string.Join(" ", config.OidScopes.Prepend("openid profile")),
RedirectUri = GetRequestBase(config.SchemeOverride) + $"/sso/OID/{(Request.Path.Value.Contains("/start/", StringComparison.InvariantCultureIgnoreCase) ? "redirect" : "r")}/" + provider,
Scope = string.Join(" ", scopes.Prepend("openid profile")),
};
options.Policy.Discovery.ValidateEndpoints = false; // For Google and other providers with different endpoints
options.Policy.Discovery.RequireHttps = config.RequireHttps;
options.Policy.Discovery.ValidateEndpoints = !config.DoNotValidateEndpoints; // For Google and other providers with different endpoints
options.Policy.Discovery.RequireHttps = !config.DisableHttps;
options.Policy.Discovery.ValidateIssuerName = !config.DoNotValidateIssuerName;
var oidcClient = new OidcClient(options);
var currentState = StateManager[state].State;
var result = await oidcClient.ProcessResponseAsync(Request.QueryString.Value, currentState).ConfigureAwait(false);
@@ -99,7 +102,7 @@ public class SSOController : ControllerBase
return ReturnError(StatusCodes.Status400BadRequest, result.Error + " Try logging in again.");
}
if (!config.EnableFolderRoles)
if (!config.EnableFolderRoles && config.EnabledFolders != null)
{
StateManager[state].Folders = new List<string>(config.EnabledFolders);
}
@@ -244,7 +247,7 @@ public class SSOController : ControllerBase
if (StateManager[state].Valid)
{
_logger.LogInformation($"Is request linking: {isLinking}");
return Content(WebResponse.Generator(data: state, provider: provider, baseUrl: GetRequestBase(), mode: "OID", isLinking: isLinking), MediaTypeNames.Text.Html);
return Content(WebResponse.Generator(data: state, provider: provider, baseUrl: GetRequestBase(config.SchemeOverride), mode: "OID", isLinking: isLinking), MediaTypeNames.Text.Html);
}
else
{
@@ -269,6 +272,7 @@ public class SSOController : ControllerBase
/// <param name="isLinking">Whether or not this request is to link accounts (Rather than authenticate).</param>
/// <returns>An asynchronous result for the authentication.</returns>
[HttpGet("OID/p/{provider}")]
[HttpGet("OID/start/{provider}")]
public async Task<ActionResult> OidChallenge(string provider, [FromQuery] bool isLinking = false)
{
Invalidate();
@@ -289,7 +293,7 @@ public class SSOController : ControllerBase
Authority = config.OidEndpoint?.Trim(),
ClientId = config.OidClientId?.Trim(),
ClientSecret = config.OidSecret?.Trim(),
RedirectUri = GetRequestBase() + "/sso/OID/r/" + provider,
RedirectUri = GetRequestBase(config.SchemeOverride) + $"/sso/OID/{(Request.Path.Value.Contains("/start/", StringComparison.InvariantCultureIgnoreCase) ? "redirect" : "r")}/" + provider,
Scope = string.Join(" ", config.OidScopes.Prepend("openid profile")),
};
options.Policy.Discovery.ValidateEndpoints = false; // For Google and other providers with different endpoints
@@ -423,6 +427,7 @@ public class SSOController : ControllerBase
/// </param>
/// <returns>A webpage that will complete the client-side flow.</returns>
[HttpPost("SAML/p/{provider}")]
[HttpPost("SAML/post/{provider}")]
public ActionResult SamlPost(string provider, [FromQuery] string relayState = null)
{
SamlConfig config;
@@ -470,7 +475,7 @@ public class SSOController : ControllerBase
WebResponse.Generator(
data: Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(samlResponse.Xml)),
provider: provider,
baseUrl: GetRequestBase(),
baseUrl: GetRequestBase(config.SchemeOverride),
mode: "SAML",
isLinking: isLinking),
MediaTypeNames.Text.Html);
@@ -494,6 +499,7 @@ public class SSOController : ControllerBase
/// <param name="isLinking">Whether this flow intends to link an account, or initiate auth.</param>
/// <returns>A redirect to the SAML provider's auth page.</returns>
[HttpGet("SAML/p/{provider}")]
[HttpGet("SAML/start/{provider}")]
public RedirectResult SamlChallenge(string provider, [FromQuery] bool isLinking = false)
{
SamlConfig config;
@@ -516,7 +522,7 @@ public class SSOController : ControllerBase
var request = new AuthRequest(
config.SamlClientId.Trim(),
GetRequestBase() + "/sso/SAML/p/" + provider);
GetRequestBase(config.SchemeOverride) + $"/sso/SAML/{(Request.Path.Value.Contains("/start/", StringComparison.InvariantCultureIgnoreCase) ? "post" : "p")}/" + provider);
return Redirect(request.GetRedirectUrl(config.SamlEndpoint.Trim(), relayState));
}
@@ -594,7 +600,7 @@ public class SSOController : ControllerBase
bool liveTvManagement = config.EnableLiveTvManagement;
var samlResponse = new Response(config.SamlCertificate, response.Data);
List<string> folders;
if (!config.EnableFolderRoles)
if (!config.EnableFolderRoles && config.EnabledFolders != null)
{
folders = new List<string>(config.EnabledFolders);
}
@@ -618,11 +624,14 @@ public class SSOController : ControllerBase
if (config.EnableFolderRoles)
{
foreach (FolderRoleMap folderRoleMap in config.FolderRoleMapping)
if (config.FolderRoleMapping != null)
{
if (folderRoleMap.Role.Equals(role))
foreach (FolderRoleMap folderRoleMap in config.FolderRoleMapping)
{
folders.AddRange(folderRoleMap.Folders);
if (folderRoleMap.Role.Equals(role))
{
folders.AddRange(folderRoleMap.Folders);
}
}
}
}
@@ -655,7 +664,7 @@ public class SSOController : ControllerBase
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())
.ConfigureAwait(false);
return Ok(authenticationResult);
}
@@ -1037,7 +1046,7 @@ public class SSOController : ControllerBase
}
}
private string GetRequestBase()
private string GetRequestBase(string schemeOverride = null)
{
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)))
@@ -1045,9 +1054,14 @@ public class SSOController : ControllerBase
requestPort = -1;
}
if (schemeOverride != "http" && schemeOverride != "https")
{
schemeOverride = null;
}
return new UriBuilder
{
Scheme = Request.Scheme,
Scheme = schemeOverride ?? Request.Scheme,
Host = Request.Host.Host,
Port = requestPort,
Path = Request.PathBase
+21 -1
View File
@@ -126,6 +126,11 @@ public class SamlConfig
/// </summary>
public string DefaultProvider { get; set; }
/// <summary>
/// Gets or sets the redirect scheme override.
/// </summary>
public string SchemeOverride { get; set; }
/// <summary>
/// Gets or sets a mapping of canonical names from the provider to jellyfin user ids.
/// </summary>
@@ -250,6 +255,11 @@ public class OidConfig
/// </summary>
public string DefaultProvider { get; set; }
/// <summary>
/// Gets or sets the redirect scheme override.
/// </summary>
public string SchemeOverride { get; set; }
/// <summary>
/// Gets or sets a mapping of canonical names from the provider to jellyfin user ids.
/// </summary>
@@ -276,7 +286,17 @@ public class OidConfig
/// <summary>
/// Gets or sets a value indicating whether HTTPS in the discovery endpoint is required.
/// </summary>
public bool RequireHttps { get; set; }
public bool DisableHttps { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the OpenID endpoints are validated.
/// </summary>
public bool DoNotValidateEndpoints { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the OpenID issuer name is validated.
/// </summary>
public bool DoNotValidateIssuerName { get; set; }
}
/// <summary>
+61 -1
View File
@@ -447,7 +447,7 @@
<input
is="emby-checkbox"
id="EnableLiveTvManagement"
name="EnableLiveTv"
name="EnableLiveTvManagement"
type="checkbox"
class="sso-toggle"
/>
@@ -559,6 +559,66 @@
</div>
</div>
<div class="checkboxContainer">
<label>
<input
is="emby-checkbox"
id="DisableHttps"
name="DisableHttps"
type="checkbox"
class="sso-toggle"
/>
<span>Disable OpenID HTTPS Discovery (Insecure)</span>
</label>
<div class="fieldDescription checkboxFieldDescription"></div>
</div>
<div
class="checkboxContainer checkboxContainer-withDescription"
>
<label>
<input
is="emby-checkbox"
id="DoNotValidateEndpoints"
name="DoNotValidateEndpoints"
type="checkbox"
class="sso-toggle"
/>
<span>Do Not Validate OpenID Endpoints (Insecure)</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
May be required for Google OpenID
</div>
</div>
<div class="checkboxContainer">
<label>
<input
is="emby-checkbox"
id="DoNotValidateIssuerName"
name="DoNotValidateIssuerName"
type="checkbox"
class="sso-toggle"
/>
<span>Do Not Validate OpenID Issuer Name (Insecure)</span>
</label>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="RoleClaim"
>Scheme Override</label
>
<input
is="emby-input"
id="SchemeOverride"
type="text"
class="sso-text"
/>
<div class="fieldDescription">
If the plugin is redirecting to an insecure URL, set this to
"https"
</div>
</div>
<button
id="SaveProvider"
is="emby-button"
+2 -2
View File
@@ -493,7 +493,7 @@ async function main() {
jfCreds['Servers'][0]['UserId'] = responseJson['User']['Id'];
localStorage.setItem('jellyfin_credentials', JSON.stringify(jfCreds));
localStorage.setItem('enableAutoLogin', 'true');
window.location.replace('" + baseUrl + @"');
window.location.replace('" + baseUrl + @"/web/index.html');
}
document.addEventListener('DOMContentLoaded', function () {
@@ -501,6 +501,6 @@ document.addEventListener('DOMContentLoaded', function () {
});
// https://stackoverflow.com/a/25435165
</script><iframe class='docs-texteventtarget-iframe' sandbox='allow-same-origin allow-forms allow-scripts' src='" + baseUrl + "' style='position: absolute;width:0;height:0;border:0;'></iframe></body></html>";
</script><iframe class='docs-texteventtarget-iframe' sandbox='allow-same-origin allow-forms allow-scripts' src='" + baseUrl + "/web/index.html' style='position: absolute;width:0;height:0;border:0;'></iframe></body></html>";
}
}
+5 -1
View File
@@ -1,7 +1,7 @@
name: "SSO Authentication"
guid: "505ce9d1-d916-42fa-86ca-673ef241d7df"
imageUrl: "https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/main/img/logo.png"
version: "3.5.0.0"
version: "3.5.2.1"
targetAbi: "10.8.0.0"
framework: "net6.0"
owner: "9p4"
@@ -15,6 +15,10 @@ artifacts:
- "IdentityModel.OidcClient.dll"
- "IdentityModel.dll"
changelog: |
3.5.2.1: Hotfix for SAML null checks
3.5.2.0: Allow overriding the scheme used for generating URLs.
3.5.1.1: Change iframe URL to point to the web UI instead of the root
3.5.1.0: Improved paths! No more obscure "p" versus "r" URLs! Improve final redirect for automatic authentication. Add more configuration options for OpenID discovery.
3.5.0.0: Add support for Live TV authentication. Fix various null pointer bugs.
3.4.0.0: Add user self-service for linking existing accounts + managing existing links. Allow IDP accounts to be linked to jellyfin accounts with a different display-name.
3.3.0.0: Add fallback authentication provider. Add OpenID admin page.
Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

+8 -5
View File
@@ -17,6 +17,7 @@ This section is broken into providers that support Role-Based Access Control (RB
- ✅ Google OIDC
- ❗ Usernames are numeric
- ❗ Requires disabling validating OpenID endpoints
## General Options, when RBAC is supported
@@ -52,7 +53,7 @@ identity_providers:
secret: <redacted>
authorization_policy: one_factor
redirect_uris:
- https://jellyfin.example.com/sso/OID/r/authelia
- https://jellyfin.example.com/sso/OID/redirect/authelia
```
### Jellyfin's Config
@@ -113,7 +114,7 @@ Now we can add this property mapping to authentik's Jellyfin OAuth provider:
![image](img/authentik-config-04.jpg)
- Edit / Update your Jellyfin OAuth provider
- Verify your **"Redirect URIs/Origins (RegEx)"** follows the format: `https://domain.tld/sso/OID/r/Authentik`.
- Verify your **"Redirect URIs/Origins (RegEx)"** follows the format: `https://domain.tld/sso/OID/redirect/Authentik`.
- Under **"Advanced Protocol Settings"**, add the **Group Membership** Scope
![image](img/authentik-config-05.jpg)
@@ -133,6 +134,8 @@ authentik:
OidScopes: ["groups"]
```
If you recieve the error `Error processing request.` from Jellyfin when attempting to login and the Jellyfin logs show `Error loading discovery document: Endpoint belongs to different authority` try setting `Do not validate endpoints` in the plugin settings.
## Keycloak OIDC
Keycloak in general is a little more complicated than other providers. Ensure that you have a realm created and have some usable users.
@@ -145,7 +148,7 @@ Ensure that the following configuration options are set:
- Access Type: Confidential
- Standard Flow Enabled
- Redirect URI: https://myjellyfin.example.com/sso/OID/r/PROVIDER_NAME
- Redirect URI: https://myjellyfin.example.com/sso/OID/redirect/PROVIDER_NAME
- Base URL: https://myjellyfin.example.com
Press the "Save" button at the bottom of the page and open the "Credentials" tab. Note down the secret.
@@ -179,9 +182,9 @@ Ensure that the following configuration options are set:
- Sign Documents on
- Sign Assertions off
- Client Signature Required off
- Redirect URI: [https://myjellyfin.example.com/sso/SAML/p/PROVIDER_NAME](https://myjellyfin.example.com/sso/SAML/p/PROVIDER_NAME)
- Redirect URI: [https://myjellyfin.example.com/sso/SAML/start/PROVIDER_NAME](https://myjellyfin.example.com/sso/SAML/start/PROVIDER_NAME)
- Base URL: [https://myjellyfin.example.com](https://myjellyfin.example.com)
- Master SAML processing URL: [https://myjellyfin.example.com/sso/SAML/p/PROVIDER_NAME](https://myjellyfin.example.com/sso/SAML/p/PROVIDER_NAME)
- Master SAML processing URL: [https://myjellyfin.example.com/sso/SAML/start/PROVIDER_NAME](https://myjellyfin.example.com/sso/SAML/start/PROVIDER_NAME)
Press the "Save" button at the bottom of the page.