class SamlTokenProvider
Provides SAML-based claims authentication for Office 365 SharePoint Online, handling security token acquisition and cookie-based authentication.
/tf/active/vicechatdev/SPFCsync/venv/lib64/python3.11/site-packages/office365/runtime/auth/providers/saml_token_provider.py
42 - 327
complex
Purpose
This class implements a SAML (Security Assertion Markup Language) token provider for authenticating with Office 365 SharePoint Online using claims-based authentication. It supports both federated (ADFS) and non-federated authentication scenarios, manages the complete authentication flow including user realm discovery, security token acquisition from STS endpoints, and conversion of tokens to authentication cookies. The provider can operate in browser mode to mimic browser-based authentication patterns.
Source Code
class SamlTokenProvider(AuthenticationProvider, office365.logger.LoggerContext):
def __init__(self, url, username, password, browser_mode):
"""
SAML Security Token Service provider (claims-based authentication)
:param str url: Site or Web absolute url
:param str username: Typically a UPN in the form of an email address
:param str password: The password
:param bool browser_mode:
"""
# Security Token Service info
self._sts_profile = STSProfile(resolve_base_url(url))
# Obtain authentication cookies, using the browser mode
self._browser_mode = browser_mode
# Last occurred error
self.error = ""
self._username = username
self._password = password
self._cached_auth_cookies = None
self.__ns_prefixes = {
"S": "{http://www.w3.org/2003/05/soap-envelope}",
"s": "{http://www.w3.org/2003/05/soap-envelope}",
"psf": "{http://schemas.microsoft.com/Passport/SoapServices/SOAPFault}",
"wst": "{http://schemas.xmlsoap.org/ws/2005/02/trust}",
"wsse": "{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}",
"saml": "{urn:oasis:names:tc:SAML:1.0:assertion}",
"u": "{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}",
"wsa": "{http://www.w3.org/2005/08/addressing}",
"wsp": "{http://schemas.xmlsoap.org/ws/2004/09/policy}",
"ps": "{http://schemas.microsoft.com/LiveID/SoapServices/v1}",
"ds": "{http://www.w3.org/2000/09/xmldsig#}",
}
for key in self.__ns_prefixes.keys():
ElementTree.register_namespace(key, self.__ns_prefixes[key][1:-1])
def authenticate_request(self, request):
"""
Authenticate request handler
"""
logger = self.logger(self.authenticate_request.__name__)
self.ensure_authentication_cookie()
logger.debug_secrets(self._cached_auth_cookies)
cookie_header_value = "; ".join(
[
"=".join([key, str(val)])
for key, val in self._cached_auth_cookies.items()
]
)
request.set_header("Cookie", cookie_header_value)
def ensure_authentication_cookie(self):
if self._cached_auth_cookies is None:
self._cached_auth_cookies = self.get_authentication_cookie()
return True
def get_authentication_cookie(self):
"""Acquire authentication cookie"""
logger = self.logger(self.ensure_authentication_cookie.__name__)
logger.debug("get_authentication_cookie called")
try:
logger.debug("Acquiring Access Token..")
user_realm = self._get_user_realm()
if user_realm.IsFederated:
token = self._acquire_service_token_from_adfs(user_realm.STSAuthUrl)
else:
token = self._acquire_service_token()
return self._get_authentication_cookie(token, user_realm.IsFederated)
except requests.exceptions.RequestException as e:
logger.error(e.response.text)
self.error = "Error: {}".format(e)
raise ValueError(e.response.text)
def _get_user_realm(self):
"""Get User Realm"""
resp = requests.post(
self._sts_profile.user_realm_service_url,
data="login={0}&xml=1".format(self._username),
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
xml = ElementTree.fromstring(resp.content)
node = xml.find("NameSpaceType")
if node is not None:
if node.text == "Federated":
info = UserRealmInfo(xml.find("STSAuthURL").text, True)
else:
info = UserRealmInfo(None, False)
return info
return None
def get_last_error(self):
return self.error
def _acquire_service_token_from_adfs(self, adfs_url):
logger = self.logger(self._acquire_service_token_from_adfs.__name__)
payload = self._prepare_request_from_template(
"FederatedSAML.xml",
{
"auth_url": adfs_url,
"message_id": str(uuid.uuid4()),
"username": xml_escape(self._username),
"password": xml_escape(self._password),
"created": self._sts_profile.created,
"expires": self._sts_profile.expires,
"issuer": self._sts_profile.tokenIssuer,
},
)
response = requests.post(
adfs_url,
data=payload,
headers={"Content-Type": "application/soap+xml; charset=utf-8"},
)
dom = minidom.parseString(response.content.decode())
assertion_node = dom.getElementsByTagNameNS(
"urn:oasis:names:tc:SAML:1.0:assertion", "Assertion"
)[0].toxml()
try:
payload = self._prepare_request_from_template(
"RST2.xml",
{
"auth_url": self._sts_profile.tenant,
"serviceTokenUrl": self._sts_profile.security_token_service_url,
"assertion_node": assertion_node,
},
)
# 3. get security token
response = requests.post(
self._sts_profile.security_token_service_url,
data=payload,
headers={"Content-Type": "application/soap+xml"},
)
token = self._process_service_token_response(response)
logger.debug_secrets("security token: %s", token)
return token
except ElementTree.ParseError as e:
self.error = (
"An error occurred while parsing the server response: {}".format(e)
)
logger.error(self.error)
return None
def _acquire_service_token(self):
"""Retrieve service token"""
logger = self.logger(self._acquire_service_token.__name__)
payload = self._prepare_request_from_template(
"SAML.xml",
{
"auth_url": self._sts_profile.authorityUrl,
"username": xml_escape(self._username),
"password": xml_escape(self._password),
"message_id": str(uuid.uuid4()),
"created": self._sts_profile.created,
"expires": self._sts_profile.expires,
"issuer": self._sts_profile.tokenIssuer,
},
)
logger.debug_secrets("options: %s", payload)
response = requests.post(
self._sts_profile.security_token_service_url,
data=payload,
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
token = self._process_service_token_response(response)
logger.debug_secrets("security token: %s", token)
return token
def _process_service_token_response(self, response):
logger = self.logger(self._process_service_token_response.__name__)
logger.debug_secrets(
"response: %s\nresponse.content: %s", response, response.content
)
try:
xml = ElementTree.fromstring(response.content)
except ElementTree.ParseError as e:
self.error = (
"An error occurred while parsing the server response: {}".format(e)
)
logger.error(self.error)
return None
# check for errors
if xml.find("{0}Body/{0}Fault".format(self.__ns_prefixes["s"])) is not None:
error = xml.find(
"{0}Body/{0}Fault/{0}Detail/{1}error/{1}internalerror/{1}text".format(
self.__ns_prefixes["s"], self.__ns_prefixes["psf"]
)
)
if error is None:
self.error = (
"An error occurred while retrieving token from XML response."
)
else:
self.error = "An error occurred while retrieving token from XML response: {0}".format(
error.text
)
logger.error(self.error)
raise ValueError(self.error)
# extract token
token = xml.find(
"{0}Body/{1}RequestSecurityTokenResponse/{1}RequestedSecurityToken/{2}BinarySecurityToken".format(
self.__ns_prefixes["s"],
self.__ns_prefixes["wst"],
self.__ns_prefixes["wsse"],
)
)
if token is None:
self.error = "Cannot get binary security token for from {0}".format(
self._sts_profile.security_token_service_url
)
logger.error(self.error)
raise ValueError(self.error)
logger.debug_secrets("token: %s", token)
return token.text
def _get_authentication_cookie(self, security_token, federated=False):
"""Retrieve auth cookie from STS
:type federated: bool
:type security_token: str
"""
logger = self.logger(self._get_authentication_cookie.__name__)
session = requests.session()
logger.debug_secrets(
"session: %s\nsession.post(%s, data=%s)",
session,
self._sts_profile.signin_page_url,
security_token,
)
if not federated or self._browser_mode:
headers = {"Content-Type": "application/x-www-form-urlencoded"}
if self._browser_mode:
headers[
"User-Agent"
] = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"
session.post(
self._sts_profile.signin_page_url, data=security_token, headers=headers
)
else:
idcrl_endpoint = "https://{}/_vti_bin/idcrl.svc/".format(
self._sts_profile.tenant
)
session.get(
idcrl_endpoint,
headers={
"User-Agent": "Office365 Python Client",
"X-IDCRL_ACCEPTED": "t",
"Authorization": "BPOSIDCRL {0}".format(security_token),
},
)
logger.debug_secrets("session.cookies: %s", session.cookies)
cookies = requests.utils.dict_from_cookiejar(session.cookies)
logger.debug_secrets("cookies: %s", cookies)
if not is_valid_auth_cookies(cookies):
self.error = (
"An error occurred while retrieving auth cookies from {0}".format(
self._sts_profile.signin_page_url
)
)
logger.error(self.error)
raise ValueError(self.error)
return cookies
@staticmethod
def _prepare_request_from_template(template_name, params):
"""Construct the request body to acquire security token from STS endpoint"""
logger = SamlTokenProvider.logger()
logger.debug_secrets("params: %s", params)
f = open(
os.path.join(os.path.dirname(__file__), "templates", template_name),
encoding="utf8",
)
try:
data = f.read()
for key in params:
data = data.replace("{" + key + "}", str(params[key]))
return data
finally:
f.close()
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
AuthenticationProvider, office365.logger.LoggerContext | - |
Parameter Details
url: The absolute URL of the SharePoint site or web to authenticate against. Used to resolve the base URL and determine the STS profile configuration.
username: The user's login name, typically a UPN (User Principal Name) in email address format (e.g., user@domain.com).
password: The user's password for authentication. This is used in SAML token requests and is XML-escaped before transmission.
browser_mode: Boolean flag indicating whether to use browser-mode authentication. When True, mimics browser behavior by setting appropriate User-Agent headers and using specific authentication endpoints.
Return Value
Instantiation returns a SamlTokenProvider object that can be used to authenticate HTTP requests. The authenticate_request method modifies requests in-place by adding Cookie headers. The get_authentication_cookie method returns a dictionary of authentication cookies (typically FedAuth and rtFa cookies). Methods like _acquire_service_token return security token strings, while _get_user_realm returns a UserRealmInfo object.
Class Interface
Methods
__init__(self, url: str, username: str, password: str, browser_mode: bool)
Purpose: Initializes the SAML token provider with authentication parameters and configures XML namespaces
Parameters:
url: Site or Web absolute URLusername: User Principal Name (UPN) typically in email formatpassword: User passwordbrowser_mode: Flag to enable browser-mode authentication
Returns: None
authenticate_request(self, request) -> None
Purpose: Authenticates an HTTP request by adding authentication cookies to the Cookie header
Parameters:
request: HTTP request object to be authenticated (must have set_header method)
Returns: None - modifies the request object in-place by setting Cookie header
ensure_authentication_cookie(self) -> bool
Purpose: Ensures authentication cookies are available, acquiring them if not already cached
Returns: Always returns True after ensuring cookies are available
get_authentication_cookie(self) -> dict
Purpose: Acquires authentication cookies by performing the complete SAML authentication flow
Returns: Dictionary of authentication cookies (e.g., {'FedAuth': '...', 'rtFa': '...'})
get_last_error(self) -> str
Purpose: Returns the last error message that occurred during authentication
Returns: String containing the last error message, or empty string if no error
_get_user_realm(self) -> UserRealmInfo
Purpose: Determines the user's authentication realm (federated or managed) by querying the user realm service
Returns: UserRealmInfo object containing realm type and ADFS URL if federated
_acquire_service_token_from_adfs(self, adfs_url: str) -> str
Purpose: Acquires a security token from an ADFS endpoint for federated authentication scenarios
Parameters:
adfs_url: The ADFS STS authentication URL obtained from user realm discovery
Returns: Security token string, or None if acquisition fails
_acquire_service_token(self) -> str
Purpose: Acquires a security token from the Office 365 STS endpoint for non-federated authentication
Returns: Security token string
_process_service_token_response(self, response) -> str
Purpose: Parses the STS response XML to extract the binary security token and handle errors
Parameters:
response: HTTP response object from the STS endpoint containing XML
Returns: Binary security token string extracted from the XML response
_get_authentication_cookie(self, security_token: str, federated: bool = False) -> dict
Purpose: Exchanges a security token for authentication cookies by posting to the sign-in page
Parameters:
security_token: The binary security token obtained from STSfederated: Boolean indicating if this is federated authentication
Returns: Dictionary of authentication cookies
_prepare_request_from_template(template_name: str, params: dict) -> str
static
Purpose: Loads an XML template file and substitutes parameters to create a SOAP request body
Parameters:
template_name: Name of the template file (e.g., 'SAML.xml', 'FederatedSAML.xml')params: Dictionary of parameter names to values for template substitution
Returns: String containing the prepared XML request body with substituted parameters
Attributes
| Name | Type | Description | Scope |
|---|---|---|---|
_sts_profile |
STSProfile | Contains Security Token Service configuration including URLs and endpoints for the target SharePoint site | instance |
_browser_mode |
bool | Flag indicating whether to use browser-mode authentication with browser-like headers | instance |
error |
str | Stores the last error message that occurred during authentication operations | instance |
_username |
str | The user's login name (UPN) used for authentication | instance |
_password |
str | The user's password used for authentication | instance |
_cached_auth_cookies |
dict or None | Cached authentication cookies to avoid repeated authentication; None until first successful authentication | instance |
__ns_prefixes |
dict | Dictionary mapping XML namespace prefixes to their full URIs for SOAP/SAML XML processing | instance |
Dependencies
requestsoffice365.loggeroffice365.runtime.auth.authentication_provideroffice365.runtime.auth.sts_profileoffice365.runtime.auth.user_realm_info
Required Imports
import os
import uuid
from xml.dom import minidom
from xml.etree import ElementTree
import requests
import requests.utils
import office365.logger
from office365.runtime.auth.authentication_provider import AuthenticationProvider
from office365.runtime.auth.sts_profile import STSProfile
from office365.runtime.auth.user_realm_info import UserRealmInfo
Usage Example
from office365.runtime.auth.saml_token_provider import SamlTokenProvider
import requests
# Initialize the SAML token provider
url = 'https://contoso.sharepoint.com/sites/mysite'
username = 'user@contoso.com'
password = 'mypassword'
browser_mode = False
provider = SamlTokenProvider(url, username, password, browser_mode)
# Get authentication cookies
try:
cookies = provider.get_authentication_cookie()
print(f"Authentication successful: {cookies}")
except ValueError as e:
print(f"Authentication failed: {e}")
print(f"Error details: {provider.get_last_error()}")
# Use with HTTP requests
request = requests.Request('GET', url)
provider.authenticate_request(request)
prepared = request.prepare()
response = requests.Session().send(prepared)
Best Practices
- Always handle ValueError exceptions when calling get_authentication_cookie() as authentication can fail for various reasons (invalid credentials, network issues, STS errors)
- Check get_last_error() after authentication failures to get detailed error messages
- The provider caches authentication cookies after first successful authentication; subsequent calls to authenticate_request use cached cookies
- Credentials (username and password) are stored in instance variables and logged with debug_secrets, ensure proper logging configuration for security
- The provider automatically handles both federated (ADFS) and non-federated authentication by detecting the user realm type
- Browser mode should be enabled when authenticating against environments that require browser-like behavior
- The class maintains state through _cached_auth_cookies; create a new instance if you need to re-authenticate with different credentials
- XML namespace prefixes are registered globally during initialization, which may affect other XML processing in the application
- Template files must be accessible at runtime; ensure they are packaged with the application
- The provider inherits from LoggerContext, use the logger() method for consistent logging throughout the authentication flow
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class NtlmProvider 64.2% similar
-
class STSProfile 60.8% similar
-
class AuthenticationProvider 60.0% similar
-
class SamlOrWsFedProvider 59.8% similar
-
function test_sharepoint_token 59.6% similar