Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/msal/wstrust_request.py: 36%
28 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:20 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:20 +0000
1#------------------------------------------------------------------------------
2#
3# Copyright (c) Microsoft Corporation.
4# All rights reserved.
5#
6# This code is licensed under the MIT License.
7#
8# Permission is hereby granted, free of charge, to any person obtaining a copy
9# of this software and associated documentation files(the "Software"), to deal
10# in the Software without restriction, including without limitation the rights
11# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12# copies of the Software, and to permit persons to whom the Software is
13# furnished to do so, subject to the following conditions :
14#
15# The above copyright notice and this permission notice shall be included in
16# all copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24# THE SOFTWARE.
25#
26#------------------------------------------------------------------------------
28import uuid
29from datetime import datetime, timedelta
30import logging
32from .mex import Mex
33from .wstrust_response import parse_response
35logger = logging.getLogger(__name__)
37def send_request(
38 username, password, cloud_audience_urn, endpoint_address, soap_action, http_client,
39 **kwargs):
40 if not endpoint_address:
41 raise ValueError("WsTrust endpoint address can not be empty")
42 if soap_action is None:
43 if '/trust/2005/usernamemixed' in endpoint_address:
44 soap_action = Mex.ACTION_2005
45 elif '/trust/13/usernamemixed' in endpoint_address:
46 soap_action = Mex.ACTION_13
47 if soap_action not in (Mex.ACTION_13, Mex.ACTION_2005):
48 raise ValueError("Unsupported soap action: %s. "
49 "Contact your administrator to check your ADFS's MEX settings." % soap_action)
50 data = _build_rst(
51 username, password, cloud_audience_urn, endpoint_address, soap_action)
52 resp = http_client.post(endpoint_address, data=data, headers={
53 'Content-type':'application/soap+xml; charset=utf-8',
54 'SOAPAction': soap_action,
55 }, **kwargs)
56 if resp.status_code >= 400:
57 logger.debug("Unsuccessful WsTrust request receives: %s", resp.text)
58 # It turns out ADFS uses 5xx status code even with client-side incorrect password error
59 # resp.raise_for_status()
60 return parse_response(resp.text)
63def escape_password(password):
64 return (password.replace('&', '&').replace('"', '"')
65 .replace("'", ''') # the only one not provided by cgi.escape(s, True)
66 .replace('<', '<').replace('>', '>'))
69def wsu_time_format(datetime_obj):
70 # WsTrust (http://docs.oasis-open.org/ws-sx/ws-trust/v1.4/ws-trust.html)
71 # does not seem to define timestamp format, but we see YYYY-mm-ddTHH:MM:SSZ
72 # here (https://www.ibm.com/developerworks/websphere/library/techarticles/1003_chades/1003_chades.html)
73 # It avoids the uncertainty of the optional ".ssssss" in datetime.isoformat()
74 # https://docs.python.org/2/library/datetime.html#datetime.datetime.isoformat
75 return datetime_obj.strftime('%Y-%m-%dT%H:%M:%SZ')
78def _build_rst(username, password, cloud_audience_urn, endpoint_address, soap_action):
79 now = datetime.utcnow()
80 return """<s:Envelope xmlns:s='{s}' xmlns:wsa='{wsa}' xmlns:wsu='{wsu}'>
81 <s:Header>
82 <wsa:Action s:mustUnderstand='1'>{soap_action}</wsa:Action>
83 <wsa:MessageID>urn:uuid:{message_id}</wsa:MessageID>
84 <wsa:ReplyTo>
85 <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
86 </wsa:ReplyTo>
87 <wsa:To s:mustUnderstand='1'>{endpoint_address}</wsa:To>
89 <wsse:Security s:mustUnderstand='1'
90 xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
91 <wsu:Timestamp wsu:Id='_0'>
92 <wsu:Created>{time_now}</wsu:Created>
93 <wsu:Expires>{time_expire}</wsu:Expires>
94 </wsu:Timestamp>
95 <wsse:UsernameToken wsu:Id='ADALUsernameToken'>
96 <wsse:Username>{username}</wsse:Username>
97 <wsse:Password>{password}</wsse:Password>
98 </wsse:UsernameToken>
99 </wsse:Security>
101 </s:Header>
102 <s:Body>
103 <wst:RequestSecurityToken xmlns:wst='{wst}'>
104 <wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'>
105 <wsa:EndpointReference>
106 <wsa:Address>{applies_to}</wsa:Address>
107 </wsa:EndpointReference>
108 </wsp:AppliesTo>
109 <wst:KeyType>{key_type}</wst:KeyType>
110 <wst:RequestType>{request_type}</wst:RequestType>
111 </wst:RequestSecurityToken>
112 </s:Body>
113 </s:Envelope>""".format(
114 s=Mex.NS["s"], wsu=Mex.NS["wsu"], wsa=Mex.NS["wsa10"],
115 soap_action=soap_action, message_id=str(uuid.uuid4()),
116 endpoint_address=endpoint_address,
117 time_now=wsu_time_format(now),
118 time_expire=wsu_time_format(now + timedelta(minutes=10)),
119 username=username, password=escape_password(password),
120 wst=Mex.NS["wst"] if soap_action == Mex.ACTION_13 else Mex.NS["wst2005"],
121 applies_to=cloud_audience_urn,
122 key_type='http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer'
123 if soap_action == Mex.ACTION_13 else
124 'http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey',
125 request_type='http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue'
126 if soap_action == Mex.ACTION_13 else
127 'http://schemas.xmlsoap.org/ws/2005/02/trust/Issue',
128 )