ShibbolethSupport

Add Shibboleth based Single Sign-On authentication support for moinmoin logins .

Authentication support

strict session support

lazy session support

I have written a basic Shibboleth auth module with lazy authentication (users need to click the Login link):

from MoinMoin.auth import ContinueLogin, MultistageRedirectLogin
from MoinMoin.auth.http import HTTPAuth

class ShibbolethAuth(HTTPAuth):
    """ Authenticate with Shibboleth """
    name = 'shibboleth'
    login_inputs = ['special_no_input']

    def request(self, request, user_obj, **kw):
        try:
            # hack to make HTTPAuth work with Shibboleth
            if request.env.get('REMOTE_USER'):
                request.env['AUTH_TYPE'] = 'Basic'
        except AttributeError:
            pass
        return HTTPAuth.request(self, request, user_obj, **kw)

    def login(self, request, user_obj, **kw):
        if kw.get('multistage'):
            u, cont = self.request(request, user_obj, **kw)
            return ContinueLogin(u)
        else:
            shiburl = request.getQualifiedURL('/Shibboleth.sso/Login')
            return MultistageRedirectLogin(shiburl + '?target=%return')

I have tested it with MoinMoin 1.7.1. Simply paste it into your configuration file (or in a separate file and import it). It also works with non-lazy (strict) authentication.

Bug: You always get redirected to the front page after login.

I'm not experienced with Python or the MoinMoin codebase, so any suggestions are welcome. --PerOlofsson

Lazy Session, new version - Juli 2018

To clarify the concepts discussed on this page a bit more: The idea with lazy login is to open the Wiki for read-only access to the public but protect it with Shibboleth for write access. This this is different from the strict session stuff found above, which blocks all access to the Wiki without valid credentials.

I found the above lazy login code to be lacking Single Sign Out and not documented very cleanly. Also it was written for an older version of Moin. I therefore hacked together this. The code was tested it in our production environment and it *seems* to run fine, including Single Sign Out, working redirect back to the page we came from and update of user info (email, alias name). If I find any bugs in the future, I will update this page accordingly.

DerBachmannRocker, MHT

from MoinMoin.auth import *
from werkzeug import redirect, abort, url_quote, url_quote_plus
from MoinMoin import log
logging = log.getLogger(__name__)
#Configure Moin logging and set log level to DEBUG to see all messages!

class mhtShibbolethAuth(GivenAuth):
    # Authenticate users with Shibboleth SP
    # Adapted and improved from:
    # https://moinmo.in/ShibbolethSupport
    #
    # Tested with Shibboleth SP v2.6.0 on Apache for Debian 8
    # Moin 1.9.8
    #
    # This requires Shibboleth SP (mod_shib), fully configured, to accept your users.
    #
    # To use this class
    # save mhtshibbolethlogin.py to /etc/moin
    # make executable: chmod a+rx /etc/moin/mhtshibbolethlogin.py
    # Put this in your mywiki.py
    # from mhtshibbolethlogin import mhtShibbolethAuth
    # auth = [mhtShibbolethAuth(autocreate=True)]
    #
    # Mapping of Shibboleth user attributes to Moin:
    # moin aliasname: displayName (urn:oid:2.16.840.1.113730.3.1.241)
    # moin email: mail
    # moin username: REMOTE_USER (configure in shibboleth2.xml which value gets put into REMOTE_USER)
    #
    ################################
    #Hints for Apache configuration
    ################################
    #Place these lines inside your virtual host configuration file
    #
    ## This uses lazy login. Do not protect root of Moin - but make sure Shibboleth is enabled
    ## https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPApacheConfig
    ## Warning. Use exactly this syntax, or else Shibboleth SP may never be invoked / session variables may stay empty.
    ## Require shibboleth is critical here.
    #<Location / >
    #    AuthType Shibboleth
    #    ShibRequestSetting requireSession false
    #    Require shibboleth
    #</Location>
    ## Protect moin admin special pages with Shibboleth
    #<Location /SystemInfo >
    #    ShibRequestSetting requireSession true
    #    Require valid-user
    #</Location>
    #<Location /SystemAdmin >
    #    ShibRequestSetting requireSession true
    #    Require valid-user
    #</Location>
    #
    ######################################
    #Hints for Shibboleth SP configuration
    ######################################
    # This class will accept any Shibboleth user that is able to log in!
    # If you need to check for any user attributes (e.g. eduPersonScopedAffiliation, eduPersonPrincipalName)
    # please configure in shibboleth2.xml:
    # <ApplicationDefaults ... sessionHook="/Shibboleth.sso/AttrChecker" />
    # AttrChecker elements can be used to check for required values before login is allowed
    # https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPHandler
    #<Handler type="AttributeChecker" Location="/AttrChecker" template="attrChecker.html" flushSession="true">
    #   <AND>
    #     <!-- attribute-map.xml: id of attributes -->
    #     <Rule require="displayName"/>
    #     <RuleRegex require="eppn" caseSensitive="false">^.+@mh-trossingen\.de$</RuleRegex>
    #   </AND>
    #</Handler>
    #
    #
    #

    name = 'shibboleth'
    logout_possible = True
    login_inputs = ['special_no_input']

    #GivenAuth constructor + Shibboleth login/logout hooks
    def __init__(self,
                 env_var=None,  # environment variable we want to read (default: REMOTE_USER)
                 user_name=None,  # can be used to just give a specific user name to log in
                 autocreate=False,  # create/update the user profile for the auth. user
                 strip_maildomain=False,  # joe@example.org -> joe
                 strip_windomain=False,  # DOMAIN\joe -> joe
                 titlecase=False,  # joe doe -> Joe Doe
                 remove_blanks=False,  # Joe Doe -> JoeDoe
                 coding=None,  # for decoding REMOTE_USER correctly (default: auto)
                 shibLoginHook="/Shibboleth.sso/Login",
                 shibLogoutHook="/Shibboleth.sso/Logout",
                ):
        self.shibLoginHook = shibLoginHook
        self.shibLogoutHook = shibLogoutHook
        GivenAuth.__init__(self,
                           env_var,
                           user_name,
                           autocreate,
                           strip_maildomain,
                           strip_windomain,
                           titlecase,
                           remove_blanks,
                           coding
                          )

    def login(self, request, user_obj, **kw):
        if kw.get('multistage'):
            logging.debug("Returning from Shibboleth login page. Redirect back to Moin was successful.")
            #test if shibboleth login was successful
            if request.environ.get("Shib-Session-ID"):
                logging.debug("A Shibboleth session ID was found. Login successful. Returning user object.")
                u, cont = self.request(request, user_obj, **kw)
                u.aliasname = request.environ.get("displayName")
                u.email = request.environ.get("mail")

                if u and self.autocreate:
                    logging.debug("calling create_or_update to autocreate user %r" % u.name)
                    u.create_or_update(True)

                return LoginReturn (u, True, message=None, multistage=None, redirect_to=url_quote(request.values.get("wikiPageBeforeLogin")))
            else:
                #this should never happen (?)
                logging.warning("No Shibboleth session ID found. Aborting.")
                return CancelLogin("Could not log in via Shibboleth. Please contact your Administrator.")

        #stolen from openid connector
        # openid is designed to work together with other auths. I did NOT test this functionality
        elif user_obj and user_obj.valid:
            return ContinueLogin(user_obj)
        else:
            #Request to Shibboleth idp. Pass ?target= parameter pointing back to this multistage login procedure
            return_to = get_multistage_continuation_url(request, self.name, extra_fields={"wikiPageBeforeLogin" : request.base_url})
            #return URL needs to be escaped. Otherwise redirect back to Moin (and multistage) will not work correctly
            return_to = url_quote(return_to)
            logging.debug("shibboleth multistage return url: %s", return_to)
            shiburl = "%s?target=%s" %(request.getQualifiedURL(self.shibLoginHook).encode("utf-8"), return_to)
            #request to Shibboleth idp
            return MultistageRedirectLogin( shiburl )

    def logout(self, request, user_obj, **kw):
        shiburl = request.getQualifiedURL(self.shibLogoutHook).encode("utf-8")
        logging.info("Logout: redirecting to %s" % shiburl)
        abort(redirect(shiburl))
        return GivenAuth.logout(self, request, user_obj, **kw)

Discussion

So, to summarize, we need auth method '' for shibboleth v1 and 'shibboleth' for v2? And that's all we need to do on the moin side to start with basic support for it? Or do we need to wait for more code first?


CategoryFeatureRequest

MoinMoin: ShibbolethSupport (last edited 2018-06-11 21:53:00 by nt01)