updated with the new version of gp-saml-gui (from upstream)
This commit is contained in:
parent
0b6ace2da3
commit
5d9f17d8ee
@ -1,9 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import warnings
|
||||
try:
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('WebKit2', '4.0')
|
||||
try:
|
||||
gi.require_version('WebKit2', '4.1')
|
||||
except ValueError: # I wish this were ImportError
|
||||
gi.require_version('WebKit2', '4.0')
|
||||
warnings.warn("Using WebKit2Gtk 4.0 (obsolete); please upgrade to WebKit2Gtk 4.1")
|
||||
from gi.repository import Gtk, WebKit2, GLib
|
||||
except ImportError:
|
||||
try:
|
||||
@ -11,10 +17,11 @@ except ImportError:
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('WebKit2', '4.0')
|
||||
from pgi.repository import Gtk, WebKit2, GLib
|
||||
warnings.warn("Using PGI and WebKit2Gtk 4.0 (both obsolete); please upgrade to PyGObject and WebKit2Gtk 4.1")
|
||||
except ImportError:
|
||||
gi = None
|
||||
if gi is None:
|
||||
raise ImportError("Either gi (PyGObject) or pgi module is required.")
|
||||
raise ImportError("Either gi (PyGObject) or pgi (obsolete) module is required.")
|
||||
|
||||
import argparse
|
||||
import urllib3
|
||||
@ -24,11 +31,11 @@ import ssl
|
||||
import tempfile
|
||||
|
||||
from operator import setitem
|
||||
from os import path, dup2, execvp
|
||||
from os import path, dup2, execvp, environ
|
||||
from shlex import quote
|
||||
from sys import stderr, platform
|
||||
from binascii import a2b_base64, b2a_base64
|
||||
from urllib.parse import urlparse, urlencode
|
||||
from urllib.parse import urlparse, urlencode, urlunsplit
|
||||
from html.parser import HTMLParser
|
||||
|
||||
|
||||
@ -45,30 +52,35 @@ COOKIE_FIELDS = ('prelogin-cookie', 'portal-userauthcookie')
|
||||
|
||||
|
||||
class SAMLLoginView:
|
||||
def __init__(self, uri, html=None, verbose=False, cookies=None, verify=True, user_agent=None):
|
||||
def __init__(self, uri, html, args):
|
||||
|
||||
Gtk.init(None)
|
||||
window = Gtk.Window()
|
||||
self.window = window = Gtk.Window()
|
||||
|
||||
# API reference: https://lazka.github.io/pgi-docs/#WebKit2-4.0
|
||||
|
||||
self.closed = False
|
||||
self.success = False
|
||||
self.saml_result = {}
|
||||
self.verbose = verbose
|
||||
self.verbose = args.verbose
|
||||
|
||||
self.ctx = WebKit2.WebContext.get_default()
|
||||
if not verify:
|
||||
if not args.verify:
|
||||
self.ctx.set_tls_errors_policy(WebKit2.TLSErrorsPolicy.IGNORE)
|
||||
self.cookies = self.ctx.get_cookie_manager()
|
||||
if cookies:
|
||||
if args.cookies:
|
||||
self.cookies.set_accept_policy(WebKit2.CookieAcceptPolicy.ALWAYS)
|
||||
self.cookies.set_persistent_storage(cookies, WebKit2.CookiePersistentStorage.TEXT)
|
||||
self.cookies.set_persistent_storage(args.cookies, WebKit2.CookiePersistentStorage.TEXT)
|
||||
self.wview = WebKit2.WebView()
|
||||
|
||||
if user_agent is None:
|
||||
user_agent = 'PAN GlobalProtect'
|
||||
if args.no_proxy:
|
||||
data_manager = self.ctx.get_website_data_manager()
|
||||
data_manager.set_network_proxy_settings(WebKit2.NetworkProxyMode.NO_PROXY, None)
|
||||
|
||||
if args.user_agent is None:
|
||||
args.user_agent = 'PAN GlobalProtect'
|
||||
settings = self.wview.get_settings()
|
||||
settings.set_user_agent(user_agent)
|
||||
settings.set_user_agent(args.user_agent)
|
||||
self.wview.set_settings(settings)
|
||||
|
||||
window.resize(500, 500)
|
||||
@ -101,7 +113,8 @@ class SAMLLoginView:
|
||||
h = rs.get_http_headers() if rs else None
|
||||
if h:
|
||||
ct, cl = h.get_content_type(), h.get_content_length()
|
||||
content_type, charset = ct[0], ct.params.get('charset')
|
||||
content_type = ct[0]
|
||||
charset = ct.params.get('charset') if ct.params else None
|
||||
content_details = '%d bytes of %s%s for ' % (cl, content_type, ('; charset='+charset) if charset else '')
|
||||
print('[RECEIVE] %sresource %s %s' % (content_details if h else '', m, uri), file=stderr)
|
||||
|
||||
@ -124,10 +137,17 @@ class SAMLLoginView:
|
||||
uri = mr.get_uri()
|
||||
rs = mr.get_response()
|
||||
h = rs.get_http_headers() if rs else None
|
||||
ct = h.get_content_type()
|
||||
ct = h.get_content_type() if h else None
|
||||
|
||||
if self.verbose:
|
||||
print('[PAGE ] Finished loading page %s' % uri, file=stderr)
|
||||
urip = urlparse(uri)
|
||||
origin = '%s %s' % ('🔒' if urip.scheme == 'https' else '🔴', urip.netloc)
|
||||
self.window.set_title("SAML Login (%s)" % origin)
|
||||
|
||||
# if no response or no headers (for e.g. about:blank), skip checking this
|
||||
if not rs or not h:
|
||||
return
|
||||
|
||||
# convert to normal dict
|
||||
d = {}
|
||||
@ -212,10 +232,25 @@ class TLSAdapter(requests.adapters.HTTPAdapter):
|
||||
We have extracted the relevant value from <openssl/ssl.h>.
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, verify=True):
|
||||
self.verify = verify
|
||||
super().__init__()
|
||||
|
||||
def init_poolmanager(self, connections, maxsize, block=False):
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
ssl_context.set_ciphers('DEFAULT:@SECLEVEL=1')
|
||||
ssl_context.options |= 1<<2 # OP_LEGACY_SERVER_CONNECT
|
||||
|
||||
if not self.verify:
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
if hasattr(ssl_context, "keylog_filename"):
|
||||
sslkeylogfile = environ.get("SSLKEYLOGFILE")
|
||||
if sslkeylogfile:
|
||||
ssl_context.keylog_filename = sslkeylogfile
|
||||
|
||||
self.poolmanager = urllib3.PoolManager(
|
||||
num_pools=connections,
|
||||
maxsize=maxsize,
|
||||
@ -236,6 +271,7 @@ def parse_args(args = None):
|
||||
x.add_argument('-K', '--no-cookies', dest='cookies', action='store_const', const=None,
|
||||
help="Don't use or store cookies at all")
|
||||
x = p.add_mutually_exclusive_group()
|
||||
p.add_argument('-i', '--ignore-redirects', action='store_true', help='Use specified gateway hostname as server, ignoring redirects')
|
||||
x.add_argument('-g','--gateway', dest='interface', action='store_const', const='gateway', default='portal',
|
||||
help='SAML auth to gateway')
|
||||
x.add_argument('-p','--portal', dest='interface', action='store_const', const='portal',
|
||||
@ -251,6 +287,7 @@ def parse_args(args = None):
|
||||
x.add_argument('-x','--external', action='store_true', help='Launch external browser (for debugging)')
|
||||
x.add_argument('-P','--pkexec-openconnect', action='store_const', dest='exec', const='pkexec', help='Use PolicyKit to exec openconnect')
|
||||
x.add_argument('-S','--sudo-openconnect', action='store_const', dest='exec', const='sudo', help='Use sudo to exec openconnect')
|
||||
x.add_argument('-E','--exec-openconnect', action='store_const', dest='exec', const='exec', help='Execute openconnect directly (advanced users)')
|
||||
g.add_argument('-u','--uri', action='store_true', help='Treat server as the complete URI of the SAML entry point, rather than GlobalProtect server')
|
||||
g.add_argument('--clientos', choices=set(pf2clientos.values()), default=default_clientos, help="clientos value to send (default is %(default)s)")
|
||||
p.add_argument('-f','--field', dest='extra', action='append', default=[],
|
||||
@ -259,6 +296,7 @@ def parse_args(args = None):
|
||||
help='Allow use of insecure renegotiation or ancient 3DES and RC4 ciphers')
|
||||
p.add_argument('--user-agent', '--useragent', default='PAN GlobalProtect',
|
||||
help='Use the provided string as the HTTP User-Agent header (default is %(default)r, as used by OpenConnect)')
|
||||
p.add_argument('--no-proxy', action='store_true', help='Disable system proxy settings')
|
||||
p.add_argument('openconnect_extra', nargs='*', help="Extra arguments to include in output OpenConnect command-line")
|
||||
args = p.parse_args(args)
|
||||
|
||||
@ -284,7 +322,7 @@ def main(args = None):
|
||||
|
||||
s = requests.Session()
|
||||
if args.insecure:
|
||||
s.mount('https://', TLSAdapter())
|
||||
s.mount('https://', TLSAdapter(verify=args.verify))
|
||||
s.headers['User-Agent'] = 'PAN GlobalProtect' if args.user_agent is None else args.user_agent
|
||||
s.cert = args.cert
|
||||
|
||||
@ -354,7 +392,7 @@ def main(args = None):
|
||||
# spawn WebKit view to do SAML interactive login
|
||||
if args.verbose:
|
||||
print("Got SAML %s, opening browser..." % sam, file=stderr)
|
||||
slv = SAMLLoginView(uri, html, verbose=args.verbose, cookies=args.cookies, verify=args.verify, user_agent=args.user_agent)
|
||||
slv = SAMLLoginView(uri, html, args)
|
||||
Gtk.main()
|
||||
if slv.closed:
|
||||
print("Login window closed by user.", file=stderr)
|
||||
@ -364,7 +402,10 @@ def main(args = None):
|
||||
|
||||
# extract response and convert to OpenConnect command-line
|
||||
un = slv.saml_result.get('saml-username')
|
||||
server = slv.saml_result.get('server', args.server)
|
||||
if args.ignore_redirects:
|
||||
server = args.server
|
||||
else:
|
||||
server = slv.saml_result.get('server', args.server)
|
||||
|
||||
for cn, ifh in (('prelogin-cookie','gateway'), ('portal-userauthcookie','portal')):
|
||||
cv = slv.saml_result.get(cn)
|
||||
@ -393,6 +434,8 @@ def main(args = None):
|
||||
if key:
|
||||
openconnect_args.insert(1, "--sslkey="+key)
|
||||
openconnect_args.insert(1, "--certificate="+cert)
|
||||
if args.no_proxy:
|
||||
openconnect_args.insert(1, "--no-proxy")
|
||||
|
||||
openconnect_command = ''' echo {} |\n sudo openconnect {}'''.format(
|
||||
quote(cv), " ".join(map(quote, openconnect_args)))
|
||||
@ -400,9 +443,14 @@ def main(args = None):
|
||||
if args.verbose:
|
||||
# Warn about ambiguities
|
||||
if server != args.server and not args.uri:
|
||||
print('''IMPORTANT: During the SAML auth, you were redirected from {0} to {1}. This probably '''
|
||||
'''means you should specify {1} as the server for final connection, but we're not 100% '''
|
||||
'''sure about this. You should probably try both.\n'''.format(args.server, server), file=stderr)
|
||||
if args.ignore_redirects:
|
||||
print('''IMPORTANT: During the SAML auth, you were redirected from {0} to {1}. This probably '''
|
||||
'''means you should specify {1} as the server for final connection, but we're not 100% '''
|
||||
'''sure about this. You should probably try both; if necessary, use the '''
|
||||
'''--ignore-redirects option to specify desired behavior.\n'''.format(args.server, server), file=stderr)
|
||||
else:
|
||||
print('''IMPORTANT: During the SAML auth, you were redirected from {0} to {1}, however the '''
|
||||
'''redirection was ignored because you specified --ignore-redirects.\n'''.format(args.server, server), file=stderr)
|
||||
if ifh != args.interface and not args.uri:
|
||||
print('''IMPORTANT: We started with SAML auth to the {} interface, but received a cookie '''
|
||||
'''that's often associated with the {} interface. You should probably try both.\n'''.format(args.interface, ifh),
|
||||
@ -423,10 +471,11 @@ def main(args = None):
|
||||
# redirect stdin from this file, before it is closed by the context manager
|
||||
# (it will remain accessible via the open file descriptor)
|
||||
dup2(tf.fileno(), 0)
|
||||
cmd = ["openconnect"] + openconnect_args
|
||||
if args.exec == 'pkexec':
|
||||
cmd = ["pkexec", "--user", "root", "openconnect"] + openconnect_args
|
||||
cmd = ["pkexec", "--user", "root"] + cmd
|
||||
elif args.exec == 'sudo':
|
||||
cmd = ["sudo", "openconnect"] + openconnect_args
|
||||
cmd = ["sudo"] + cmd
|
||||
execvp(cmd[0], cmd)
|
||||
|
||||
else:
|
||||
|
||||
@ -1,13 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
from __future__ import print_function
|
||||
from sys import stderr, version_info, platform
|
||||
if (version_info >= (3, 0)):
|
||||
from urllib.parse import urlparse, urlencode
|
||||
raw_input = input
|
||||
else:
|
||||
from urlparse import urlparse
|
||||
from urllib import urlencode
|
||||
from sys import stderr, platform
|
||||
from urllib.parse import urlparse, urlencode
|
||||
import requests
|
||||
import argparse
|
||||
import getpass
|
||||
@ -68,7 +62,7 @@ if prelogin:
|
||||
else:
|
||||
# same request params work for /global-protect/getconfig.esp as for /ssl-vpn/login.esp
|
||||
if args.user == None:
|
||||
args.user = raw_input('Username: ')
|
||||
args.user = input('Username: ')
|
||||
if args.password == None:
|
||||
args.password = getpass.getpass('Password: ')
|
||||
data=dict(user=args.user, passwd=args.password,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user