6 Commits

Author SHA1 Message Date
Ray Lyon
283dc417f3 Merge pull request #1 from skoobasteeve/python-package
Convert to Python package
2023-08-05 11:16:13 -04:00
Ray Lyon
fc7b910a9d update repo link 2023-08-05 15:14:59 +00:00
Ray Lyon
d5dd13bd09 rev 2023-08-05 15:04:41 +00:00
Ray Lyon
62ffe0d491 update readme 2023-08-05 15:03:00 +00:00
Ray Lyon
75b0fedbd5 linting 2023-08-05 14:43:00 +00:00
Ray Lyon
ad9a5b7e0a include template in package 2023-08-05 14:27:34 +00:00
4 changed files with 83 additions and 54 deletions

View File

@@ -1,49 +1,62 @@
# SuccessFactors + Python # SuccessFactors Auth
Authenticate with and use the SAP SuccessFactors API with Python. Authenticate with the SAP SuccessFactors API with OAuth2 and Python.
## Authentcation ## How to use
### How to use
1. Create an OAuth application in SuccessFactors. 1. Create an OAuth application in SuccessFactors.
2. Download the private key and copy the Client ID. 2. Download the private key and copy the Client ID.
3. Clone this repo and copy `sf_auth.py` and `sf_saml_template.xml` into your Python project directory. 3. Install the Python module:
4. Import `sf_auth` into your project. ``` shell
5. Install all the requirements listed in `requirements.txt` in this repo. pip install successfactors_auth
6. Call the `sf_auth.auth()` function in your Python project. You'll need to pass the following parameters: ```
4. Import `successfactors_auth` into your Python >=3.9 project.
5. Call the `successfactors_auth.auth()` function in your Python project. You'll need to pass the following parameters:
- `sf_url`: Base API url of your SuccessFactors instance, e.g. "https://api55.sapsf.eu". - `sf_url`: Base API url of your SuccessFactors instance, e.g. "https://api55.sapsf.eu".
- `sf_company_id`: SuccessFactors company ID. - `sf_company_id`: SuccessFactors company ID.
- `sf_oauth_client_id`: The Client ID for the OAuth application you created earlier. - `sf_oauth_client_id`: The Client ID for the OAuth application you created earlier.
- `sf_admin_user`: An admin user in SuccessFactors that has access to the OAuth application. - `sf_admin_user`: An admin user in SuccessFactors that has access to the OAuth application.
- `sf_saml_private_key`: Path to the private key file you downloaded when you created the OAuth application. - `sf_saml_private_key`: Path to the private key file you downloaded when you created the OAuth application.
- `template_file`: Path to the template file from this repo.
Example: ### Example
``` python
#!/usr/bin/env python
import requests ``` python
import sf_auth #!/usr/bin/env python
sf_url = 'https://your.base.url.com' import requests
sf_company_id = 'your-company-id' import successfactors_auth
sf_oauth_client_id = 'OAUTH-CLIENT-ID'
sf_admin_user = 'your_admin_user'
sf_saml_private_key = 'your_app_private_key.pem'
template_file = 'sf_saml_template.xml'
token = sf_auth.auth(sf_url, sf_company_id, sf_oauth_client_id, sf_admin_user, sf_saml_private_key, sf_saml_template) sf_url = 'https://your.base.url.com'
sf_company_id = 'your-company-id'
sf_oauth_client_id = 'your_app_client_id'
sf_admin_user = 'your_admin_user'
sf_saml_private_key = 'your_app_private_key.pem'
headers = { token = successfactors_auth.auth(
"Accept: application/json", sf_url,
f"Authorization: {token}" sf_company_id,
} sf_oauth_client_id,
sf_admin_user,
sf_saml_private_key,
sf_saml_template
)
request = requests.get(f"{sf_url}/User", headers=headers) headers = {
"Accept: application/json",
f"Authorization: {token}"
}
print(request.json()) request = requests.get(f"{sf_url}/User", headers=headers)
``` user = request.json()
### Using as an AWS Lambda function w/ API Gateway print(user)
```
Coming soon... ## Background
I wrote this module because I was forced to deal with the *horrific* SAP SuccessFactors API at my job, and I wanted to make sure other devs/sysadmins wouldn't have to feel the pain that I felt.
Once you get authenticated, getting the information you want is a whole new level of suffering. I hope to publish some more examples in the form of a blog post or docs in this repo.
## Contributing
All contributions welcome! Feel free to file an [issue](https://github.com/skoobasteeve/successfactors_auth/issues) or open a [pull request](https://github.com/skoobasteeve/successfactors_auth/pulls).

View File

@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
[project] [project]
name = "successfactors_auth" name = "successfactors_auth"
version = "0.0.2" version = "0.0.5"
authors = [ authors = [
{ name="Ray Lyon", email="ray@raylyon.net" }, { name="Ray Lyon", email="ray@raylyon.net" },
] ]
description = "Authenticate to the SuccessFactors API." description = "Authenticate to the SuccessFactors API."
readme = "README.md" readme = "README.md"
requires-python = ">=3.8" requires-python = ">=3.9"
classifiers = [ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: Apache Software License",
@@ -18,5 +18,5 @@ classifiers = [
] ]
[project.urls] [project.urls]
"Homepage" = "https://github.com/skoobasteeve/successfactors-python" "Homepage" = "https://github.com/skoobasteeve/successfactors_auth"
"Bug Tracker" = "https://github.com/skoobasteeve/successfactors-python/issues" "Bug Tracker" = "https://github.com/skoobasteeve/successfactors_auth/issues"

View File

@@ -10,18 +10,20 @@ Bearer token.
Derived from: https://github.com/mtrdesign/python-saml-example Derived from: https://github.com/mtrdesign/python-saml-example
This script requires the following additional files: This module requires the following additional files:
-Private key file for a previously created SuccessFactors OAuth2 application -Private key file for a previously created SuccessFactors OAuth2 application
Required packages: Required packages:
pip install requests lxml xmlsec requests
lxml
xmlsec
Example: Example:
#!/usr/bin/env python3 #!/usr/bin/env python3
import sf_auth import successfactors_auth
token = sf_auth.auth( token = successfactors_auth.auth(
SF_URL, SF_URL,
SF_COMPANY_ID, SF_COMPANY_ID,
SF_OAUTH_CLIENT_ID, SF_OAUTH_CLIENT_ID,
@@ -31,17 +33,21 @@ token = sf_auth.auth(
''' '''
import base64 import base64
from datetime import datetime, timedelta
from importlib import resources as impresources
import requests import requests
import xmlsec import xmlsec
import importlib.resources
from lxml import etree from lxml import etree
from datetime import datetime, timedelta
from . import templates
# Send POST request to SuccessFactors containing the generated def get_access_token(sf_url: str, company_id: str, client_id: str,
# SAML assertion and other details, then receive a token in response assertion: str) -> str:
def get_access_token(sf_url, company_id, client_id, assertion): """
Send POST request to SuccessFactors containing the generated
SAML assertion and other details, then receive a token in response
"""
# Request body # Request body
token_request = dict( token_request = dict(
client_id=client_id, client_id=client_id,
@@ -49,13 +55,17 @@ def get_access_token(sf_url, company_id, client_id, assertion):
grant_type='urn:ietf:params:oauth:grant-type:saml2-bearer', grant_type='urn:ietf:params:oauth:grant-type:saml2-bearer',
assertion=assertion assertion=assertion
) )
response = requests.post(f"{sf_url}/oauth/token", data=token_request) response = requests.post(f"{sf_url}/oauth/token", data=token_request,
timeout=15)
token_data = response.json() token_data = response.json()
return token_data['access_token'] return token_data['access_token']
# Generate SAML assertion from the template XML def generate_assertion(sf_root_url: str, user_id: str, client_id: str,
def generate_assertion(sf_root_url, user_id, client_id, template_file): template_file: str) -> str:
"""
Generate SAML assertion from the template XML
"""
# Calculate valid time values for the assertion's validity # Calculate valid time values for the assertion's validity
issue_instant = datetime.utcnow() issue_instant = datetime.utcnow()
auth_instant = issue_instant auth_instant = issue_instant
@@ -77,14 +87,16 @@ def generate_assertion(sf_root_url, user_id, client_id, template_file):
session_id='mock_session_index', session_id='mock_session_index',
) )
# Open the template file # Open the template file
saml_template = open(template_file).read() saml_template = open(template_file, encoding="utf-8").read()
# Fill the values into the template and return in # Fill the values into the template and return in
return saml_template.format(**context) return saml_template.format(**context)
# Sign the SAML assertion using a private key file def sign_assertion(xml_string: str, private_key: str) -> str:
def sign_assertion(xml_string, private_key): """
Sign the SAML assertion using a private key file
"""
# Import key file # Import key file
key = xmlsec.Key.from_file(private_key, xmlsec.KeyFormat.PEM) key = xmlsec.Key.from_file(private_key, xmlsec.KeyFormat.PEM)
@@ -101,10 +113,14 @@ def sign_assertion(xml_string, private_key):
return etree.tostring(root) return etree.tostring(root)
def auth(sf_url, sf_company_id, sf_oauth_client_id, def auth(sf_url: str, sf_company_id: str, sf_oauth_client_id: str,
sf_admin_user, sf_saml_private_key): sf_admin_user: str, sf_saml_private_key: str) -> str:
"""
Request an API access token by generating a signed SAML assertion
and using it to authenticate with SuccessFactors.
"""
template_file = "sf_saml_template.xml" template_file = impresources.files(templates) / 'sf_saml_template.xml'
# Generate SAML assertion XML from template file # Generate SAML assertion XML from template file
unsigned_assertion = generate_assertion(sf_url, unsigned_assertion = generate_assertion(sf_url,