Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One Login with SSO disables normal login #5

Closed
Pfuenzle opened this issue Feb 15, 2022 · 24 comments
Closed

One Login with SSO disables normal login #5

Pfuenzle opened this issue Feb 15, 2022 · 24 comments

Comments

@Pfuenzle
Copy link
Contributor

Hi,
I'm not sure if it is supposed to be like this, but after logging in once using SSO, I'm unable to login using a password using the account. Using SSO is still working, but I guess it would be preferrable to keep the password-login.

The error in Jellyfin is:

[01:41:01] [ERR] [40] Jellyfin.Server.Implementations.Users.UserManager: Error authenticating with provider InvalidOrMissingAuthenticationProvider
MediaBrowser.Controller.Authentication.AuthenticationException: User Account cannot login with this provider. The Normal provider for this user cannot be found
   at Jellyfin.Server.Implementations.Users.InvalidAuthProvider.Authenticate(String username, String password)
   at Jellyfin.Server.Implementations.Users.UserManager.AuthenticateWithProvider(IAuthenticationProvider provider, String username, String password, User resolvedUser)
[01:41:01] [INF] [40] Jellyfin.Server.Implementations.Users.UserManager: Authentication request for Pfuenzle has been denied (IP: <null>).
[01:41:01] [ERR] [40] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request: Invalid username or password entered. URL POST /Users/authenticatebyname.

Seems like the default auth provider of the user gets cleared, is this intentional?
I'm additionally using LDAP auth, may this be the reason for the error?

Also another question regarding the library's available to the user, would it be possible to get the library's from the users existing Jellyfin account, so they can be customized. From what I've seen now, it's only possible to give access to the same set of library's with SSO, no matter if the user is an admin or not.

@9p4
Copy link
Owner

9p4 commented Feb 15, 2022

As of my understanding, an account can only be tied into one authentication provider at a time. By logging in through SSO, it will force that account to use that provider thereon.

As for the user access, I am working to add more features to authorize the user based on user roles/attributes from SAML/OID. I'll keep this issue open for that.

@Pfuenzle
Copy link
Contributor Author

Pfuenzle commented Feb 15, 2022

Right, just checked the Database to be able to log in with a username + password again, and every user can only have one Auth Provider String set.

This is a bit inconvenient though, at least for me, how about others? Some people may still want to be able to login with a password, at least as a backup option.

So from what I've seen, when login in with SSO the Auth Provider gets overwritten to this plugin, but when trying to log in with a password it doesn't get overwritten back.

I guess this line is the one overwriting it?

user.AuthenticationProviderId = GetType().FullName;

If so, then shouldn't it be possible to set the Auth Provider back to the default/LDAP by doing
user.AuthenticationProviderId = "Jellyfin.Server.Implementations.Users.DefaultPasswordResetProvider" after the user has been logged in?

Maybe it's even possible to pre-save the Auth Provider the user had before, as it's just a string, and then reset it back after login in, which would be even better.

This would allow to login with SSO anytime, as the auth provider would get overwritten to SSO temporarily, but also enable normal auth login, as the auth provider gets set back to the default, so I don't see any drawbacks here (if I'm not wrong in the technicality here).

I don't have any experience with C#, but if you haven't got any problems with this implementation and are busy with the role based auth, I could try to implement this.

This is probably not a good practic, but if it doesn't work programmatically, it could be done with SQL using UPDATE Users SET AuthenticationProviderID="Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider" WHERE username="USERNAME"




Edit:
Also, what would happen if

user.SetPermission(PermissionKind.EnableAllFolders, enableAllFolders);

And
user.SetPermission(PermissionKind.IsAdministrator, isAdmin);

would not be set? Wouldnt this then just get the values from the existing accounts? So you could get the default allowed library's for nonexistent accounts that the SSO plugin is creating from the plugin config, but if the account exists it would get the admin state and the library's from the Jellyfin DB so it could get customized.
Or do you want to keep the configuration side out of Jellyfin and want to make groups which have access to certain libraries?

@9p4
Copy link
Owner

9p4 commented Feb 16, 2022

Alright, so I've pushed a commit that should fix the "overwrite of existing user perms" issue. As soon as I add a "unregister" API endpoint to unlink the user from SSO, I'll push a new plugin version. Thanks for the bug report!

@9p4
Copy link
Owner

9p4 commented Feb 16, 2022

Alright, I have added an endpoint to "unregister" the user. I'll publish a new major version of the plugin (because of the new configuration changes). Test it out and let me know how it goes. Thanks!

@9p4
Copy link
Owner

9p4 commented Feb 16, 2022

Plugin version bumped. Try updating it and let me know.

@9p4
Copy link
Owner

9p4 commented Feb 16, 2022

If those values are not set, it should go to the default JF user config: not admin, all directories are allowed.

@Pfuenzle
Copy link
Contributor Author

Pfuenzle commented Feb 16, 2022

Great, the unregistering works fine, but for some reasons the role/library mapping doesnt seem to work with the new version, did it work for you?

Loging in works fine, but I dont have access to any librarys, even when setting the necessary roles and logging out and in again.

Here is the content of the SSO-Auth.xml together with a screenshot of the user roles. I checked it multiple times but cant see anything wrong.
https://paste.blyatflix.com/?f1a90afe0fb7df3f#5hjTMALFX6SEnFyTQgEYXet8M8faCWkeGD3BDZnvExk4

Also regarding your push with the warning message, if the main Jellyfin page hasnt been loaded before, couldnt this be bypassed by loading an 1 pixel iFrame (like a tracking pixel basically) with the main Jellyfin page and then, when the main page has been fully loaded, redirecting to the actual SSO page? This way, the user would not need to open the login page anymore and site admins could link directly to "https:///sso/OID/p/jellyfin", without needing to link or redirecting to the login page. What are your thoughts about this?

Edit: Just saw that you already have a load listener for the SSO JS functions in the script

document.addEventListener('DOMContentLoaded', function () {
, so maybe this would already wait until an iFrame is loaded, so just adding one would be enough to populate the localstorage?
Otherwise, something like this may be good

    checkIframeLoaded()
    
    function checkIframeLoaded() {
        // console.log(" function checked")
        // Get a handle to the iframe element
        var iframe = document.getElementsByClassName("docs-texteventtarget-iframe")[0];
        console.log("iframe", iframe)
        // check if the iframe is loaded or not (= undefined = null)
        if (iframe == null) {
            // If we are here, it is not loaded. Set things up so we check the status again in 1000 milliseconds
            window.setTimeout(checkIframeLoaded, 1000);
        } else {
            var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
            // Check if loading is completed
            if (iframeDoc.readyState == 'complete') {
    
                // The loading is complete, call the function we want executed once the iframe is loaded
                iframeDoc.addEventListener("keydown", dispatchkeyboard, false);
            } else {
                // even if the iframe is loaded the "readystate may not be completed yet" so we need to recall the function.
                window.setTimeout(checkIframeLoaded, 1000);
            }
        }
    }
    ```
(Taken from https://stackoverflow.com/questions/751435/detecting-when-iframe-content-has-loaded-cross-browser)

@9p4
Copy link
Owner

9p4 commented Feb 16, 2022

I just tested out roles and and it seems like it's all good and working. Have you added a role mapper to the client?

2022-02-16-18-51-16

@9p4
Copy link
Owner

9p4 commented Feb 17, 2022

I've implemented your comment about the iframe. Could you build from main and try it out? This does change the config parameters a little, so you may need to remove your old config file and add the provider again.

@Pfuenzle
Copy link
Contributor Author

Pfuenzle commented Feb 17, 2022

Weird, yes I have the group mapper set up, but if it works for you, the problem seems to be on my side, so I'll try to figure it out.
Does <RoleClaim>realm_roles</RoleClaim> realm_roles in this case has to be the same value as "Token Claim Name" in Keycloak? Because if I set it to the same name, I get an Unexpected character encountered while parsing value: j. Path '', line 0, position 0. error.

With the iframe, it works great using Firefox, but using Chrome or the Chrome based Edge (tried both without extensions), I was stuck with "Logging in..." until I reloaded the page. Everything from the Jellyfin main page content got loaded in successfully, but I guess somehow the iframe ready state did not get updated, so I had to manually update to page to get redirected to jellyfin.

No idea why it didnt work, but if you also dont have an idea how to fix this, maybe an automatic page reload after like 20 seconds + a "Click here to reload" button would also suffice.

@9p4
Copy link
Owner

9p4 commented Feb 17, 2022

Ah, that seems to be your issue. It should not be realm_roles, but instead realm_access. My mistake in the README.

@Pfuenzle
Copy link
Contributor Author

Tried that too, still no access to library's for the Testaccount.
Just for clarification, are the roles supposed to be general Keycloak roles or client specific roles? I tried both, just asking to narrow my possible error area down.

@9p4
Copy link
Owner

9p4 commented Feb 17, 2022

I actually use both. As long as the role mappings are configured correctly, it shouldn't be an issue.

@9p4
Copy link
Owner

9p4 commented Feb 17, 2022

Just to be sure, how are you getting the IDs of the libraries?

@Pfuenzle
Copy link
Contributor Author

Pfuenzle commented Feb 17, 2022

Using inspect element on the library thumbnails and then from the URL it ref's to. But it should be fine, as I had a script that triggers library scans for specific library's via the API, and they worked there (unless they changed something since the last few version)

But you're right, I will try it via the method you mentioned in the Readme

@9p4
Copy link
Owner

9p4 commented Feb 17, 2022

I've fixed the reload functionality too, so it should work cross platform now (tested on Chromium and Firefox latest on Linux)

@9p4
Copy link
Owner

9p4 commented Feb 17, 2022

Could you also create a new issue for the issue with the RBAC since I feel there's enough scope creep on this issue anyway.

@9p4
Copy link
Owner

9p4 commented Feb 17, 2022

Because if I set it to the same name, I get an Unexpected character encountered while parsing value: j. Path '', line 0, position 0. error.

The plugin expects a Javascript object to be under the realm_access attribute with a key of roles and an array of valid roles.

@Pfuenzle
Copy link
Contributor Author

I agree, this may have gotten a bit off topic😅
But I don't think another issue will be necessary, as I tried it today again and the permissions worked fine now
Maybe some caching issue on my end that I overlooked, sorry about that.

The new iframe fix seems to work great on everything I tested it with, great work 👍

Regarding the initial topic of this issue, I'm currently trying out a new option with an optional setting DefaultProvider which can be set in the config.
If its not set, everything stays as it is currently, but if it's set, after successfully logging the user in, their Default Login Provider gets set to whatever is in the config.
I think that's a good option as you can either ignore it or use it, which is practical for me so I can login with SSO on my desktop, but also log in via LDAP on my TV e.g.

@9p4
Copy link
Owner

9p4 commented Feb 17, 2022

I thought that quick connect would be a better solution for devices that do not have a web browser to do the auth flow with. It doesn't seem as if it's fully supported by all clients just yet, but ideally this would be the best flow for SSO on those devices.

I'm also going to close this issue since the problems found in it are all solved.

@9p4 9p4 closed this as completed Feb 17, 2022
@Cookie-Monster-Coder
Copy link

Cookie-Monster-Coder commented Apr 6, 2022

Regarding the initial topic of this issue, I'm currently trying out a new option with an optional setting DefaultProvider which can be set in the config.

How did you set this up? @Pfuenzle

I put

 "defaultprovider": "Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider"

in my curl command, but it's not saving in the config.

@9p4
Copy link
Owner

9p4 commented Apr 6, 2022

The pull for the additional fix hasn't been merged yet. Because the original issue was fixed (allow for reassignment of user), I'm going to leave this issue as closed. Let me know if you want it reopened.

#9

@Cookie-Monster-Coder
Copy link

Any idea on when it will merge?

@Pfuenzle
Copy link
Contributor Author

Pfuenzle commented Apr 7, 2022

I'll try to merge it today 😅
I made the pull request in my holidays and didn't have a lot of time since, but I'm free today so I'll get into it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants