def test_allows_with_allowed_hosts(self): request = RequestFactory().get('/', secure=True) foobaa_domain = 'foobaa.com' assert is_safe_url( f'https://{foobaa_domain}/foo', request, allowed_hosts=[foobaa_domain] ) assert not is_safe_url( f'https://{settings.DOMAIN}', request, allowed_hosts=[foobaa_domain] )
def get(self, request, user, identity, next_path): # At this point @with_user guarantees that we have a valid fxa # identity. We are proceeding with either registering the user or # logging them on. if user is None or user.deleted: action = 'register' if user is None: user = register_user(identity) else: reregister_user(user) if not is_safe_url(next_path, request): next_path = None # If we just reverse() directly, we'd use a prefixer instance # initialized from the current view, which would not contain the # app information since it's a generic callback, the same for # everyone. To ensure the user stays on the app/locale they were # on, we extract that information from the next_path if present # and set locale/app on the prefixer instance that reverse() will # use automatically. if next_path: if prefixer := get_url_prefix(): splitted = prefixer.split_path(next_path) prefixer.locale = splitted[0] prefixer.app = splitted[1] edit_page = reverse('users.edit') if next_path: next_path = f'{edit_page}?to={quote_plus(next_path)}' else: next_path = edit_page
def safe_referer_redirect(self, request, default_url): referer = request.META.get('HTTP_REFERER') allowed_hosts = ( settings.DOMAIN, urlparse(settings.EXTERNAL_SITE_URL).netloc, ) if referer and is_safe_url(referer, request, allowed_hosts): return redirect(referer) return redirect(default_url)
def parse_next_path(state_parts, request=None): next_path = None if len(state_parts) == 2: # The = signs will be stripped off so we need to add them back # but it only cares if there are too few so add 4 of them. encoded_path = state_parts[1] + '====' try: next_path = base64.urlsafe_b64decode( force_bytes(encoded_path)).decode('utf-8') except (TypeError, ValueError): log.info(f'Error decoding next_path {encoded_path}') pass if not is_safe_url(next_path, request): next_path = None return next_path
def get(self, request, user, identity, next_path): # At this point @with_user guarantees that we have a valid fxa # identity. We are proceeding with either registering the user or # logging them on. if user is None or user.deleted: action = 'register' if user is None: user = register_user(identity) else: reregister_user(user) edit_page = reverse('users.edit') if is_safe_url(next_path, request): next_path = f'{edit_page}?to={quote_plus(next_path)}' else: next_path = edit_page else: action = 'login' login_user(self.__class__, request, user, identity) response = safe_redirect(request, next_path, action) add_api_token_to_response(response, user) return response
def fxa_login_url( config, state, next_path=None, action=None, force_two_factor=False, request=None, id_token=None, ): if next_path and is_safe_url(next_path, request): state += ':' + force_str(urlsafe_b64encode( next_path.encode('utf-8'))).rstrip('=') query = { 'client_id': config['client_id'], 'scope': 'profile openid', 'state': state, 'access_type': 'offline', } if action is not None: query['action'] = action if force_two_factor is True: # Specifying AAL2 will require the token to have an authentication # assurance level >= 2 which corresponds to requiring 2FA. query['acr_values'] = 'AAL2' # Requesting 'prompt=none' during authorization, together with passing # a valid id token in 'id_token_hint', allows the user to not have to # re-authenticate with FxA if they still have a valid session (which # they should here: they went through FxA, back to AMO, and now we're # redirecting them to FxA because we want them to have 2FA enabled). if id_token: query['prompt'] = 'none' query['id_token_hint'] = id_token if use_fake_fxa(): base_url = reverse('fake-fxa-authorization') else: base_url = f'{settings.FXA_OAUTH_HOST}/authorization' return f'{base_url}?{urlencode(query)}'
def safe_redirect(request, url, action): if not is_safe_url(url, request): url = reverse('home') log.info(f'Redirecting after {action} to: {url}') return HttpResponseRedirect(url)
def test_allows_code_manager_site_url(self): request = RequestFactory().get('/', secure=True) external_domain = urlparse(settings.CODE_MANAGER_URL).netloc assert is_safe_url(f'https://{external_domain}/foo', request)
def test_allows_domain(self): request = RequestFactory().get('/', secure=True) assert is_safe_url(f'https://{settings.DOMAIN}/foo', request) assert not is_safe_url('https://not-olympia.dev', request)
def test_does_not_require_https_when_request_is_not_secure(self): request = RequestFactory().get('/', secure=False) assert is_safe_url(f'https://{settings.DOMAIN}', request) assert is_safe_url(f'http://{settings.DOMAIN}', request)
def test_enforces_https_when_request_is_secure(self): request = RequestFactory().get('/', secure=True) assert is_safe_url(f'https://{settings.DOMAIN}', request) assert not is_safe_url(f'http://{settings.DOMAIN}', request)
def test_proxy_port_defaults_to_none(self): request = RequestFactory().get('/') assert is_safe_url('https://mozilla.com', request) assert not is_safe_url('https://mozilla.com:7000', request)
def test_includes_host_for_proxy_when_proxy_port_setting_exists(self): request = RequestFactory().get('/') assert is_safe_url('https://mozilla.com:1234', request) assert not is_safe_url('https://mozilla.com:9876', request)