]> code.communitydata.science - nu-vpn-proxy.git/blobdiff - gp-saml-gui.py
removed two out-of-date commands
[nu-vpn-proxy.git] / gp-saml-gui.py
index f540a3b27f80319b1bc9a76e727936963bd70358..a046e5329e1167c001b07cc14e210f2a9e34da06 100755 (executable)
@@ -1,9 +1,15 @@
 #!/usr/bin/env python3
 
 #!/usr/bin/env python3
 
+import warnings
 try:
     import gi
 try:
     import gi
+
     gi.require_version('Gtk', '3.0')
     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:
     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
         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:
     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
 
 import argparse
 import urllib3
@@ -24,11 +31,11 @@ import ssl
 import tempfile
 
 from operator import setitem
 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 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
 
 
 from html.parser import HTMLParser
 
 
@@ -45,30 +52,35 @@ COOKIE_FIELDS = ('prelogin-cookie', 'portal-userauthcookie')
 
 
 class SAMLLoginView:
 
 
 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)
         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 = {}
 
         # 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()
 
         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()
             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_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()
 
         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 = 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)
         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()
         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)
 
             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
         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)
 
         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 = {}
 
         # convert to normal dict
         d = {}
@@ -212,10 +232,25 @@ class TLSAdapter(requests.adapters.HTTPAdapter):
     We have extracted the relevant value from <openssl/ssl.h>.
 
     '''
     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
     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,
         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()
     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',
     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('-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=[],
     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)')
                    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)
 
     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 = 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
 
     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)
     # 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)
     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')
 
     # 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)
 
     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 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)))
 
     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:
     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),
         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)
             # 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':
         if args.exec == 'pkexec':
-            cmd = ["pkexec", "--user", "root", "openconnect"] + openconnect_args
+            cmd = ["pkexec", "--user", "root"] + cmd
         elif args.exec == 'sudo':
         elif args.exec == 'sudo':
-            cmd = ["sudo", "openconnect"] + openconnect_args
+            cmd = ["sudo"] + cmd
         execvp(cmd[0], cmd)
 
     else:
         execvp(cmd[0], cmd)
 
     else:

Community Data Science Collective || Want to submit a patch?