5042: Multifactor authentication with SAML2.0 is not working

pbuzas

What version are you running?

Tested in beanbag/reviewboard:4.0.13, beanbag/reviewboard:5.0.7, beanbag/reviewboard:6.0.2, beanbag/reviewboard:7.0.3

Docker image was used

What's the URL of the page containing the problem?

Internal testing.

What steps will reproduce the problem?

  1. Enable SAML 2.0 Authentication
  2. Set up the SAML config on both sides (reviewboard and AZURE)
  3. Log in to any Azrure service with biometrics or PIN
  4. Try to log in to Review Board with SAML
  5. Review Board refuses the authentication, throwing AADSTS75011 error message

What is the expected output? What do you see instead?

The expected output is a successfull login. Insted I see the attached error message "AADSTS75011: Authentication method 'X509, MultiFactor, X509Device' by which the user authenticated with the service doesn't match requested authentication method 'Password, ProtectedTransport'. Contact the Reviewboard application owner."

What operating system are you using? What browser?

Windows 10, 11
Firefox, Edge, Chrome

Please provide any additional information below.

The issue is that reviewboard forces Password based authentication from SAML. If I use password login to any Azure service, the authentication works as expected. If a user authenticates with PIN or biometrics Azure responds with a different authnmethod, ('X509, MultiFactor, X509Device) and this is not supported.

This could be resolved if the mentioned authentication method were added to the supported list, or the force_authn optional parameter were to set true, so the users can re-authenticate with the correct method (or maybe wired to the settings page, so admins can add methods or enable force_authn). An other major issue is that this is not communicated towards the users, so they usually don't know why the login works sometimes and why it fails the other. (Azure sends back tha latest login type)

For me, the supported list lookse like this
/venv/lib/python3.11/site-packages/onelogin/saml2/schemas/saml-schema-authn-context-types-2.0.xsd:

<xs:group name="AuthenticatorChoiceGroup">
<xs:choice>
<xs:element ref="PreviousSession"/>
<xs:element ref="ResumeSession"/>
<xs:element ref="DigSig"/>
<xs:element ref="Password"/>
<xs:element ref="RestrictedPassword"/>
<xs:element ref="ZeroKnowledge"/>
<xs:element ref="SharedSecretChallengeResponse"/>
<xs:element ref="SharedSecretDynamicPlaintext"/>
<xs:element ref="IPAddress"/>
<xs:element ref="AsymmetricDecryption"/>
<xs:element ref="AsymmetricKeyAgreement"/>
<xs:element ref="SubscriberLineNumber"/>
<xs:element ref="UserSuffix"/>
<xs:element ref="ComplexAuthenticator"/>
</xs:choice>
</xs:group>

<xs:group name="AuthenticatorSequenceGroup">
<xs:sequence>
<xs:element ref="PreviousSession" minOccurs="0"/>
<xs:element ref="ResumeSession" minOccurs="0"/>
<xs:element ref="DigSig" minOccurs="0"/>
<xs:element ref="Password" minOccurs="0"/>
<xs:element ref="RestrictedPassword" minOccurs="0"/>
<xs:element ref="ZeroKnowledge" minOccurs="0"/>
<xs:element ref="SharedSecretChallengeResponse" minOccurs="0"/>
<xs:element ref="SharedSecretDynamicPlaintext" minOccurs="0"/>
<xs:element ref="IPAddress" minOccurs="0"/>
<xs:element ref="AsymmetricDecryption" minOccurs="0"/>
<xs:element ref="AsymmetricKeyAgreement" minOccurs="0"/>
<xs:element ref="SubscriberLineNumber" minOccurs="0"/>
<xs:element ref="UserSuffix" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:group>

The force_authn=False can be found here: /venv/lib/python3.11/site-packages/onelogin/saml2/auth.py:

  def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None):
    """
    Initiates the SSO process.

    :param return_to: Optional argument. The target URL the user should be redirected to after login.
    :type return_to: string

    :param force_authn: Optional argument. When true the AuthNRequest will set the ForceAuthn='true'.
    :type force_authn: bool

    :param is_passive: Optional argument. When true the AuthNRequest will set the Ispassive='true'.
    :type is_passive: bool

    :param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element.
    :type set_nameid_policy: bool

    :param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated
    :type name_id_value_req: string

    :returns: Redirection URL
    :rtype: string
    """
    authn_request = self.authn_request_class(self._settings, force_authn, is_passive, set_nameid_policy, name_id_value_req)
    self._last_request = authn_request.get_xml()
    self._last_request_id = authn_request.get_id()

    saml_request = authn_request.get_request()
    parameters = {'SAMLRequest': saml_request}

    if return_to is not None:
        parameters['RelayState'] = return_to
    else:
        parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self._request_data)

    security = self._settings.get_security_data()
    if security.get('authnRequestsSigned', False):
        self.add_request_signature(parameters, security['signatureAlgorithm'])
    return self.redirect_to(self.get_sso_url(), parameters)