def _generate_google_storage_signed_url(self, http_verb, resource_path, expiration_time, user_id, username): proxy_group_id = get_or_create_proxy_group_id() private_key, key_db_entry = get_or_create_primary_service_account_key( user_id=user_id, username=username, proxy_group_id=proxy_group_id) # Make sure the service account key expiration is later # than the expiration for the signed url. If it's not, we need to # provision a new service account key. # # NOTE: This should occur very rarely: only when the service account key # already exists and is very close to expiring. # # If our scheduled maintainence script removes the url-signing key # before the expiration of the url then the url will NOT work # (even though the url itself isn't expired) if key_db_entry and key_db_entry.expires < expiration_time: private_key = create_primary_service_account_key( user_id=user_id, username=username, proxy_group_id=proxy_group_id) final_url = cirrus.google_cloud.utils.get_signed_url( resource_path, http_verb, expiration_time, extension_headers=None, content_type="", md5_value="", service_account_creds=private_key, ) return final_url
def _extend_account_expiration(): user_id = current_token["sub"] google_email = get_users_linked_google_email(user_id) proxy_group = get_or_create_proxy_group_id() access_expiration = _force_update_user_google_account(user_id, google_email, proxy_group, _allow_new=False) return {"exp": access_expiration}, 200
def _generate_google_storage_signed_url( self, http_verb, resource_path, expiration_time, user_id, username, r_pays_project=None, ): proxy_group_id = get_or_create_proxy_group_id() private_key, key_db_entry = get_or_create_primary_service_account_key( user_id=user_id, username=username, proxy_group_id=proxy_group_id) # Make sure the service account key expiration is later # than the expiration for the signed url. If it's not, we need to # provision a new service account key. # # NOTE: This should occur very rarely: only when the service account key # already exists and is very close to expiring. # # If our scheduled maintainence script removes the url-signing key # before the expiration of the url then the url will NOT work # (even though the url itself isn't expired) if key_db_entry and key_db_entry.expires < expiration_time: private_key = create_primary_service_account_key( user_id=user_id, username=username, proxy_group_id=proxy_group_id) if config["ENABLE_AUTOMATIC_BILLING_PERMISSION_SIGNED_URLS"]: give_service_account_billing_access_if_necessary( private_key, r_pays_project, default_billing_project=config[ "BILLING_PROJECT_FOR_SIGNED_URLS"], ) # use configured project if it exists and no user project was given if config["BILLING_PROJECT_FOR_SIGNED_URLS"] and not r_pays_project: r_pays_project = config["BILLING_PROJECT_FOR_SIGNED_URLS"] final_url = cirrus.google_cloud.utils.get_signed_url( resource_path, http_verb, expiration_time, extension_headers=None, content_type="", md5_value="", service_account_creds=private_key, requester_pays_user_project=r_pays_project, ) return final_url
def post(self): """ Generate a keypair for user **Example:** .. code-block:: http POST /credentials/cdis/?expires_in=3600 HTTP/1.1 Content-Type: application/json Accept: application/json (JSON key in Google Credentials File format) .. code-block:: JavaScript { "type": "service_account", "project_id": "project-id", "private_key_id": "some_number", "private_key": "-----BEGIN PRIVATE KEY-----\n.... =\n-----END PRIVATE KEY-----\n", "client_email": "<api-name>[email protected]", "client_id": "...", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/...<api-name>api%40project-id.iam.gserviceaccount.com" } """ user_id = current_token["sub"] client_id = current_token.get("azp") or None proxy_group_id = get_or_create_proxy_group_id() username = current_token.get("context", {}).get("user", {}).get("name") r_pays_project = flask.request.args.get("userProject", None) key, service_account = create_google_access_key( client_id, user_id, username, proxy_group_id ) if config["ENABLE_AUTOMATIC_BILLING_PERMISSION_SA_CREDS"]: give_service_account_billing_access_if_necessary( key, r_pays_project, default_billing_project=config["BILLING_PROJECT_FOR_SA_CREDS"], ) if client_id is None: self.handle_user_service_account_creds(key, service_account) return flask.jsonify(key)
def _link_google_account(): provided_redirect = flask.request.args.get("redirect") if not provided_redirect: raise UserError({"error": "No redirect provided."}) user_id = current_token["sub"] google_email = get_users_linked_google_email(user_id) proxy_group = get_or_create_proxy_group_id() # Set session flag to signify that we're linking and not logging in # Save info needed for linking in session since we need to AuthN first flask.session["google_link"] = True flask.session["user_id"] = user_id flask.session["google_proxy_group_id"] = proxy_group flask.session["linked_google_email"] = google_email if not google_email: # save off provided redirect in session and initiate Google AuthN flask.session["redirect"] = provided_redirect flask.redirect_url = flask.current_app.google_client.get_auth_url() # Tell Google to let user select an account flask.redirect_url = append_query_params(flask.redirect_url, prompt="select_account") else: # double check that the token isn't stale by hitting db linked_email_in_db = get_linked_google_account_email(user_id) if linked_email_in_db: # skip Google AuthN, already linked, error redirect_with_errors = append_query_params( provided_redirect, error="g_acnt_link_error", error_description= "User already has a linked Google account.", ) flask.redirect_url = redirect_with_errors _clear_google_link_info_from_session() else: # TODO can we handle this error? redirect_with_errors = append_query_params( provided_redirect, error="g_acnt_link_error", error_description="Stale access token, please refresh.", ) flask.redirect_url = redirect_with_errors _clear_google_link_info_from_session() return flask.redirect(flask.redirect_url)
def get(self): """ List access keys for user **Example:** .. code-block:: http POST /credentials/apis/ HTTP/1.1 Content-Type: application/json Accept: application/json Info from Google API /serviceAccounts/<account>/keys endpoint TODO: In the future we should probably add in our expiration time, when we start monitoring and deleting after x amount of time .. code-block:: JavaScript { "access_keys": [ { "keyAlgorithm": enum(ServiceAccountKeyAlgorithm), "validBeforeTime": string, "name": string, "validAfterTime": string, }, ... ] } """ client_id = current_token.get("azp") or None user_id = current_token["sub"] username = current_token.get("context", {}).get("user", {}).get("name") with GoogleCloudManager() as g_cloud_manager: proxy_group_id = get_or_create_proxy_group_id() service_account = get_or_create_service_account( client_id=client_id, user_id=user_id, username=username, proxy_group_id=proxy_group_id, ) keys = g_cloud_manager.get_service_account_keys_info( service_account.google_unique_id) result = {"access_keys": keys} return flask.jsonify(result)
def _extend_account_expiration(): user_id = current_token["sub"] google_email = get_users_linked_google_email(user_id) proxy_group = get_or_create_proxy_group_id() # requested time (in seconds) during which the link will be valid requested_expires_in = get_valid_expiration_from_request() access_expiration = _force_update_user_google_account( user_id, google_email, proxy_group, _allow_new=False, requested_expires_in=requested_expires_in, ) return {"exp": access_expiration}, 200
def post(self): """ Force the creation of the User's Primary Google Service Account instead of relying on lazy creation at first time of Google Data Access. """ user_id = current_token["sub"] proxy_group_id = get_or_create_proxy_group_id() username = current_token.get("context", {}).get("user", {}).get("name") service_account_email = None # do the same thing signed URL creation is doing, but don't use the resulting # key, just extract the service account email sa_private_key, _ = get_or_create_primary_service_account_key( user_id=user_id, username=username, proxy_group_id=proxy_group_id ) service_account_email = sa_private_key.get("client_email") # NOTE: service_account_from_db.email is what gets populated in the UserInfo endpoint's # "primary_google_service_account" as well, so this remains consistent return flask.jsonify({"primary_google_service_account": service_account_email})
def _link_google_account(): provided_redirect = flask.request.args.get("redirect") # will raise UserError if invalid validate_redirect(provided_redirect) if not provided_redirect: raise UserError({"error": "No redirect provided."}) user_id = current_token["sub"] google_email = get_users_linked_google_email(user_id) proxy_group = get_or_create_proxy_group_id() # Set session flag to signify that we're linking and not logging in # Save info needed for linking in session since we need to AuthN first flask.session["google_link"] = True flask.session["user_id"] = user_id flask.session["google_proxy_group_id"] = proxy_group flask.session["linked_google_email"] = google_email if not google_email: # save off provided redirect in session and initiate Google AuthN flask.session["redirect"] = provided_redirect # requested time (in seconds) during which the link will be valid requested_expires_in = get_valid_expiration_from_request() if requested_expires_in: flask.session["google_link_expires_in"] = requested_expires_in # if we're mocking Google login, skip to callback if config.get("MOCK_GOOGLE_AUTH", False): flask.redirect_url = (config["BASE_URL"].strip("/") + "/link/google/callback?code=abc") response = flask.redirect(flask.redirect_url) # pass-through the authorization header. The user's username # MUST be a Google email for MOCK_GOOGLE_AUTH to actually link that # email correctly response.headers["Authorization"] = flask.request.headers.get( "Authorization") return response flask.redirect_url = flask.current_app.google_client.get_auth_url() # Tell Google to let user select an account flask.redirect_url = append_query_params(flask.redirect_url, prompt="select_account") else: # double check that the token isn't stale by hitting db linked_email_in_db = get_linked_google_account_email(user_id) if linked_email_in_db: # skip Google AuthN, already linked, error redirect_with_errors = append_query_params( provided_redirect, error="g_acnt_link_error", error_description= "User already has a linked Google account.", ) flask.redirect_url = redirect_with_errors _clear_google_link_info_from_session() else: # TODO can we handle this error? redirect_with_errors = append_query_params( provided_redirect, error="g_acnt_link_error", error_description="Stale access token, please refresh.", ) flask.redirect_url = redirect_with_errors _clear_google_link_info_from_session() return flask.redirect(flask.redirect_url)
def get(self): """ List access keys for user **Example:** .. code-block:: http POST /credentials/apis/ HTTP/1.1 Content-Type: application/json Accept: application/json Info from Google API /serviceAccounts/<account>/keys endpoint but get the expiration time from our DB .. code-block:: JavaScript { "access_keys": [ { "keyAlgorithm": enum(ServiceAccountKeyAlgorithm), "validBeforeTime": string, "name": string, "validAfterTime": string, }, ... ] } """ client_id = current_token.get("azp") or None user_id = current_token["sub"] username = current_token.get("context", {}).get("user", {}).get("name") with GoogleCloudManager() as g_cloud_manager: proxy_group_id = get_or_create_proxy_group_id() service_account = get_or_create_service_account( client_id=client_id, user_id=user_id, username=username, proxy_group_id=proxy_group_id, ) keys = g_cloud_manager.get_service_account_keys_info( service_account.email) # replace Google's expiration date by the one in our DB reg = re.compile( ".+\/keys\/(.+)") # get key_id from xx/keys/key_id for i, key in enumerate(keys): key_id = reg.findall(key["name"])[0] db_entry = ( current_session.query(GoogleServiceAccountKey).filter_by( service_account_id=service_account.id).filter_by( key_id=key_id).first()) if db_entry: # convert timestamp to date - use the same format as Google API exp_date = datetime.utcfromtimestamp( db_entry.expires).strftime("%Y-%m-%dT%H:%M:%SZ") key["validBeforeTime"] = exp_date # the key exists in Google but not in our DB. This should not # happen! Delete the key from Google else: keys.pop(i) logger.warning( "No GoogleServiceAccountKey entry was found in the fence database for service account name {} for key_id {}, which exists in Google. It will now be deleted from Google." .format(username, key_id)) with GoogleCloudManager() as g_cloud: g_cloud.delete_service_account_key( service_account.email, key_id) result = {"access_keys": keys} return flask.jsonify(result)