saml_pre_sso.py #1

  • //
  • guest/
  • perforce_software/
  • helix-saml/
  • main/
  • saml_pre_sso.py
  • View
  • Commits
  • Open Download .zip Download (3 KB)
#!/usr/bin/env python
"""Generate a SAML login request URL.

This script is used to generate a login request URL for SAML 2.0. This URL
is printed to standard output, which is then passed to the client side
application specified in the P4LOGINSSO environment setting. The trigger
is expected to be run by the Perforce server as an auth-pre-sso trigger.
The one command line argument is the Perforce user name or email address.

"""

import argparse
import base64
import json
import os
import six
import sys

from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser
from onelogin.saml2.settings import OneLogin_Saml2_Settings


def encode_nameid(name_id):
    """Produce a base64 encoded version of name_id."""
    if six.PY3:
        return base64.urlsafe_b64encode(name_id.encode('utf8')).decode('utf8')
    else:
        return base64.urlsafe_b64encode(name_id)


def save_request_id(base_path, name_id, request_id):
    """Save the request identifier so it can be verified later."""
    requests_dir = os.path.expanduser(os.path.join(base_path, 'requests'))
    if not os.path.exists(requests_dir):
        os.makedirs(requests_dir)
    key_path = os.path.join(requests_dir, encode_nameid(name_id))
    with open(key_path, 'w') as fobj:
        fobj.write(request_id)


def load_settings(idp_url):
    """Build out the settings for SAML library."""
    # Elaborate process to construct settings that have everything python3-saml
    # is expecting, and has the correct IdP certificate.
    if idp_url:
        settings = OneLogin_Saml2_IdPMetadataParser.parse_remote(idp_url)
    else:
        settings = dict()
    base_path = os.path.dirname(sys.argv[0])
    with open(os.path.join(base_path, 'settings.json')) as fobj:
        settings.update(json.load(fobj))
    with open(os.path.join(base_path, 'advanced_settings.json')) as fobj:
        settings.update(json.load(fobj))
    # Set the path for finding the 'certs' directory, otherwise it defaults
    # to the installation location of the pysaml module.
    return OneLogin_Saml2_Settings(settings, custom_base_path=base_path)


def main():
    """Generate a SAML login URL and print to stdout."""
    parser = argparse.ArgumentParser(description='Generate a SAML login request URL.')
    parser.add_argument('--idpUrl', help='URL of IdP metadata')
    parser.add_argument('--base', default='~/saml', help='base path for trigger to store files')
    parser.add_argument('identifier', help='Perforce user User or Email field value')
    args = parser.parse_args()
    settings = load_settings(args.idpUrl)
    auth = OneLogin_Saml2_Auth({}, settings)
    print(auth.login(return_to='ignored'))
    save_request_id(args.base, args.identifier, auth.get_last_request_id())


if __name__ == "__main__":
    main()
# Change User Description Committed
#4 25196 Nathan Fiedler Retry and then raise the error if unsuccesful.
#3 25195 Nathan Fiedler Retry IdP metadata fetch a couple of times.

To address empheral errors when retrieving the IdP metadata from the
remote side, retry the request 3 times with a 1 second pause
inbetween.
#2 24933 Nathan Fiedler Add copyright and license details for SAML triggers.
#1 24785 Nathan Fiedler Add SAML triggers.