Exemple #1
0
def set_headers(response: Response) -> Response:
    if not in_development():
        response.headers[
            "Strict-Transport-Security"] = "max-age=63072000"  # max age of 2 years
    response.headers["Content-Security-Policy"] = "frame-ancestors 'none'"
    response.headers["X-Frame-Options"] = "DENY"
    return response
Exemple #2
0
    def get_batch_ids(
            self,
            state_code: StateCode,
            *,
            override_fs: Optional[GCSFileSystem] = None) -> List[str]:
        """Returns a sorted list of batch id numbers from the a specific state bucket from GCS"""
        if in_development():
            project_id = GCP_PROJECT_STAGING
        else:
            project_id = metadata.project_id()
        if override_fs is None:
            buckets = self.monthly_reports_gcsfs.ls_with_blob_prefix(
                bucket_name=f"{project_id}-report-html",
                blob_prefix=state_code.value,
            )
        else:
            buckets = override_fs.ls_with_blob_prefix(
                bucket_name=f"{project_id}-report-html",
                blob_prefix=state_code.value,
            )
        files = [file for file in buckets if isinstance(file, GcsfsFilePath)]
        batch_ids = list(
            {batch_id.blob_name.split("/")[1]
             for batch_id in files})
        batch_ids.sort(reverse=True)

        return batch_ids
Exemple #3
0
    def auth_and_call(*args: Any, **kwargs: Any) -> Any:
        """Authenticates the inbound request and delegates.

        Args:
            *args: args to the function
            **kwargs: keyword args to the function

        Returns:
            The output of the function, if successfully authenticated.
            An error or redirect response, otherwise.
        """
        if in_development():
            # Bypass GAE auth check in development.
            return func(*args, **kwargs)

        is_cron = request.headers.get("X-Appengine-Cron")
        is_task = request.headers.get("X-AppEngine-QueueName")
        incoming_app_id = request.headers.get("X-Appengine-Inbound-Appid")
        jwt = request.headers.get("x-goog-iap-jwt-assertion")

        project_id = metadata.project_id()
        project_number = metadata.project_number()

        if is_cron:
            logging.info("Requester is one of our cron jobs, proceeding.")

        elif is_task:
            logging.info("Requester is the taskqueue, proceeding.")

        elif incoming_app_id:
            # Check whether this is an intra-app call from our GAE service
            logging.info("Requester authenticated as app-id: [%s].", incoming_app_id)

            if incoming_app_id == project_id:
                logging.info("Authenticated intra-app call, proceeding.")
            else:
                logging.info("App ID is [%s], not allowed - exiting.", incoming_app_id)
                return (
                    "Failed: Unauthorized external request.",
                    HTTPStatus.UNAUTHORIZED,
                )
        elif jwt:
            (
                user_id,
                user_email,
                error_str,
            ) = validate_jwt.validate_iap_jwt_from_app_engine(
                jwt, project_number, project_id
            )
            logging.info("Requester authenticated as [%s] ([%s]).", user_id, user_email)
            if error_str:
                logging.info("Error validating user credentials: [%s].", error_str)
                return ("Error: %s" % error_str, HTTPStatus.UNAUTHORIZED)
        else:
            return ("Failed: Unauthorized external request.", HTTPStatus.UNAUTHORIZED)

        # If we made it this far, client is authorized - run the decorated func
        return func(*args, **kwargs)
Exemple #4
0
def runtime_env_vars() -> Tuple[str, HTTPStatus]:
    if in_development():
        env_string = "development"
    elif in_gcp_staging():
        env_string = "staging"
    elif in_gcp_production():
        env_string = "production"
    else:
        env_string = "unknown"
    return f'window.RUNTIME_GCP_ENVIRONMENT="{env_string}";', HTTPStatus.OK
Exemple #5
0
def set_headers(response: Response) -> Response:
    if not in_development():
        response.headers[
            "Strict-Transport-Security"] = "max-age=63072000"  # max age of 2 years
    response.headers["Content-Security-Policy"] = "frame-ancestors 'none'"
    response.headers["X-Frame-Options"] = "DENY"

    # Recidiviz-specific version header
    response.headers["X-Recidiviz-Current-Version"] = os.getenv(
        "CURRENT_GIT_SHA", "")

    return response
Exemple #6
0
 def _fetch_etl_view_ids() -> Tuple[str, HTTPStatus]:
     override_project_id: Optional[str] = None
     if in_development():
         override_project_id = GCP_PROJECT_STAGING
     return (
         jsonify(
             [builder.view_id for builder in CASE_TRIAGE_EXPORTED_VIEW_BUILDERS]
             + list(
                 get_importable_csvs(override_project_id=override_project_id).keys()
             )
         ),
         HTTPStatus.OK,
     )
Exemple #7
0
def get_local_file(file_path: GcsfsFilePath) -> str:
    """
    Helper function for supporting local development flows.
    When in development environments, we fetch file contents from `recidiviz/case_triage/local/gcs`
    In Google Cloud environments, we delegate to Cloud Storage
    """

    if in_development():
        return Path(os.path.join(local_path, "gcs",
                                 file_path.abs_path())).read_text()

    gcs_fs = GcsfsFactory.build()
    return gcs_fs.download_as_string(file_path)
Exemple #8
0
    def start_timers(self) -> None:
        """Starts store refresh timers for all stores that are a subclass of the AdminPanelStore class."""
        if in_gcp() or in_development():
            stores_with_timers = [
                self.ingest_metadata_store,
                self.validation_metadata_store,
                self.ingest_data_freshness_store,
                self.validation_status_store,
            ]

            for store in stores_with_timers:
                RepeatedTimer(15 * 60,
                              store.recalculate_store,
                              run_immediately=True).start()
Exemple #9
0
def get_local_secret(secret_name: str) -> Optional[str]:
    """
    Helper function for supporting local development flows.
    When in development environments, we fetch file contents from `recidiviz/case_triage/local/gsm`
    In Google Cloud environments, we delegate to Secrets Manager
    """
    if in_development():
        try:
            return Path(os.path.join(local_path, "gsm",
                                     secret_name)).read_text()
        except OSError:
            logging.error("Couldn't locate secret %s", secret_name)
            return None

    return get_secret(secret_name)
    def _get_operations_db_metadata(
            state_code: StateCode,
            ingest_db_name: str) -> Dict[str, Union[int, Optional[datetime]]]:
        """Returns the following dictionary with information about the operations database for the state:
        {
            unprocessedFilesRaw: <int>
            unprocessedFilesIngestView: <int>
            dateOfEarliestUnprocessedIngestView: <datetime>
        }

        If running locally, this does not hit the live DB instance and only returns fake data.
        """
        if in_development():
            return {
                "unprocessedFilesRaw": -1,
                "unprocessedFilesIngestView": -2,
                "dateOfEarliestUnprocessedIngestView": datetime(2021, 4, 28),
            }

        file_metadata_manager = PostgresDirectIngestFileMetadataManager(
            region_code=state_code.value,
            ingest_database_name=ingest_db_name,
        )

        try:
            # Raw files are processed in the primary instance, not secondary
            num_unprocessed_raw_files = (
                file_metadata_manager.get_num_unprocessed_raw_files())
        except DirectIngestInstanceError as _:
            num_unprocessed_raw_files = 0

        return {
            "unprocessedFilesRaw":
            num_unprocessed_raw_files,
            "unprocessedFilesIngestView":
            file_metadata_manager.get_num_unprocessed_ingest_files(),
            "dateOfEarliestUnprocessedIngestView":
            file_metadata_manager.get_date_of_earliest_unprocessed_ingest_file(
            ),
        }
Exemple #11
0
from recidiviz.utils.environment import in_development, in_test
from recidiviz.utils.flask_exception import FlaskException
from recidiviz.utils.timer import RepeatedTimer

# Flask setup
static_folder = os.path.abspath(
    os.path.join(
        os.path.dirname(os.path.realpath(__file__)),
        "../../frontends/case-triage/build/",
    ))

app = Flask(__name__, static_folder=static_folder)
app.secret_key = get_local_secret("case_triage_secret_key")
CSRFProtect(app)

if in_development():
    db_url = local_postgres_helpers.postgres_db_url_from_env_vars()
else:
    db_url = SQLAlchemyEngineManager.get_server_postgres_instance_url(
        schema_type=SchemaType.CASE_TRIAGE)
    app.config["SESSION_COOKIE_HTTPONLY"] = True
    app.config["SESSION_COOKIE_SECURE"] = True
    app.config["SESSION_COOKIE_SAMESITE"] = "Strict"

setup_scoped_sessions(app, db_url)


# Auth setup
def on_successful_authorization(_payload: Dict[str, str], token: str) -> None:
    """
    Memoize the user's info (email_address, picture, etc) into our session
Exemple #12
0
 def __init__(self) -> None:
     if in_development():
         with local_project_id_override(GCP_PROJECT_STAGING):
             self._initialize_stores()
     elif in_gcp():
         self._initialize_stores()
Exemple #13
0
    def _generate_emails(state_code_str: str) -> Tuple[str, HTTPStatus]:
        try:
            data = request.json
            state_code = StateCode(state_code_str)
            if state_code not in EMAIL_STATE_CODES:
                raise ValueError("State code is invalid for PO monthly reports")
            # TODO(#7790): Support more email types.
            report_type = ReportType(data.get("reportType"))
            if report_type != ReportType.POMonthlyReport:
                raise ValueError(f"{report_type.value} is not a valid ReportType")
            test_address = data.get("testAddress")
            region_code = data.get("regionCode")
            message_body_override = data.get("messageBodyOverride")
            email_allowlist = data.get("emailAllowlist")

            validate_email_address(test_address)

            if email_allowlist is not None:
                for recipient_email in email_allowlist:
                    validate_email_address(recipient_email)

        except (ValueError, JSONDecodeError) as error:
            logging.error(error)
            return str(error), HTTPStatus.BAD_REQUEST

        if test_address == "":
            test_address = None
        if region_code not in REGION_CODES:
            region_code = None

        try:
            batch_id = generate_batch_id()
            if in_development():
                with local_project_id_override(GCP_PROJECT_STAGING):
                    result: MultiRequestResult[str, str] = data_retrieval.start(
                        state_code=state_code,
                        report_type=report_type,
                        batch_id=batch_id,
                        test_address=test_address,
                        region_code=region_code,
                        email_allowlist=email_allowlist,
                        message_body_override=message_body_override,
                    )
            else:
                result = data_retrieval.start(
                    state_code=state_code,
                    report_type=report_type,
                    batch_id=batch_id,
                    test_address=test_address,
                    region_code=region_code,
                    email_allowlist=email_allowlist,
                    message_body_override=message_body_override,
                )
        except InvalidRegionCodeException:
            return "Invalid region code provided", HTTPStatus.BAD_REQUEST

        new_batch_text = f"New batch started for {state_code} and {report_type}. Batch id = {batch_id}."
        test_address_text = (
            f"Emails generated for test address: {test_address}" if test_address else ""
        )
        counts_text = f"Successfully generated {len(result.successes)} email(s)"
        success_text = f"{new_batch_text} {test_address_text} {counts_text}."
        if result.failures and not result.successes:
            return (
                f"{success_text}"
                f" Failed to generate all emails. Retry the request again."
            ), HTTPStatus.INTERNAL_SERVER_ERROR
        if result.failures:
            return (
                f"{success_text}"
                f" Failed to generate {len(result.failures)} email(s): {', '.join(result.failures)}"
            ), HTTPStatus.MULTI_STATUS

        return (
            jsonify(
                {
                    "batchId": batch_id,
                    "statusText": f"{success_text}",
                }
            ),
            HTTPStatus.OK,
        )