Compare commits

...

15 Commits

Author SHA1 Message Date
Gabriel Dutra
253468d050 Incorporate SAML type with Enabled setting 2020-10-14 15:36:43 -03:00
Christopher Grant
6e78bddf4c reconcile upstream master and christophergrant/master fork 2020-09-29 15:24:39 -07:00
Christopher Grant
6339665a34 move mustache template for inline saml metadata to the global level 2020-09-29 09:58:26 -07:00
Christopher Grant
420f444614 use mustache_render instead of string formatting. put all static logic into static branch 2020-09-28 12:51:35 -07:00
Christopher Grant
e1a5418364 add logging for entityid for validation 2020-09-22 10:43:35 -07:00
Christopher Grant
f694f97165 pysaml2's entityid should point to the sp, not the idp 2020-09-21 17:24:46 -07:00
Christopher Grant
219d914a24 format SAML Javascript, revert want_signed_response to pre-PR value 2020-09-14 16:27:53 -07:00
Christopher Grant
7553690922 Use utility to find xmlsec binary for encryption, formatting saml_auth module 2020-09-14 16:24:12 -07:00
Christopher Grant
340980e153 remove print debug statement 2020-09-12 01:47:42 -07:00
Christopher Grant
15208cce66 add ability to encrypt/decrypt SAML assertions with pem and crt files. Upgraded to pysaml2 6.1.0 to mitigate signature mismatch during decryption 2020-09-12 01:46:45 -07:00
Christopher Grant
9556560cb4 Fixed config of backend static/dynamic to match UI 2020-09-11 14:11:47 -07:00
Christopher Grant
0ad99bd714 add switch for static and dynamic SAML configurations 2020-09-11 00:00:38 -07:00
Christopher Grant
8a2f90ffe8 Merge pull request #1 from christophergrant/saml_with_cert
Add changes to use inline metadata.
2020-09-10 10:26:23 -07:00
Chad Chen
b1040a5f9a Add changes to use inline metadata. 2020-09-09 16:33:35 -07:00
Christopher Grant
6ac181666b Change front-end and data model for SAML2 auth - static configuration 2020-09-08 16:55:34 -07:00
5 changed files with 114 additions and 35 deletions

View File

@@ -1,26 +1,59 @@
import React from "react";
import Form from "antd/lib/form";
import Checkbox from "antd/lib/checkbox";
import Input from "antd/lib/input";
import Radio from "antd/lib/radio";
import DynamicComponent from "@/components/DynamicComponent";
import { SettingsEditorPropTypes, SettingsEditorDefaultProps } from "../prop-types";
export default function SAMLSettings(props) {
const { values, onChange } = props;
const onChangeEnabledStatus = e => {
const updates = { auth_saml_enabled: !!e.target.value };
if (e.target.value) {
updates.auth_saml_type = e.target.value;
}
onChange(updates);
};
return (
<DynamicComponent name="OrganizationSettings.SAMLSettings" {...props}>
<h4>SAML</h4>
<Form.Item label="SAML Enabled">
<Checkbox
name="auth_saml_enabled"
checked={values.auth_saml_enabled}
onChange={e => onChange({ auth_saml_enabled: e.target.checked })}>
SAML Enabled
</Checkbox>
<Radio.Group
onChange={onChangeEnabledStatus}
value={values.auth_saml_enabled && (values.auth_saml_type || "static")}>
<Radio value={false}>Disabled</Radio>
<Radio value={"static"}>Enabled (Static)</Radio>
<Radio value={"dynamic"}>Enabled (Dynamic)</Radio>
</Radio.Group>
</Form.Item>
{values.auth_saml_enabled && (
<div>
<>
{values.auth_saml_type === "static" && (
<>
<Form.Item label="SAML Single Sign-on URL">
<Input
value={values.auth_saml_sso_url}
onChange={e => onChange({ auth_saml_sso_url: e.target.value })}
/>
</Form.Item>
<Form.Item label="SAML Entity ID">
<Input
value={values.auth_saml_entity_id}
onChange={e => onChange({ auth_saml_entity_id: e.target.value })}
/>
</Form.Item>
<Form.Item label="SAML x509 cert">
<Input
value={values.auth_saml_x509_cert}
onChange={e => onChange({ auth_saml_x509_cert: e.target.value })}
/>
</Form.Item>
</>
)}
{values.auth_saml_type === "dynamic" && (
<>
<Form.Item label="SAML Metadata URL">
<Input
value={values.auth_saml_metadata_url}
@@ -39,7 +72,9 @@ export default function SAMLSettings(props) {
onChange={e => onChange({ auth_saml_nameid_format: e.target.value })}
/>
</Form.Item>
</div>
</>
)}
</>
)}
</DynamicComponent>
);

View File

@@ -4,13 +4,17 @@ from redash import settings
from redash.authentication import create_and_login_user, logout_and_redirect_to_index
from redash.authentication.org_resolving import current_org
from redash.handlers.base import org_scoped_rule
from redash.utils import mustache_render
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT, entity
from saml2.client import Saml2Client
from saml2.config import Config as Saml2Config
from saml2.saml import NAMEID_FORMAT_TRANSIENT
from saml2.sigver import get_xmlsec_binary
logger = logging.getLogger("saml_auth")
blueprint = Blueprint("saml_auth", __name__)
inline_metadata_template = """<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor entityID="{{entity_id}}" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"><md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>{{x509_cert}}</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="{{sso_url}}"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="{{sso_url}}"/></md:IDPSSODescriptor></md:EntityDescriptor>"""
def get_saml_client(org):
@@ -19,12 +23,20 @@ def get_saml_client(org):
The configuration is a hash for use by saml2.config.Config
"""
metadata_url = org.get_setting("auth_saml_metadata_url")
saml_type = org.get_setting("auth_saml_type")
entity_id = org.get_setting("auth_saml_entity_id")
sso_url = org.get_setting("auth_saml_sso_url")
x509_cert = org.get_setting("auth_saml_x509_cert")
metadata_url = org.get_setting("auth_saml_metadata_url")
if settings.SAML_SCHEME_OVERRIDE:
acs_url = url_for("saml_auth.idp_initiated", org_slug=org.slug, _external=True,
_scheme=settings.SAML_SCHEME_OVERRIDE)
acs_url = url_for(
"saml_auth.idp_initiated",
org_slug=org.slug,
_external=True,
_scheme=settings.SAML_SCHEME_OVERRIDE,
)
else:
acs_url = url_for("saml_auth.idp_initiated", org_slug=org.slug, _external=True)
@@ -51,8 +63,30 @@ def get_saml_client(org):
},
}
if entity_id is not None and entity_id != "":
saml_settings["entityid"] = entity_id
if settings.SAML_ENCRYPTION_ENABLED:
encryption_dict = {
"xmlsec_binary": get_xmlsec_binary(),
"encryption_keypairs": [
{
"key_file": settings.SAML_ENCRYPTION_PEM_PATH,
"cert_file": settings.SAML_ENCRYPTION_CERT_PATH,
}
],
}
saml_settings.update(encryption_dict)
if saml_type is not None and saml_type == "static":
metadata_inline = mustache_render(
inline_metadata_template,
entity_id=entity_id,
x509_cert=x509_cert,
sso_url=sso_url,
)
saml_settings["metadata"] = {"inline": [metadata_inline]}
if acs_url is not None and acs_url != "":
saml_settings["entityid"] = acs_url
sp_config = Saml2Config()
sp_config.load(saml_settings)

View File

@@ -167,6 +167,10 @@ GOOGLE_OAUTH_ENABLED = bool(GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET)
# This setting will force the URL scheme.
SAML_SCHEME_OVERRIDE = os.environ.get("REDASH_SAML_SCHEME_OVERRIDE", "")
SAML_ENCRYPTION_PEM_PATH = os.environ.get("REDASH_SAML_ENCRYPTION_PEM_PATH", "")
SAML_ENCRYPTION_CERT_PATH = os.environ.get("REDASH_SAML_ENCRYPTION_CERT_PATH", "")
SAML_ENCRYPTION_ENABLED = SAML_ENCRYPTION_PEM_PATH != "" and SAML_ENCRYPTION_CERT_PATH != ""
# Enables the use of an externally-provided and trusted remote user via an HTTP
# header. The "user" must be an email address.
#

View File

@@ -14,10 +14,13 @@ PASSWORD_LOGIN_ENABLED = parse_boolean(
os.environ.get("REDASH_PASSWORD_LOGIN_ENABLED", "true")
)
SAML_LOGIN_TYPE = os.environ.get("REDASH_SAML_AUTH_TYPE", "")
SAML_METADATA_URL = os.environ.get("REDASH_SAML_METADATA_URL", "")
SAML_ENTITY_ID = os.environ.get("REDASH_SAML_ENTITY_ID", "")
SAML_NAMEID_FORMAT = os.environ.get("REDASH_SAML_NAMEID_FORMAT", "")
SAML_LOGIN_ENABLED = SAML_METADATA_URL != ""
SAML_SSO_URL = os.environ.get("REDASH_SAML_SSO_URL", "")
SAML_X509_CERT = os.environ.get("REDASH_SAML_X509_CERT", "")
SAML_LOGIN_ENABLED = SAML_SSO_URL != "" and SAML_METADATA_URL != ""
DATE_FORMAT = os.environ.get("REDASH_DATE_FORMAT", "DD/MM/YY")
TIME_FORMAT = os.environ.get("REDASH_TIME_FORMAT", "HH:mm")
@@ -52,9 +55,12 @@ settings = {
"beacon_consent": None,
"auth_password_login_enabled": PASSWORD_LOGIN_ENABLED,
"auth_saml_enabled": SAML_LOGIN_ENABLED,
"auth_saml_type": SAML_LOGIN_TYPE,
"auth_saml_entity_id": SAML_ENTITY_ID,
"auth_saml_metadata_url": SAML_METADATA_URL,
"auth_saml_nameid_format": SAML_NAMEID_FORMAT,
"auth_saml_sso_url": SAML_SSO_URL,
"auth_saml_x509_cert": SAML_X509_CERT,
"date_format": DATE_FORMAT,
"time_format": TIME_FORMAT,
"integer_format": INTEGER_FORMAT,

View File

@@ -39,7 +39,7 @@ rq==1.1.0
rq-scheduler==0.9.1
jsonschema==3.1.1
RestrictedPython==5.0
pysaml2==4.8.0
pysaml2==6.1.0
pycrypto==2.6.1
funcy==1.13
sentry-sdk>=0.14.3,<0.15.0