Example #1
0
def signin(request: HttpRequest):
    next_url = request.GET.get("next") or get_default_next_url()

    try:
        if "next=" in unquote(next_url):
            parsed_next_url = urlparse.parse_qs(
                urlparse.urlparse(unquote(next_url)).query)
            next_url = dictor(parsed_next_url, "next.0")
    except:
        next_url = request.GET.get("next") or get_default_next_url()

    # Only permit signin requests where the next_url is a safe URL
    allowed_hosts = set(settings.SAML2_AUTH.get("ALLOWED_REDIRECT_HOSTS", []))
    if parse_version(get_version()) >= parse_version("2.0"):
        url_ok = is_safe_url(next_url, allowed_hosts)
    else:
        url_ok = is_safe_url(next_url)

    if not url_ok:
        return HttpResponseRedirect(
            get_reverse([denied, "denied", "django_saml2_auth:denied"]))

    request.session["login_next_url"] = next_url

    saml_client = get_saml_client(get_assertion_url(request), acs)
    _, info = saml_client.prepare_for_authenticate(relay_state=next_url)

    redirect_url = None

    if "Location" in info["headers"]:
        redirect_url = info["headers"]["Location"]

    return HttpResponseRedirect(redirect_url)
Example #2
0
def test_get_default_next_url_no_default_next_url(settings: SettingsWrapper):
    """Test get_default_next_url function with no default next url for redirection to see if it
    returns the admin:index route.

    Args:
        settings (SettingsWrapper): Fixture for django settings
    """
    settings.SAML2_AUTH["DEFAULT_NEXT_URL"] = None
    with pytest.raises(SAMLAuthError) as exc_info:
        get_default_next_url()

    # This doesn't happen on a real instance, unless you don't have "admin:index" route
    assert str(exc_info.value) == "We got a URL reverse issue: ['admin:index']"
    assert issubclass(exc_info.value.extra["exc_type"], NoReverseMatch)
Example #3
0
def acs(request: HttpRequest):
    """Assertion Consumer Service is SAML terminology for the location at a ServiceProvider that
    accepts <samlp:Response> messages (or SAML artifacts) for the purpose of establishing a session
    based on an assertion. Assertion is a signed authentication request from identity provider (IdP)
    to acs endpoint.

    Args:
        request (HttpRequest): Incoming request from identity provider (IdP) for authentication

    Exceptions:
        SAMLAuthError: The target user is inactive.

    Returns:
        HttpResponseRedirect: Redirect to various endpoints: denied, welcome or next_url (e.g.
            the front-end app)

    Notes:
        https://wiki.shibboleth.net/confluence/display/CONCEPT/AssertionConsumerService
    """
    authn_response = decode_saml_response(request, acs)
    user = extract_user_identity(authn_response.get_identity())

    next_url = request.session.get("login_next_url") or get_default_next_url()

    # If RelayState params is passed, it is a JWT token that identifies the user trying to login
    # via sp_initiated_login endpoint
    relay_state = request.POST.get("RelayState")
    if relay_state:
        redirected_user_id = decode_jwt_token(relay_state)

        # This prevents users from entering an email on the SP, but use a different email on IdP
        if get_user_id(user) != redirected_user_id:
            raise SAMLAuthError("The user identifier doesn't match.",
                                extra={
                                    "exc_type": ValueError,
                                    "error_code": USER_MISMATCH,
                                    "reason": "User identifier mismatch.",
                                    "status_code": 403
                                })

    is_new_user, target_user = get_or_create_user(user)

    before_login_trigger = dictor(settings.SAML2_AUTH, "TRIGGER.BEFORE_LOGIN")
    if before_login_trigger:
        run_hook(before_login_trigger, user)

    request.session.flush()

    use_jwt = settings.SAML2_AUTH.get("USE_JWT", False)
    if use_jwt and target_user.is_active:
        # Create a new JWT token for IdP-initiated login (acs)
        jwt_token = create_jwt_token(target_user.email)
        # Use JWT auth to send token to frontend
        query = f"?token={jwt_token}"

        frontend_url = settings.SAML2_AUTH.get("FRONTEND_URL", next_url)

        return HttpResponseRedirect(frontend_url + query)

    if target_user.is_active:
        model_backend = "django.contrib.auth.backends.ModelBackend"
        login(request, target_user, model_backend)

        after_login_trigger = dictor(settings.SAML2_AUTH,
                                     "TRIGGER.AFTER_LOGIN")
        if after_login_trigger:
            run_hook(after_login_trigger, request.session, user)
    else:
        raise SAMLAuthError("The target user is inactive.",
                            extra={
                                "exc_type": Exception,
                                "error_code": INACTIVE_USER,
                                "reason": "User is inactive.",
                                "status_code": 500
                            })

    if is_new_user:
        try:
            return render(request, "django_saml2_auth/welcome.html",
                          {"user": request.user})
        except TemplateDoesNotExist:
            return HttpResponseRedirect(next_url)
    else:
        return HttpResponseRedirect(next_url)
Example #4
0
def welcome(request: HttpRequest):
    try:
        return render(request, "django_saml2_auth/welcome.html",
                      {"user": request.user})
    except TemplateDoesNotExist:
        return HttpResponseRedirect(get_default_next_url())
Example #5
0
def test_get_default_next_url_success():
    """Test get_default_next_url to verify if it returns the correct default next URL."""
    default_next_url = get_default_next_url()
    assert default_next_url == "http://app.example.com/account/login"