|
8 | 8 | import re
|
9 | 9 | from base64 import urlsafe_b64decode
|
10 | 10 | from base64 import urlsafe_b64encode
|
11 |
| -from base64 import b64encode |
12 | 11 | from urllib.parse import quote
|
13 | 12 | from urllib.parse import quote_plus
|
14 | 13 | from urllib.parse import unquote
|
|
17 | 16 | from http.cookies import SimpleCookie
|
18 | 17 |
|
19 | 18 | from saml2 import SAMLError, xmldsig
|
20 |
| -from saml2 import BINDING_HTTP_POST |
21 |
| -from saml2.client_base import Base |
22 |
| -from saml2.config import IdPConfig, SPConfig |
| 19 | +from saml2.config import IdPConfig |
23 | 20 | from saml2.extension.ui import NAMESPACE as UI_NAMESPACE
|
24 | 21 | from saml2.metadata import create_metadata_string
|
25 | 22 | from saml2.saml import NameID
|
|
31 | 28 | from saml2.server import Server
|
32 | 29 |
|
33 | 30 | from satosa.base import SAMLBaseModule
|
34 |
| -from satosa.backends.saml2 import SAMLBackend |
35 | 31 | from satosa.context import Context
|
36 | 32 | from .base import FrontendModule
|
37 | 33 | from ..logging_util import satosa_logging
|
@@ -1018,222 +1014,3 @@ def _register_endpoints(self, backend_names):
|
1018 | 1014 | logger.debug("Adding mapping {}".format(mapping))
|
1019 | 1015 |
|
1020 | 1016 | return url_to_callable_mappings
|
1021 |
| - |
1022 |
| - |
1023 |
| -class SAMLUnsolicitedFrontend(SAMLFrontend): |
1024 |
| - """ |
1025 |
| - Frontend module that provides all of the functionality of the base class |
1026 |
| - SAMLFrontend but also provides a proprietary endpoint for initiating |
1027 |
| - unsolicted SAML flows. The unsolicited SAML flows are not part of any |
1028 |
| - SAML standard. |
1029 |
| - """ |
1030 |
| - |
1031 |
| - KEY_ENDPOINT = "endpoint" |
1032 |
| - KEY_DISCO_URL_WHITE = "discovery_service_url_whitelist" |
1033 |
| - KEY_DISCO_POLICY_WHITE = "discovery_service_policy_whitelist" |
1034 |
| - KEY_QUERY_IDP = "authId" |
1035 |
| - KEY_QUERY_SP = "providerId" |
1036 |
| - KEY_QUERY_ACS = "shire" |
1037 |
| - KEY_QUERY_RELAY = "target" |
1038 |
| - KEY_QUERY_DISCO_URL = "discoveryURL" |
1039 |
| - KEY_QUERY_DISCO_POLICY = "discoveryPolicy" |
1040 |
| - KEY_SAML_DISCOVERY_SERVICE_URL = SAMLBackend.KEY_SAML_DISCOVERY_SERVICE_URL |
1041 |
| - KEY_SAML_DISCOVERY_SERVICE_POLICY = SAMLBackend.KEY_SAML_DISCOVERY_SERVICE_POLICY |
1042 |
| - KEY_UNSOLICITED = "unsolicited" |
1043 |
| - |
1044 |
| - def __init__( |
1045 |
| - self, auth_req_callback_func, internal_attributes, config, base_url, name |
1046 |
| - ): |
1047 |
| - super().__init__( |
1048 |
| - auth_req_callback_func, internal_attributes, config, base_url, name |
1049 |
| - ) |
1050 |
| - |
1051 |
| - def register_endpoints(self, backend_names): |
1052 |
| - """ |
1053 |
| - See super class |
1054 |
| - satosa.frontends.saml2.SAMLFrontend#register_endpoints |
1055 |
| -
|
1056 |
| - :type providers: list[str] |
1057 |
| - :rtype: list[(str, ((satosa.context.Context, Any) |
1058 |
| - -> satosa.response.Response, Any))] |
1059 |
| - :param providers: A list of backend providers |
1060 |
| - :return: A list of endpoint/method pairs |
1061 |
| - """ |
1062 |
| - url_map = super().register_endpoints(backend_names) |
1063 |
| - |
1064 |
| - path = urlparse(self.config[self.KEY_UNSOLICITED].get(self.KEY_ENDPOINT)).path |
1065 |
| - |
1066 |
| - for backend in backend_names: |
1067 |
| - pat = "(^{})/{}$".format(backend, path) |
1068 |
| - url_map.append((pat, self.unsolicited_endpoint)) |
1069 |
| - |
1070 |
| - logger.debug("URL maps to be registered are {}".format(url_map)) |
1071 |
| - |
1072 |
| - return url_map |
1073 |
| - |
1074 |
| - def unsolicited_endpoint(self, context): |
1075 |
| - """ |
1076 |
| - Endpoint to process unsolicited SAML flows. The unsolicited flows |
1077 |
| - are proprietary and not defined as part of any SAML standard. |
1078 |
| -
|
1079 |
| - :type context: satosa.context.Context |
1080 |
| - :rtype: satosa.response.Response |
1081 |
| -
|
1082 |
| - :param context: The current context |
1083 |
| - :return: response |
1084 |
| - """ |
1085 |
| - request = context.request |
1086 |
| - |
1087 |
| - target_idp_entity_id = request.get(self.KEY_QUERY_IDP, None) |
1088 |
| - target_sp_entity_id = request.get(self.KEY_QUERY_SP, None) |
1089 |
| - target_sp_acs_url = request.get(self.KEY_QUERY_ACS, None) |
1090 |
| - target_sp_relay_state_url = request.get(self.KEY_QUERY_RELAY, None) |
1091 |
| - requested_disco_url = request.get(self.KEY_QUERY_DISCO_URL, None) |
1092 |
| - requested_disco_policy = request.get(self.KEY_QUERY_DISCO_POLICY, None) |
1093 |
| - |
1094 |
| - logger.debug( |
1095 |
| - "Unsolicited target authenticating IdP is {}".format(target_idp_entity_id) |
1096 |
| - ) |
1097 |
| - logger.debug("Unsolicited target SP is {}".format(target_sp_entity_id)) |
1098 |
| - logger.debug("Unsolicited ACS URL is {}".format(target_sp_acs_url)) |
1099 |
| - logger.debug("Unsolicited relay state is {}".format(target_sp_relay_state_url)) |
1100 |
| - logger.debug("Unsolicted discovery URL is {}".format(requested_disco_url)) |
1101 |
| - logger.debug("Unsolicted discovery policy is {}".format(requested_disco_policy)) |
1102 |
| - |
1103 |
| - # We only proceed with known federated SPs. |
1104 |
| - try: |
1105 |
| - target_sp_metadata = self.idp.metadata[target_sp_entity_id] |
1106 |
| - except KeyError: |
1107 |
| - msg = "Target SP with entityID {} is unknown in metadata" |
1108 |
| - msg = msg.format(target_sp_entity_id) |
1109 |
| - satosa_logging(logger, logging.ERROR, msg, context.state) |
1110 |
| - raise SATOSAError(msg) |
1111 |
| - |
1112 |
| - # The SP ACS URL if input must match one from the trusted metadata. |
1113 |
| - # We assume the SP only has one SPSSODescriptor element in metadata. |
1114 |
| - acs_ob_list = target_sp_metadata.get("spsso_descriptor", [{}])[0].get( |
1115 |
| - "assertion_consumer_service", [{}] |
1116 |
| - ) |
1117 |
| - acs_locations = [acs_ob["location"] for acs_ob in acs_ob_list] |
1118 |
| - |
1119 |
| - if target_sp_acs_url: |
1120 |
| - if target_sp_acs_url not in acs_locations: |
1121 |
| - msg = "Target ACS URL {} not allowed" |
1122 |
| - msg = msg.format(target_sp_acs_url) |
1123 |
| - satosa_logging(logger, logging.ERROR, msg, context.state) |
1124 |
| - raise SATOSAError(msg) |
1125 |
| - else: |
1126 |
| - for acs_ob in acs_ob_list: |
1127 |
| - # We assume the SP has HTTP_POST binding and we simply |
1128 |
| - # take the first one we find. |
1129 |
| - if acs_ob["binding"] == BINDING_HTTP_POST: |
1130 |
| - target_sp_acs_url = acs_ob["location"] |
1131 |
| - logger.debug( |
1132 |
| - "Unsolicited found SP ACS URL {}".format(target_sp_acs_url) |
1133 |
| - ) |
1134 |
| - break |
1135 |
| - |
1136 |
| - if not target_sp_acs_url: |
1137 |
| - msg = "No ACS for SP with entityID {}".format(target_sp_entity_id) |
1138 |
| - satosa_logging(logger, logging.ERROR, msg, context.state) |
1139 |
| - raise SATOSAError(msg) |
1140 |
| - |
1141 |
| - # If provided the exact scheme, host, and port for relay state URL |
1142 |
| - # must match that of the target SP ACS URL. |
1143 |
| - if target_sp_relay_state_url: |
1144 |
| - target = urlparse(target_sp_relay_state_url) |
1145 |
| - acs = urlparse(target_sp_acs_url) |
1146 |
| - if not ( |
1147 |
| - target.scheme == acs.scheme |
1148 |
| - and target.netloc == acs.netloc |
1149 |
| - and target.port == acs.port |
1150 |
| - ): |
1151 |
| - msg = "RelayState {} is not permitted" |
1152 |
| - msg = msg.format(target_sp_relay_state_url) |
1153 |
| - satosa_logging(logger, logging.ERROR, msg, context.state) |
1154 |
| - raise SATOSAError(msg) |
1155 |
| - |
1156 |
| - # Create a temporary SP configuration to represent the target SP. |
1157 |
| - acs = [[target_sp_acs_url, BINDING_HTTP_POST]] |
1158 |
| - sp_config_dict = { |
1159 |
| - "entityid": target_sp_entity_id, |
1160 |
| - "service": {"sp": {"endpoints": {"assertion_consumer_service": acs}}}, |
1161 |
| - } |
1162 |
| - sp_config = SPConfig().load(sp_config_dict, False) |
1163 |
| - |
1164 |
| - # Create a temporary SP object and use it to create a authn request |
1165 |
| - # with a destination of our own SingleSignOnService location with |
1166 |
| - # HTTP-POST binding. |
1167 |
| - target_sp = Base(sp_config) |
1168 |
| - |
1169 |
| - destination = None |
1170 |
| - endpoints = self.idp.config.getattr("endpoints") |
1171 |
| - sso_service_list = endpoints["single_sign_on_service"] |
1172 |
| - for location, binding in sso_service_list: |
1173 |
| - if binding == BINDING_HTTP_POST: |
1174 |
| - destination = location |
1175 |
| - break |
1176 |
| - |
1177 |
| - if not destination: |
1178 |
| - msg = ( |
1179 |
| - "Could not determine location for SingleSignOnService " |
1180 |
| - "with HTTP-POST binding" |
1181 |
| - ) |
1182 |
| - satosa_logging(logger, logging.ERROR, msg, context.state) |
1183 |
| - raise SATOSAError(msg) |
1184 |
| - |
1185 |
| - logger.debug("Unsolicited using destination {}".format(destination)) |
1186 |
| - |
1187 |
| - req_id, authn_request = target_sp.create_authn_request(destination) |
1188 |
| - |
1189 |
| - # Convert the authn request object to an encoded set of bytes. |
1190 |
| - authn_request_str = "{}".format(authn_request) |
1191 |
| - logger.debug("Unsolicted authn request is {}".format(authn_request_str)) |
1192 |
| - authn_request_bytes = authn_request_str.encode("utf-8") |
1193 |
| - authn_request_encoded = b64encode(authn_request_bytes) |
1194 |
| - |
1195 |
| - # Add the authn request to the context as if it arrived through |
1196 |
| - # an endpoint. |
1197 |
| - context.request["SAMLRequest"] = authn_request_encoded |
1198 |
| - |
1199 |
| - # Add the relay state to the context if provided. |
1200 |
| - if target_sp_relay_state_url: |
1201 |
| - context.request["RelayState"] = target_sp_relay_state_url |
1202 |
| - |
1203 |
| - # If provided and is whitelisted set the discovery service to use. |
1204 |
| - if requested_disco_url: |
1205 |
| - allowed = self.config[self.KEY_UNSOLICITED].get(self.KEY_DISCO_URL_WHITE) |
1206 |
| - if requested_disco_url not in allowed: |
1207 |
| - msg = "Discovery service URL {} not allowed" |
1208 |
| - msg = msg.format(requested_disco_url) |
1209 |
| - satosa_logging(logger, logging.ERROR, msg, context.state) |
1210 |
| - raise SATOSAError(msg) |
1211 |
| - |
1212 |
| - context.decorate(self.KEY_SAML_DISCOVERY_SERVICE_URL, requested_disco_url) |
1213 |
| - |
1214 |
| - # If provided and is whitelisted set the discovery policy to use. |
1215 |
| - if requested_disco_policy: |
1216 |
| - allowed = self.config[self.KEY_UNSOLICITED].get(self.KEY_DISCO_POLICY_WHITE) |
1217 |
| - if requested_disco_policy not in allowed: |
1218 |
| - msg = "Discovery service policy {} not allowed" |
1219 |
| - msg = msg.format(requested_disco_policy) |
1220 |
| - satosa_logging(logger, logging.ERROR, msg, context.state) |
1221 |
| - raise SATOSAError(msg) |
1222 |
| - |
1223 |
| - context.decorate( |
1224 |
| - self.KEY_SAML_DISCOVERY_SERVICE_POLICY, requested_disco_policy |
1225 |
| - ) |
1226 |
| - |
1227 |
| - # If provided and known in the SAML metadata set the entityID for |
1228 |
| - # the IdP to use for authentication. |
1229 |
| - if target_idp_entity_id: |
1230 |
| - if target_idp_entity_id in self.idp.metadata: |
1231 |
| - context.decorate(Context.KEY_TARGET_ENTITYID, target_idp_entity_id) |
1232 |
| - else: |
1233 |
| - msg = "Target IdP with entityID {} is unknown in metadata" |
1234 |
| - msg = msg.format(target_idp_entity_id) |
1235 |
| - satosa_logging(logger, logging.ERROR, msg, context.state) |
1236 |
| - raise SATOSAError(msg) |
1237 |
| - |
1238 |
| - # Handle the authn request use the base class. |
1239 |
| - return self._handle_authn_request(context, BINDING_HTTP_POST, self.idp) |
0 commit comments