flipboxfactory / saml-sp

SAML Service Provider (SP) Plugin for Craft CMS
https://saml-sp.flipboxfactory.com/
Other
19 stars 5 forks source link

Disallow Login from Native Craft Login / Only Allow SSO Login #52

Open dsmrt opened 4 years ago

dsmrt commented 4 years ago

Basically, only allow login through SAML/SSO.

Please Note: This feature may block all admin users from getting into the Control panel and that might create issues if SSO goes down for any reason.

dsmrt commented 4 years ago

One thing we could do is have a config in the config/saml-sp.php, like requireSsoLogin (boolean), which enables and disables this feature. That way as simple code push can mitigate issues that may arise.

dsmrt commented 3 years ago

Update on this. The biggest thing preventing me from adding this feature overriding the functionality with the elevated session manager and auth manager. See https://github.com/craftcms/cms/blob/62b9b99fc78b69a127db2d2c9003fd4072f15d3b/src/web/assets/cp/src/js/ElevatedSessionManager.js

Basically, to add this feature, I'm worried about the amount of work it'd take to get that piece working (without it being hacky) and the upkeep of the feature.

dsmrt commented 3 years ago

Also, relavant: https://github.com/craftcms/cms/issues/1471#issuecomment-407034303

ilicmarko commented 2 years ago

Is there a status update on this? Because having a normal login can look like a tech risk item for corp.

hiasl commented 2 years ago

Having the same requirement here. We are in a government context and the only way to login should be SAML. BTW: there is a new command line feature to create impersonating links which could be used as a fallback for admins in cases SAML/IDP is not working: https://github.com/craftcms/cms/pull/9919

dsmrt commented 2 years ago

The hard part with this is hooking into Craft where there isn’t extendibility in place.

I was hoping to see that would happen with Craft 4.0 due to there being better integration option for third party authentication/authorization.

I can revisit this and see if there is something more temporary (Craft 3.x to 4.0) we can do.

dsmrt commented 2 years ago

@hiasl Thanks for the link!

ilicmarko commented 2 years ago

What I will try to do is override the default login page. Create a custom login template that will only list the SSO buttons. I think I saw a way to list/fetch all the SSOs but will have to check. Here is a related solution: https://github.com/craftcms/cms/issues/6134#issuecomment-633395451

Another idea I have, but maybe strictly to the corp I work with is: Disable the login page entirely and use the SSO short link provided by IDP which will log you in and redirect you to Craft.\

Will keep the issue updated. Relevant XKCD (Wisdom of the Ancients).

ilicmarko commented 2 years ago

The hard part with this is hooking into Craft where there isn’t extendibility in place.

I was hoping to see that would happen with Craft 4.0 due to there being better integration option for third party authentication/authorization.

I can revisit this and see if there is something more temporary (Craft 3.x to 4.0) we can do.

Maybe a temporary solution would be to add a twig variable that would allow the user to list the buttons. Because a custom login page won't work because we can't query/fetch anything :cry:

timeverts commented 2 years ago

@dsmrt, another issue I have is that the session times out and the enter password box reappears (for native Craft login):

image

I've even set the userSessionDuration general configuration parameter to 0, but it still pops up periodically.

When logged in via SSO, what's a user expected to do in this case? Sure a user can go back to the login URL, but most users are unlikely to know they would have to do this.

dsmrt commented 2 years ago

@timeverts this is one of the biggest hurdles in closing this issue. That is controlled by the "Elevated Session".

You may be able to overwrite it. Look here: https://github.com/craftcms/cms/blob/a7b3d1813b92ef489d3566d30daa9a4688b6ec00/src/web/assets/cp/src/js/ElevatedSessionManager.js

dsmrt commented 1 year ago

Elevated Session is still a problem (the login modal in the cp). There's a lot that'd go into fixing this including ui changes on the user element and overwriting the set password functionality.

Although, it looks like you can get around the modal (asking for the password) by setting the general config item elevatedSessionDuration to 0 (zero).

<?php
return [
   'elevatedSessionDuration' => 0,
];
uandco commented 1 year ago

Hi, what is the roadmap for this? Has any talk with Pixel & Tonic been initiated to make all these password elements configurable?

ilicmarko commented 1 year ago

This should definitely include P&T because it is a major problem/blocker for SSO.

We currently can't generate a GraphQL token because it is requesting a password validation, our headless development has stopped completely. The problem is there is no solution in sight, because even if you want to copy the reset password link and play around with that (idea) you can't, it requires password validation.

cc @brandonkelly @craftcms

Edit:

I did some digging and found that this modal is created as a part of ElevatedSessionManager which means if I disable the elevated session this won't popup 'elevatedSessionDuration' => 0. This effect both session expiration and password request for changes.

uandco commented 1 year ago

@timeverts this is one of the biggest hurdles in closing this issue. That is controlled by the "Elevated Session".

You may be able to overwrite it. Look here: https://github.com/craftcms/cms/blob/a7b3d1813b92ef489d3566d30daa9a4688b6ec00/src/web/assets/cp/src/js/ElevatedSessionManager.js

The screenshot above ("Your session has ended") is not the elevated session modal, it's <form id="loginmodal"> which is not present in ElevatedSessionManager.js. So setting elevatedSessionDuration to 0 doesn't disable this modal.

This is where it's actually handled: https://github.com/craftcms/cms/blob/3c16404560f03b7c63f31cb6e368fb4787e86dd4/src/web/assets/cp/src/js/AuthManager.js

devinpitcher commented 1 year ago

Any updates on this?

dsmrt commented 1 year ago

Unfortunately, I don't think we are going to be able to fix this.

There are too many areas that need highly customized overrides that will be impossible to support as new Craft versions come out. Also, there are some areas like forgot password which can't be overwritten. At least, not through Craft.

We've (Craft and us) talked before about the future work here but I don't know where that's at. I just reached out to them now but I know they have a lot to support. I'll see what I can find out.

JJefferyDev commented 1 year ago

I just wanted to add my work around for this issue. I copied the code directly for the button from view source and input that into a basic Craft login form, as used as an example on their website: https://craftcms.com/knowledge-base/front-end-user-accounts

< form method="post" accept-charset="UTF-8">
    {{ csrfInput() }}
    {{ actionInput('users/login') }}

    <a href="/sso/login/request/<my idp uid, will make env variable>?RelayState=<my website url/userdash>" class="btn submit">
        Via Microsoft AD
    </a>
</form >

It's a bit goofy and requires me to find the uid information beforehand, but it works. This is for a government intranet site. I don't have the .env file variable working yet, but that's my next step. This way completely bypasses non-admin users from accessing the craft dashboard.

d--j commented 5 months ago

craftcms/cms#1471 (comment)

I use these patches for a Craft4 site (that I register in every CP page):

jQuery(function ($) {
    if (document.body.classList.contains('edit-user')) {
        // this is an SSO user?
        if ($('#fields-fieldUserSamlIdentity-field table tbody tr').length > 0) {
            // disabled a bunch of fields the user should not change
            ['#username', '#fullName', '#email', '#newPassword'].forEach(function (selector) {
                $(selector).addClass('disabled').prop('disabled', true).prop('readonly', true);
            });
        }
    }

    // Patch the AuthManager, when we cannot use it because the user is an SSO user and does not have a password
    if (window.CurrentBackendUser && CurrentBackendUser.isSSO) {
        Craft.AuthManager.prototype.showLoginModal = function () {
            var quickShow;

            if (this.showingLogoutWarningModal) {
                this.hideLogoutWarningModal(true);
                quickShow = true;
            } else {
                quickShow = false;
            }

            this.showingLoginModal = true;

            if (!this.loginModal) {
                var $form = $('<div id="loginmodal" class="modal alert fitted"/>'),
                    $body = $(
                        '<div class="body"><h2>' +
                        Craft.t('app', 'Your session has ended.') +
                        '</h2><p>' +
                        Craft.t('_patch', 'You need to login with you SSO provider in another tab. After successful login, come back to this tab and refresh the login status.') +
                        '</p></div>'
                    ).appendTo($form),
                    $inputContainer = $('<div class="inputcontainer">').appendTo($body),
                    $inputsFlexContainer = $('<div class="flex"/>').appendTo(
                        $inputContainer
                    ),
                    $loginButtonContainer = $('<div/>').appendTo($inputsFlexContainer),
                    $refreshButtonContainer = $('<div/>').appendTo($inputsFlexContainer);

                $('<a class="btn submit" target="_blank"/>')
                    .attr('href', '/' + Craft.cpTrigger + '/login')
                    .text(Craft.t('_patch', 'Login again'))
                    .appendTo($loginButtonContainer);
                this.$refreshBtn = Craft.ui.createButton({
                    label: Craft.t('_patch', 'Refresh login status'),
                    spinner: true,
                })
                    .appendTo($refreshButtonContainer);
                this.$loginErrorPara = $('<p class="error"/>').appendTo($body);

                // set these to empty jQuery objects to not break other parts of AuthManager
                this.$loginBtn = this.$passwordInput = $('#this-is-not-an-ID');

                this.loginModal = new Garnish.Modal($form, {
                    autoShow: false,
                    closeOtherModals: false,
                    hideOnEsc: false,
                    hideOnShadeClick: false,
                    shadeClass: 'modal-shade dark loginmodalshade',
                    onFadeIn: () => {
                    },
                    onFadeOut: () => {
                    },
                });

                this.addListener(this.$refreshBtn, 'click', 'refreshSessionStatus');
            }

            if (quickShow) {
                this.loginModal.quickShow();
            } else {
                this.loginModal.show();
            }
        }
        Craft.AuthManager.prototype.refreshSessionStatus = function () {
            this.$refreshBtn.addClass('loading');
            this.doRefreshSessionStatus = true;
            this.clearLoginError();
            this.checkRemainingSessionTime();
        }
        var oldUpdateRemainingSessionTime = Craft.AuthManager.prototype.updateRemainingSessionTime;
        Craft.AuthManager.prototype.updateRemainingSessionTime = function (remainingSessionTime) {
            oldUpdateRemainingSessionTime.apply(this, arguments);
            if (this.doRefreshSessionStatus) {
                this.$refreshBtn.removeClass('loading');
                if (remainingSessionTime < 1) {
                    this.showLoginError(Craft.t('_patch', 'Login status did not change.'));
                }
                this.doRefreshSessionStatus = false;
            }
        }
    }
});
timeverts commented 1 week ago

@dsmrt, just wondering whether you have had any further discussions about this with @brandonkelly and the Pixel and Tonic team since Craft 5 was released?

I wonder whether there might be some new functionality in Craft 5 that now makes it possible to only permit SSO login.

uandco commented 1 week ago

I wonder whether there might be some new functionality in Craft 5 that now makes it possible to only permit SSO login.

Well, Craft 5 now has native SAML SSO on enterprise plans, so yes, but highly doubt this is going to be accessible to third party plugins for lower plans.