示例#1
0
    def check_site():
        try:
            params = parse_params(request.forms.decode(),
                                  domain=string_param('domain', required=True, strip=True,
                                                      min_length=1, max_length=DOMAIN_MAX_LENGTH),
                                  no_rescan=boolean_param('no_rescan', default=False, empty=True,
                                                          strip=True))
        except ParamParseError:
            return template('gpc_invalid', domain=None)

        domain = normalise_domain(params['domain'])
        if not check_domain(domain):
            return template('gpc_invalid', domain=domain)

        result = es_dao.get(domain)
        if result is not None:
            if params['no_rescan'] or result['status'] == 'pending':
                redirect(f'/sites/{domain}')

            # Non-pending scans should have a scan datetime.
            last_scan_dt = rfc3339.parse_datetime(result['last_scan_dt'])
            # If the last scan hasn't expired yet, don't rescan.
            if rfc3339.now() < last_scan_dt + SCAN_TTL:
                if testing_mode:
                    log.info('Would have redirected to existing scan for %(domain)s if on prod.',
                             {'domain': domain})
                else:
                    redirect(f'/sites/{domain}')

        r = requests.post(well_known_sites_endpoint, data={'domain': domain, 'rescan': 'true'})
        r.raise_for_status()

        redirect(f'/sites/{domain}')
示例#2
0
    def logout():
        params = parse_params(request.query.decode(),
                              continue_url=string_param('continue',
                                                        strip=True,
                                                        max_length=2000))
        continue_url = params.get('continue_url')
        if continue_url:
            check_continue_url(continue_url)

        if request.session is not None:
            session_handler.clear_session()

        redirect(continue_url or DEFAULT_CONTINUE_URL)
示例#3
0
    def submit_secret():
        if request.session:
            user_id = request.session['user_id']
        else:
            user_id = None

        params = parse_params(request.forms.decode(),
                              description=string_param('description',
                                                       strip=True,
                                                       max_length=100),
                              secret=string_param('secret',
                                                  required=True,
                                                  max_length=2000),
                              ttl=string_param('ttl',
                                               required=True,
                                               enum=VALID_TTLS.keys()))
        description = params.get('description')
        secret = params['secret']
        ttl = params['ttl']

        now_dt = rfc3339.now()
        secret_id = generate_id()
        secret = Secret(secret_id=secret_id,
                        user_id=user_id,
                        description=description,
                        secret=secret,
                        create_dt=now_dt,
                        expire_dt=now_dt + VALID_TTLS[ttl][0])
        dao.insert_secret(secret)

        response.status = 202
        response.set_header('Location', f'{service_path}/secrets/{secret_id}')
        return template('submit_result',
                        service_address=service_address,
                        user_id=user_id,
                        secret_id=secret_id,
                        ttl=VALID_TTLS[ttl][1])
示例#4
0
    def get_login():
        params = parse_params(request.query.decode(),
                              continue_url=string_param('continue',
                                                        strip=True,
                                                        max_length=2000))
        continue_url = params.get('continue_url')
        if continue_url:
            check_continue_url(continue_url)

        state, nonce, oidc_login_uri = construct_oidc_request('openid')

        # NOTE: The state field works as Login CSRF protection.
        session_handler.set_oidc_data(state, nonce, continue_url
                                      or DEFAULT_CONTINUE_URL)

        return template('login',
                        oidc_name=oidc_name,
                        oidc_about_url=oidc_about_url,
                        oidc_login_uri=oidc_login_uri)
示例#5
0
    def index():
        try:
            params = parse_params(request.query.decode(),
                                  domain=string_param('domain', strip=True,
                                                      min_length=1, max_length=DOMAIN_MAX_LENGTH))
            domain = params.get('domain')

        except ParamParseError:
            domain = None

        if domain:
            domain = normalise_domain(domain)
            if not check_domain(domain):
                domain = None

        scanned_count, supporting_count = es_dao.count(timeout=30)

        r = template('index', domain=domain,
                     scanned_count=scanned_count, supporting_count=supporting_count)
        set_headers(r, STATIC_FILE_HEADERS)
        return r
示例#6
0
    def get_oidc_callback():

        # NOTE: Generally raise 500 for unexpected issues with the oidc flow, caused either by our
        #       oidc state management or the oidc provider response. All traffic to this endpoint
        #       should be via the oidc flow, and the provider should be working to spec. If not,
        #       something is likely wrong with the flow implementation, so 500 is appropriate.

        # Check state and error before anything else, to make sure nothing's fishy
        params = parse_params(request.query.decode(),
                              state=string_param('state', strip=True),
                              error=string_param('error', strip=True),
                              error_description=string_param(
                                  'error_description', strip=True))

        # Use 500 rather than "nicer" error if state is missing.
        state = params.get('state')
        if not state:
            log.warning('Received OIDC callback with no state.')
            abort(500)

        oidc_data = session_handler.get_oidc_data(state)
        if not oidc_data:
            log.warning('Received OIDC callback with no OIDC data cookie.')
            abort(500)

        # Only part of the state is used when fetching the OIDC data, so still need to check it.
        if state != oidc_data['state']:
            log.warning('Received OIDC callback with mismatching state.')
            abort(500)

        # State seems to be OK, so this is a valid response for this request. Drop the OIDC data
        # so this state can't be used again.
        # NOTE: This won't take effect if a later error occurs.
        session_handler.clear_oidc_data(state)

        error = params.get('error')
        if error:
            # If they rejected the OIDC scopes, send them back home. Up to them to initiate again.
            # TODO: Should we show them a message explaining this somehow?
            if error == 'access_denied':
                redirect('/')
            # Any other error...
            else:
                error_description = params.get('error_description')
                log_msg = 'Received OIDC callback with error %(error)s'
                log_params = {'error': error}
                if error_description:
                    log_msg += ': %(error_description)s'
                    log_params['error_description'] = error_description
                log.warning(log_msg, log_params)
                abort(500)

        # If there wasn't an error, there should be a code
        params = parse_params(request.query.decode(),
                              code=string_param('code', strip=True))
        code = params.get('code')
        # Once again, use 500 rather than a "nicer" error if code is missing
        if not code:
            log.warning('Received OIDC callback with no code.')
            abort(500)

        # Check continue_url before attempting to call the OIDC provider, just in case.
        continue_url = oidc_data['continue_url']
        # NOTE: we check this again, since the oidc cookie data isn't signed so could be changed by
        #       the user agent.
        check_continue_url(continue_url)

        r = requests.post(oidc_token_endpoint,
                          timeout=10,
                          auth=(oidc_client_id, oidc_client_secret),
                          data={
                              'grant_type': 'authorization_code',
                              'client_id': oidc_client_id,
                              'redirect_uri': oidc_redirect_uri,
                              'code': code
                          })

        # Only supported response status code.
        if r.status_code == 200:
            pass
        else:
            log.warning(
                'OIDC token endpoint returned unexpected status code %(status_code)s.',
                {'status_code': r.status_code})
            r.raise_for_status()
            raise NotImplementedError(
                f'Unsupported status code {r.status_code}.')

        try:
            id_token = r.json()['id_token']
            id_token_jwt = token_decoder.decode_id_token(id_token)
        except (ValueError, KeyError, InvalidTokenError) as e:
            log.warning(
                'OIDC token endpoint returned invalid response: %(error)s',
                {'error': e})
            abort(500)

        if 'nonce' not in id_token_jwt:
            log.warning('OIDC token endpoint didn\'t return nonce in token.')
            abort(500)

        if id_token_jwt['nonce'] != hash_urlsafe(oidc_data['nonce']):
            log.warning(
                'OIDC token endpoint returned incorrect nonce in token.')
            abort(500)

        session_handler.set_session(id_token)
        redirect(continue_url)