#!/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. """ # Copyright and license info is available in the LICENSE file. import argparse import base64 import json import os import six import sys import time 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: retries = 0 while True: try: settings = OneLogin_Saml2_IdPMetadataParser.parse_remote(idp_url) break except: time.sleep(1) retries += 1 if retries > 3: raise 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()