Ejemplo n.º 1
0
    def authenticate_to_adfs_portal(self, response):
        payload = self.generate_payload_from_login_page(response)
        idp_auth_form_submit_url = self.build_idp_auth_url(response)

        logger.debug(
            'Posting login data to URL: {}'.format(idp_auth_form_submit_url))
        login_response = self.session.post(idp_auth_form_submit_url,
                                           data=payload,
                                           verify=True)
        login_response_page = BeautifulSoup(login_response.text, "html.parser")
        # Checks for errorText id on page to indicate any errors
        login_error_message = login_response_page.find(id='errorText')
        # Checks for specific text in a paragraph element to indicate any errors
        vip_login_error_message = login_response_page.find(
            lambda tag: tag.name == "p" and "Authentication failed" in tag.text
        )
        if (login_error_message and len(login_error_message.string) > 0) or (
                vip_login_error_message and len(vip_login_error_message) > 0):
            msg = ('Login page returned the following message. '
                   'Please resolve this issue before continuing:')
            click.secho(msg, fg='red')
            error_msg = login_error_message if login_error_message else vip_login_error_message
            click.secho(error_msg.string, fg='red')
            sys.exit(1)
        return login_response
Ejemplo n.º 2
0
    def handle_okta_verification(self, response):
        # If there is no assertion, and we find an Okta portal on the page,
        # we have to pass through the Okta portal regardless of whether MFA
        # will be required or not.
        if not self.okta_org:
            msg = ('Okta MFA required but no Okta Organization set. '
                   'Please either set in the config or use `--okta-org`')
            click.secho(msg, fg='red')
            sys.exit(1)

        verification_status = self.get_verification_status_from_response(
            response)
        logger.debug(
            'Current Verification Status: {}.'.format(verification_status))
        if verification_status == 'success':
            logger.debug(
                'Okta portal already authenticated, passing through...')
            # If the Okta portal status is already 'success', we can just
            # pass through the Okta portal, otherwise, we will have to do MFA.
            okta_form_submit_response = self.submit_adapter_glue_form(response)
            return okta_form_submit_response
        elif verification_status == 'mfa_required':
            # If the Okta portal is in 'mfa_required' status,
            # we need to begin the MFA process.
            okta_available_factors = self.fetch_available_mfa_factors()
            mfa_verified = self.process_okta_mfa(okta_available_factors)
            if mfa_verified:
                okta_response = self.submit_adapter_glue_form(response)
                return okta_response

        click.secho('Okta verification failed. Exiting...', fg='red')
        sys.exit(1)
Ejemplo n.º 3
0
    def okta_totp_verification(self, factor_details):
        if not self.okta_shared_secret:
            logger.debug(
                'TOTP Verification available but Okta Shared Secret '
                'is not set. For instructions to set the Shared Secret, '
                'refer to the README: '
                'https://github.com/cshamrick/stsauth/blob/master/README.md')
            return False

        totp = pyotp.TOTP(self.okta_shared_secret)
        verify_url = factor_details.get('_links', {}).get('verify',
                                                          {}).get('href')
        try:
            pass_code = totp.now()
        except Exception as e:
            msg = 'An error occured fetching your TOTP code. Please check your Shared Secret.'
            click.secho(msg, fg='red')
            click.secho('Error: {}'.format(str(e)), fg='red')
            sys.exit(1)
        if verify_url:
            verify_response = self.session.post(verify_url,
                                                json={
                                                    'stateToken':
                                                    self.state_token,
                                                    'passCode': pass_code
                                                })
            if verify_response.ok:
                status = verify_response.json().get('status')
                if status == 'SUCCESS':
                    return True
        click.secho(
            'TOTP Verification failed. '
            'Continuing to other methods if available',
            fg='red')
        return False
Ejemplo n.º 4
0
 def submit_adapter_glue_form(self, response):
     response.soup = BeautifulSoup(response.content, 'lxml')
     adapter_glue_form = response.soup.find(id='adapterGlue')
     referer = response.url
     self.session.headers.update({'Referer': referer})
     selectors = ",".join("{}[name]".format(i)
                          for i in ("input", "button", "textarea",
                                    "select"))
     data = [(tag.get('name'), tag.get('value'))
             for tag in adapter_glue_form.select(selectors)]
     logger.debug('Posting data to url: {}\n{}'.format(referer, data))
     return self.session.post(referer, data=data)
Ejemplo n.º 5
0
    def get_saml_response(self, response=None):
        if not response:
            logger.debug('No response provided. Fetching IDP Entry URL...')
            response = self.session.get(self.idpentryurl)
        response.soup = BeautifulSoup(response.text, "lxml")
        assertion_pattern = re.compile(
            r'name=\"SAMLResponse\" value=\"(.*)\"\s*/><noscript>')
        assertion = re.search(assertion_pattern, response.text)

        if assertion:
            # If there is already an assertion in the response body,
            # we can attach the parsed assertion to the response object and
            # return the whole response for use later.
            # return account_map, assertion.group(1)
            response.assertion = assertion.group(1)
            return response
        logger.debug(
            'No SAML assertion found in response. Attempting to log in...')

        login_form = response.soup.find(id='loginForm')
        okta_login = response.soup.find(id='okta-login-container')

        if okta_login:
            state_token = utils.get_state_token_from_response(response.text)
            if state_token is None:
                click.secho('No State Token found in response. Exiting...',
                            fg='red')
                sys.exit(1)
            okta_client = Okta(session=self.session,
                               state_token=state_token,
                               okta_org=self.okta_org,
                               okta_shared_secret=self.okta_shared_secret)
            okta_response = okta_client.handle_okta_verification(response)
            return self.get_saml_response(response=okta_response)

        if login_form:
            # If there is no assertion, it is possible the user is attempting
            # to authenticate from outside the network, so we check for a login
            # form in their response.
            form_response = self.authenticate_to_adfs_portal(response)
            return self.get_saml_response(response=form_response)

        else:
            msg = 'Response did not contain a valid SAML assertion, a valid login form, or request MFA.'
            click.secho(msg, fg='red')
            sys.exit(1)
Ejemplo n.º 6
0
    def authenticate_to_adfs_portal(self, response):
        payload = self.generate_payload_from_login_page(response)
        idp_auth_form_submit_url = self.build_idp_auth_url(response)

        logger.debug(
            'Posting login data to URL: {}'.format(idp_auth_form_submit_url))
        login_response = self.session.post(idp_auth_form_submit_url,
                                           data=payload,
                                           verify=True)
        login_response_page = BeautifulSoup(login_response.text, "html.parser")
        login_error_message = login_response_page.find(id='errorText')
        if login_error_message and len(login_error_message.string) > 0:
            msg = ('Login page returned the following message. '
                   'Please resolve this issue before continuing:')
            click.secho(msg, fg='red')
            click.secho(login_error_message.string, fg='red')
            sys.exit(1)
        return login_response
Ejemplo n.º 7
0
    def generate_payload_from_login_page(self, response):
        login_page = BeautifulSoup(response.text, "html.parser")
        payload = {}

        for input_tag in login_page.find_all(re.compile('(INPUT|input)')):
            name = input_tag.get('name', '')
            value = input_tag.get('value', '')
            logger.debug(
                'Adding value for {!r} to Login Form payload.'.format(name))
            if "user" in name.lower():
                payload[name] = self.domain_user
            elif "email" in name.lower():
                payload[name] = self.domain_user
            elif "pass" in name.lower():
                payload[name] = self.password
            elif "security_code" in name.lower():
                payload[name] = self.vip_access_security_code
            else:
                payload[name] = value

        return payload
Ejemplo n.º 8
0
    def okta_push_verification(self,
                               factor_details,
                               notify_count=5,
                               poll_count=10):
        status = 'MFA_CHALLENGE'
        tries = 0
        verify_data = {'stateToken': self.state_token}
        verify_url = factor_details.get('_links', {}).get('verify',
                                                          {}).get('href')
        if verify_url is None:
            click.secho(
                'No Okta verification URL present in response. Exiting...',
                fg='red')
            sys.exit(1)
        while (status == 'MFA_CHALLENGE' and tries < notify_count):
            msg = '({}/{}) Waiting for Okta push notification to be accepted...'
            click.secho(msg.format(tries + 1, notify_count), fg='green')
            for _ in range(poll_count):
                verify_response = self.session.post(verify_url,
                                                    json=verify_data)
                if verify_response.ok:
                    verify_response_json = verify_response.json()
                    logger.debug('Okta Verification Response:\n{}'.format(
                        verify_response_json))
                    status = verify_response_json.get('status',
                                                      'MFA_CHALLENGE')

                    if verify_response_json.get('factorResult') == 'REJECTED':
                        click.secho(
                            'Okta push notification was rejected! Exiting...',
                            fg='red')
                        sys.exit(1)
                    if status == 'SUCCESS':
                        break
                    time.sleep(1)
            tries += 1

        return status == 'SUCCESS'
Ejemplo n.º 9
0
 def process_okta_mfa(self, okta_available_factors):
     # The full list of available factor types is available here:
     # https://developer.okta.com/docs/api/resources/factors#factor-type
     # ['token:software:totp', 'push', 'sms', 'question', 'call', 'token', 'token:hardware', 'web']
     logger.debug('Available Okta MFA factors found: {}.'.format(', '.join(
         okta_available_factors.keys())))
     if 'token:software:totp' in okta_available_factors.keys():
         logger.debug(
             'Okta TOTP Verification Method available, attempting to verify...'
         )
         totp_factor = okta_available_factors.get('token:software:totp')
         if self.okta_totp_verification(totp_factor):
             return True
     if 'push' in okta_available_factors.keys():
         logger.debug(
             'Okta Push Verification Method available, attempting to verify...'
         )
         push_factor = okta_available_factors.get('push')
         if self.okta_push_verification(push_factor):
             return True
     return False
Ejemplo n.º 10
0
 def parse_config_file(self):
     """Read configuration file and only set values if they
     were not passed in from the CLI.
     """
     if self.config.has_section('default'):
         logger.debug('Found \'default\' section in'
                      ' {0.credentialsfile!r}!'.format(self))
         default = self.config['default']
         msg = (
             'Attribute {1!r} not set, using value from {0.credentialsfile!r}'
         )
         if not self.region:
             logger.debug(msg.format(self, 'region'))
             self.region = default.get('region')
         if not self.output:
             logger.debug(msg.format(self, 'output'))
             self.output = default.get('output')
         if not self.idpentryurl:
             logger.debug(msg.format(self, 'idpentryurl'))
             self.idpentryurl = default.get('idpentryurl')
         if not self.domain:
             logger.debug(msg.format(self, 'domain'))
             self.domain = default.get('domain')
         if not self.okta_org:
             logger.debug(msg.format(self, 'okta_org'))
             self.okta_org = default.get('okta_org')
         if not self.okta_shared_secret:
             logger.debug(msg.format(self, 'okta_shared_secret'))
             self.okta_shared_secret = default.get('okta_shared_secret')
     else:
         logger.debug('Could not find \'default\' section in'
                      ' {0.credentialsfile!r}!'.format(self))