Example #1
0
    def attachSession(self, session: requests.Session) -> None:
        """Gives a Session superpowers
        
        This will decorate the get(), post() and request() methods of the session, in order to transparently authenticate with WebAuth when presented with one.

        For example, you can use s.get("https://checkmate.ics.uci.edu") and you will get the Checkmate page. WebAuthBot will deal with the login redirect transparently, without you noticing.

        If you try something like s.get("https://login.uci.edu/ucinetid/webauth?return_url=http%3A%2F%2Fcheckmate.ics.uci.edu"), you will also get the Checkmate page as the response, magically.

        To disable the WebAuthBot augmentations, set the `eee` attribute to False.

        Args:
            session: A Session object
        """
        session.eee = True
        eeeobj = self

        def session_request(self, method, url, **kwargs):
            if not self.eee:
                # WebAuthBot augs disabled, pass through
                return requests.Session.request(self, method, url, **kwargs)
            else:
                response = requests.Session.request(self, method, url,
                                                    **kwargs)
                parsed = urllib.parse.urlparse(response.url)
                if response.url.startswith(WEBAUTH_ENDPOINT):
                    # Needs authentication
                    needsAuth = response.url
                elif response.url.startswith(SHIBIDP_SAML_REDIRECT):
                    # Already authed via Shibboleth
                    needsAuth = False
                    url = eeeobj._handleSamlRedirect(response, self)
                    response = requests.Session.request(
                        self, method, url, **kwargs)
                elif parsed.netloc in AUTH_MARKERS and response.headers[
                        'content-type'].startswith('text/html'):
                    # Check logged-in markers
                    soup = BeautifulSoup(response.text, 'html.parser')
                    if soup.find(*AUTH_MARKERS[parsed.netloc]['auth']):
                        needsAuth = False
                    else:
                        needsAuth = url
                else:
                    # We are clear :)
                    needsAuth = False

                if needsAuth:
                    target = eeeobj.authenticate(needsAuth, self)
                    targetres = requests.Session.request(
                        self, method, target, **kwargs)
                    if targetres.url.startswith(WEBAUTH_ENDPOINT):
                        raise WebAuthLoopError
                    return targetres
                else:
                    return response

        session.request = types.MethodType(session_request, session)
Example #2
0
    def authenticate(self,
                     returnUrl: str,
                     session: requests.Session = None) -> str:
        """Attempts to authenticate to WebAuth

        Note:
            Although optional, it is strongly recommended to pass a Session object so that the auth cookies can be preserved. This is required for EEE, for example.

        Args:
            returnUrl: The URL of a protected resource
            session (optional): A Session object. If not specified, one will be created just for this call.

        Returns:
            The return URL, possibly with a auth token included in the query string. A cookie will be added to the session.
        """
        if session is None:
            session = requests.Session()

        if returnUrl.startswith(WEBAUTH_ENDPOINT):
            # Looks like a WebAuth URL
            qs = urllib.parse.parse_qs(urllib.parse.urlparse(returnUrl).query)
            if 'return_url' in qs:
                # Get the real return URL
                endpoint = returnUrl
                returnUrl = qs['return_url'].pop()
            else:
                # Not a parsable WebAuth URL
                endpoint = WEBAUTH_ENDPOINT
        else:
            endpoint = WEBAUTH_ENDPOINT

        data = {
            'referer': '',
            'return_url': returnUrl,
            'info_text': '',
            'info_url': '',
            'submit_type': '',
            'ucinetid': self.__ucinetid,
            'password': self.__password,
            'login_button': 'Login'  # Surprisingly, this is required
        }
        params = {'return_url': returnUrl}

        session.eee = False
        r = session.post(endpoint, params=params, data=data)
        session.eee = True
        if r.url.startswith(WEBAUTH_ENDPOINT):
            soup = BeautifulSoup(r.text, 'html.parser')
            redirect = soup.find('meta', {'http-equiv': 'refresh'})
            if not redirect:
                # The attempt was unsuccessful
                raise WebAuthFailureError

            if not redirect.has_attr('content'):
                raise WebAuthUnknownError('Malformed <meta> tag')

            matches = re.match(r'^[0-9]\;url\=(.+)', redirect['content'])
            if not matches:
                raise WebAuthUnknownError(
                    'Malformed content attribute: {}'.format(
                        redirect['content']))

            finalUrl = matches.group(1)
            if finalUrl.startswith(SHIBIDP_REMOTE_ENDPOINT):
                # Shibboleth IDP
                session.eee = False
                idp = session.get(finalUrl)
                session.eee = True

                if not idp.url.startswith(SHIBIDP_SAML_REDIRECT):
                    raise WebAuthUnknownError(
                        'Unexpected Shibboleth redirect: {}'.format(idp.url))

                return self._handleSamlRedirect(idp, session)

            return finalUrl
        else:
            # WebAuth is not supposed to do that
            # Don't know what to do - Let's bail
            raise WebAuthUnknownError