Ejemplo n.º 1
0
def test_get_reverse_no_reverse_match():
    """Test get_reverse with nonexistent view."""
    with pytest.raises(SAMLAuthError) as exc_info:
        get_reverse("nonexistent_view")

    assert str(
        exc_info.value) == "We got a URL reverse issue: ['nonexistent_view']"
    assert issubclass(exc_info.value.extra["exc_type"], NoReverseMatch)
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
def get_default_next_url() -> Optional[str]:
    """Get default next url for redirection, which is either the DEFAULT_NEXT_URL from settings or
    admin index.

    Returns:
        Optional[str]: Returns default next url for redirection or admin index
    """
    default_next_url = settings.SAML2_AUTH.get("DEFAULT_NEXT_URL")
    if default_next_url:
        return default_next_url

    # Lazily evaluate this in case we don't have admin loaded.
    return get_reverse("admin:index")
Ejemplo n.º 4
0
def parse_settings(domain):
    acs_url = domain + get_reverse(['acs', 'django_saml2_auth:acs'])

    saml_settings = {
        'service': {
            'sp': {
                'endpoints': {
                    'assertion_consumer_service':
                    [(acs_url, BINDING_HTTP_REDIRECT),
                     (acs_url, BINDING_HTTP_POST)],
                },
                'allow_unsolicited': True,
                'authn_requests_signed': False,
                'logout_requests_signed': True,
                'want_assertions_signed': True,
                'want_response_signed': False,
            },
        },
    }

    if 'METADATA' in settings.SAML2_AUTH:
        saml_settings['metadata'] = settings.SAML2_AUTH['METADATA']
    elif 'METADATA_AUTO_CONF_URL' in settings.SAML2_AUTH[
            'METADATA_AUTO_CONF_URL']:
        saml_settings['metadata'] = {
            'remote': [{
                'url': settings.SAML2_AUTH['METADATA_AUTO_CONF_URL']
            }]
        }
    else:
        raise Exception(
            'Invalid configuration, either "METADATA" or "METADATA_AUTO_CONF_URL" is required'
        )

    if 'ENTITY_ID' in settings.SAML2_AUTH:
        saml_settings['entityid'] = settings.SAML2_AUTH['ENTITY_ID']

    if 'NAME_ID_FORMAT' in settings.SAML2_AUTH:
        saml_settings['service']['sp']['name_id_format'] = settings.SAML2_AUTH[
            'NAME_ID_FORMAT']

    return saml_settings
Ejemplo n.º 5
0
def sp_initiated_login(request: HttpRequest) -> HttpResponseRedirect:
    # User must be created first by the IdP-initiated SSO (acs)
    if request.method == "GET":
        if request.GET.get("token"):
            user_id = decode_jwt_token(request.GET.get("token"))
            saml_client = get_saml_client(get_assertion_url(request), acs,
                                          user_id)
            jwt_token = create_jwt_token(user_id)
            _, info = saml_client.prepare_for_authenticate(
                sign=False, relay_state=jwt_token)
            redirect_url = dict(info["headers"]).get("Location", "")
            if not redirect_url:
                return HttpResponseRedirect(
                    get_reverse([denied, "denied",
                                 "django_saml2_auth:denied"]))
            return HttpResponseRedirect(redirect_url)
    else:
        raise SAMLAuthError("Request method is not supported.",
                            extra={
                                "exc_type": Exception,
                                "error_code": INVALID_REQUEST_METHOD,
                                "reason": "Request method is not supported.",
                                "status_code": 404
                            })
Ejemplo n.º 6
0
def test_get_reverse_success():
    """Test get_reverse with existing view."""
    result = get_reverse("acs")
    assert result == "/acs/"
Ejemplo n.º 7
0
def get_saml_client(domain: str,
                    acs: Callable[..., HttpResponse],
                    user_id: str = None) -> Optional[Saml2Client]:
    """Create a new Saml2Config object with the given config and return an initialized Saml2Client
    using the config object. The settings are read from django settings key: SAML2_AUTH.

    Args:
        domain (str): Domain name to get SAML config for
        acs (Callable[..., HttpResponse]): The acs endpoint

    Raises:
        SAMLAuthError: Re-raise any exception raised by Saml2Config or Saml2Client

    Returns:
        Optional[Saml2Client]: A Saml2Client or None
    """
    acs_url = domain + get_reverse([acs, "acs", "django_saml2_auth:acs"])
    metadata = get_metadata(user_id)
    if (("local" in metadata and not metadata["local"])
            or ("remote" in metadata and not metadata["remote"])):
        raise SAMLAuthError("Metadata URL/file is missing.",
                            extra={
                                "exc_type": NoReverseMatch,
                                "error_code": NO_METADATA_URL_OR_FILE,
                                "reason":
                                "There was an error processing your request.",
                                "status_code": 500
                            })

    saml_settings = {
        "metadata": metadata,
        "allow_unknown_attributes": True,
        "debug": settings.SAML2_AUTH.get("DEBUG", False),
        "service": {
            "sp": {
                "endpoints": {
                    "assertion_consumer_service":
                    [(acs_url, BINDING_HTTP_REDIRECT),
                     (acs_url, BINDING_HTTP_POST)],
                },
                "allow_unsolicited":
                True,
                "authn_requests_signed":
                True,
                "logout_requests_signed":
                True,
                "want_assertions_signed":
                dictor(settings,
                       "SAML2_AUTH.WANT_ASSERTIONS_SIGNED",
                       default=True),
                "want_response_signed":
                dictor(settings,
                       "SAML2_AUTH.WANT_RESPONSE_SIGNED",
                       default=True),
            },
        },
    }

    entity_id = settings.SAML2_AUTH.get("ENTITY_ID")
    if entity_id:
        saml_settings["entityid"] = entity_id

    name_id_format = settings.SAML2_AUTH.get("NAME_ID_FORMAT")
    if name_id_format:
        saml_settings["service"]["sp"]["name_id_format"] = name_id_format

    try:
        sp_config = Saml2Config()
        sp_config.load(saml_settings)
        saml_client = Saml2Client(config=sp_config)
        return saml_client
    except Exception as exc:
        raise SAMLAuthError(str(exc),
                            extra={
                                "exc": exc,
                                "exc_type": type(exc),
                                "error_code":
                                ERROR_CREATING_SAML_CONFIG_OR_CLIENT,
                                "reason":
                                "There was an error processing your request.",
                                "status_code": 500
                            })