Skip to content

Commit

Permalink
PA-7854 Added support for multi step/page form authentication (#59)
Browse files Browse the repository at this point in the history
* PA-7854 Added multi step/page form support

* version

* modified docs, cleanup

* code review
  • Loading branch information
SOOS-JAlvarez committed Jan 19, 2023
1 parent c7ef8a1 commit 7ea39f4
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 11 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ The basic command to run a baseline scan would look like:
| `--authUsernameField` | None | Username input id to use in auth apps |
| `--authPasswordField` | None | Password input id to use in auth apps |
| `--authSubmitField` | None | Submit button id to use in auth apps |
| `--authFirstSubmitField` | None | First submit button id to use in auth apps |
| `--authSecondSubmitField` | None | Second submit button id to use in auth apps (for multi-page forms) |
| `--authFormType` | simple | simple (all fields are displayed at once), wait_for_password (Password field is displayed only after username is filled), or multi_page (Password field is displayed only after username is filled and submit is clicked) |
| `--authDelayTime` | Delay time in seconds to wait for the page to load after performing actions in the form. (Used only on authFormType: wait_for_password and multi_page) |
| `--authSubmitAction` | None | Submit action to perform on form filled. Options: click or submit |
| `--zapOptions` | None | Additional ZAP Options |
| `--requestCookies` | None | Set Cookie values for the requests to the target URL |
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.17
1.0.18
17 changes: 15 additions & 2 deletions helpers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def login(self):
log(f"authenticate using webdriver against URL: {self.config.auth_login_url}")

self.driver.get(self.config.auth_login_url)
final_submit_button = self.config.auth_submit_field_name

# wait for the page to load
sleep(5)
Expand All @@ -200,6 +201,18 @@ def login(self):
if self.config.auth_username:
username_element = self.fill_username()

if self.config.auth_form_type == 'wait_for_password':
log(f"Waiting for {self.config.auth_password_field_name} element to load")
sleep(self.config.auth_delay_time)

if self.config.auth_form_type == 'multi_page':
continue_button = self.find_element(self.config.auth_submit_field_name, "submit", "//*[@type='submit' or @type='button' or button]" )
actions = ActionChains(self.driver)
actions.move_to_element(continue_button).click().perform()
final_submit_button = self.config.auth_submit_second_field_name
log(f"Clicked the first submit element for multi page")
sleep(self.config.auth_delay_time)

# fill out the password field
if self.config.auth_password:
try:
Expand All @@ -223,12 +236,12 @@ def login(self):
# if the OTP field was not found, we probably need to submit to go to the OTP page
# login flow: username -> next -> password -> next -> otp -> submit
self.submit_form(self.config.auth_submit_action,
self.config.auth_submit_field_name, self.config.auth_password_field_name)
final_submit_button, self.config.auth_password_field_name)
self.fill_otp()

# submit
self.submit_form(self.config.auth_submit_action,
self.config.auth_submit_field_name, self.config.auth_password_field_name)
final_submit_button, self.config.auth_password_field_name)

# wait for the page to load
if self.config.auth_check_element:
Expand Down
8 changes: 6 additions & 2 deletions helpers/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ class DASTConfig:
auth_password: Optional[str] = None
auth_otp_secret: Optional[str] = None
auth_submit_action: Optional[str] = None
auth_form_type: Optional[str] = None
auth_delay_time: Optional[int] = None
auth_token_endpoint: Optional[str] = None
auth_bearer_token: Optional[str] = None
auth_username_field_name: Optional[str] = None
auth_password_field_name: Optional[str] = None
auth_otp_field_name: Optional[str] = None
auth_submit_field_name: Optional[str] = None
auth_first_submit_field_name: Optional[str] = None
auth_submit_second_field_name: Optional[str] = None
auth_check_delay: Optional[float] = None
auth_check_element: Optional[str] = None
auth_exclude_urls: Optional[List[str]] = None
Expand All @@ -45,14 +47,16 @@ def load_config(self, extra_zap_params):
self.auth_password = self._get_zap_param('auth.password') or EMPTY_STRING
self.auth_otp_secret = self._get_zap_param('auth.otpsecret') or EMPTY_STRING
self.auth_submit_action = self._get_zap_param('auth.submit_action') or 'click'
self.auth_form_type = self._get_zap_param('auth.form_type') or 'simple'
self.auth_token_endpoint = self._get_zap_param('auth.token_endpoint') or EMPTY_STRING
self.auth_bearer_token = self._get_zap_param('auth.bearer_token') or EMPTY_STRING
self.auth_username_field_name = self._get_zap_param('auth.username_field') or 'username'
self.auth_password_field_name = self._get_zap_param('auth.password_field') or 'password'
self.auth_display = self._get_zap_param('auth.display') or EMPTY_STRING
self.auth_otp_field_name = self._get_zap_param('auth.otp_field') or 'otp'
self.auth_submit_field_name = self._get_zap_param('auth.submit_field') or 'login'
self.auth_first_submit_field_name = self._get_zap_param('auth.first_submit_field') or 'next'
self.auth_submit_second_field_name = self._get_zap_param('auth.second_submit_field') or 'login'
self.auth_delay_time = self._get_zap_param_float('auth.delay_time') or 0
self.auth_check_delay = self._get_zap_param_float('auth.check_delay') or 5
self.auth_check_element = self._get_zap_param('auth.check_element') or EMPTY_STRING
self.auth_exclude_urls = self._get_zap_param_list('auth.exclude') or list()
Expand Down
1 change: 1 addition & 0 deletions helpers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
EMPTY_STRING = ''
FAIL_THE_BUILD = "fail_the_build"
CONTINUE_ON_FAILURE = "continue_on_failure"
AUTH_DELAY_TIME = 5 # seconds

# SCAN MODES
BASELINE = 'baseline'
Expand Down
35 changes: 30 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ def __init__(self):
self.auth_username_field_name: Optional[str] = None
self.auth_password_field_name: Optional[str] = None
self.auth_submit_field_name: Optional[str] = None
self.auth_first_submit_field_name: Optional[str] = None
self.auth_submit_second_field_name: Optional[str] = None
self.auth_submit_action: Optional[str] = None
self.auth_form_type: Optional[str] = None
self.auth_delay_time: Optional[int] = None
self.auth_exclude_urls: Optional[str] = None
self.auth_display: bool = False
self.auth_bearer_token: Optional[str] = None
Expand Down Expand Up @@ -223,10 +225,14 @@ def parse_configuration(self, configuration: Dict, target_url: str):
self.auth_password_field_name = value
elif key == 'authSubmitField':
self.auth_submit_field_name = value
elif key == 'authFirstSubmitField':
self.auth_first_submit_field_name = value
elif key == 'authFormSubmitSecondField':
self.auth_submit_second_field_name = value
elif key == 'authSubmitAction':
self.auth_submit_action = value
elif key == 'authFormType':
self.auth_form_type = value
elif key == 'authDelayTime':
self.auth_delay_time = value
elif key == "zapOptions":
value = array_to_str(value)
self.zap_options = value
Expand Down Expand Up @@ -328,8 +334,14 @@ def __add_zap_options__(self, args: List[str]) -> None:
zap_options.append(self.__add_custom_option__(label="auth.display", value=self.auth_display))
if self.auth_submit_field_name is not None:
zap_options.append(self.__add_custom_option__(label="auth.submit_field", value=self.auth_submit_field_name))
if self.auth_submit_second_field_name is not None:
zap_options.append(self.__add_custom_option__(label="auth.second_submit_field", value=self.auth_submit_second_field_name))
if self.auth_submit_action is not None:
zap_options.append(self.__add_custom_option__(label="auth.submit_action", value=self.auth_submit_action))
if self.auth_form_type is not None:
zap_options.append(self.__add_custom_option__(label="auth.form_type", value=self.auth_form_type))
if self.auth_delay_time is not None:
zap_options.append(self.__add_custom_option__(label="auth.delay_time", value=self.auth_delay_time))
if self.auth_username_field_name is not None:
zap_options.append(self.__add_custom_option__(label="auth.username_field", value=self.auth_username_field_name))
if self.auth_password_field_name is not None:
Expand Down Expand Up @@ -789,8 +801,8 @@ def parse_args(self) -> None:
required=False,
)
parser.add_argument(
"--authFirstSubmitField",
help="First submit button id to use in auth apps",
"--authSecondSubmitField",
help="Second submit button id to use in auth apps (for multi-page forms)",
required=False,
)
parser.add_argument(
Expand All @@ -799,6 +811,19 @@ def parse_args(self) -> None:
type=str,
required=False,
)
parser.add_argument(
"--authFormType",
help="simple (all fields are displayed at once), wait_for_password (Password field is displayed only after username is filled), or multi_page (Password field is displayed only after username is filled and submit is clicked)",
type=str,
default="simple",
required=False,
)
parser.add_argument(
"--authDelayTime",
help="Delay time in seconds to wait for the page to load after performing actions in the form. (Used only on authFormType: wait_for_password and multi_page)",
default=Constants.AUTH_DELAY_TIME,
required=False,
)
parser.add_argument(
"--zapOptions",
help="Additional ZAP Options",
Expand Down

0 comments on commit 7ea39f4

Please sign in to comment.