def make_google_blueprint(): """ Return: flask.Blueprint: the blueprint used for ``/google`` endpoints """ blueprint = flask.Blueprint("google", __name__) blueprint_api = RestfulApi(blueprint) blueprint_api.add_resource(GoogleServiceAccountRoot, "/service_accounts", strict_slashes=False) blueprint_api.add_resource(GoogleBillingAccount, "/billing_projects", strict_slashes=False) blueprint_api.add_resource( GoogleServiceAccountDryRun, "/service_accounts/_dry_run/<id_>", strict_slashes=False, ) blueprint_api.add_resource(GoogleServiceAccount, "/service_accounts/<id_>", strict_slashes=False) return blueprint
def make_link_blueprint(): """ Return: flask.Blueprint: the blueprint used for ``/link`` endpoints """ blueprint = flask.Blueprint("link", __name__) blueprint_api = RestfulApi(blueprint) blueprint_api.add_resource(GoogleLinkRedirect, "/google", strict_slashes=False) blueprint_api.add_resource(GoogleCallback, "/google/callback", strict_slashes=False) return blueprint
def make_login_blueprint(app): """ Args: app (flask.Flask): a flask app (with `app.config` set up) Return: flask.Blueprint: the blueprint used for ``/login`` endpoints Raises: ValueError: if app is not amenably configured """ blueprint = flask.Blueprint("login", __name__) blueprint_api = RestfulApi(blueprint) @blueprint.route("", methods=["GET"]) def default_login(): """ The default root login route. """ # default login option if "DEFAULT_LOGIN_IDP" in config: default_idp = config["DEFAULT_LOGIN_IDP"] elif "default" in config.get("ENABLED_IDENTITY_PROVIDERS", {}): # fall back on ENABLED_IDENTITY_PROVIDERS.default default_idp = config["ENABLED_IDENTITY_PROVIDERS"]["default"] else: logger.warn("DEFAULT_LOGIN_IDP not configured") default_idp = None # other login options if config["LOGIN_OPTIONS"]: login_options = config["LOGIN_OPTIONS"] elif "providers" in config.get("ENABLED_IDENTITY_PROVIDERS", {}): # fall back on "providers" and convert to "login_options" format enabled_providers = config["ENABLED_IDENTITY_PROVIDERS"][ "providers"] login_options = [{ "name": details.get("name"), "idp": idp, "desc": details.get("desc"), "secondary": details.get("secondary"), } for idp, details in enabled_providers.items()] else: logger.warn("LOGIN_OPTIONS not configured or empty") login_options = [] def absolute_login_url(provider_id, fence_idp=None, shib_idp=None): """ Args: provider_id (str): provider to log in with; an IDP_URL_MAP key. fence_idp (str, optional): if provider_id is "fence" (multi-tenant Fence setup), fence_idp can be any of the providers supported by the other Fence. If not specified, will default to NIH login. shib_idp (str, optional): if provider_id is "fence" and fence_idp is "shibboleth", shib_idp can be any Shibboleth/ InCommon provider. If not specified, will default to NIH login. Returns: str: login URL for this provider, including extra query parameters if fence_idp and/or shib_idp are specified. """ try: base_url = config["BASE_URL"].rstrip("/") login_url = base_url + "/login/{}".format( IDP_URL_MAP[provider_id]) except KeyError as e: raise InternalError( "identity provider misconfigured: {}".format(str(e))) params = {} if fence_idp: params["idp"] = fence_idp if shib_idp: params["shib_idp"] = shib_idp login_url = add_params_to_uri(login_url, params) return login_url def provider_info(login_details): """ Args: login_details (dict): { name, desc, idp, fence_idp, shib_idps, secondary } - "idp": a configured provider. Multiple options can be configured with the same idp. - if provider_id is "fence", "fence_idp" can be any of the providers supported by the other Fence. If not specified, will default to NIH login. - if provider_id is "fence" and fence_idp is "shibboleth", a list of "shib_idps" can be configured for InCommon login. If not specified, will default to NIH login. - Optional parameters: "desc" (description) and "secondary" (boolean - can be used by the frontend to display secondary buttons differently). Returns: dict: { name, desc, idp, urls, secondary } - urls: list of { name, url } dictionaries """ info = { # "id" deprecated, replaced by "idp" "id": login_details["idp"], "idp": login_details["idp"], "name": login_details["name"], # "url" deprecated, replaced by "urls" "url": absolute_login_url(login_details["idp"]), "desc": login_details.get("desc", None), "secondary": login_details.get("secondary", False), } # for Fence multi-tenant login fence_idp = None if login_details["idp"] == "fence": fence_idp = login_details.get("fence_idp") # handle Shibboleth IDPs: InCommon login can either be configured # directly in this Fence, or through multi-tenant Fence if (login_details["idp"] == "shibboleth" or fence_idp == "shibboleth") and "shib_idps" in login_details: # get list of all available shib IDPs if not hasattr(app, "all_shib_idps"): app.all_shib_idps = get_all_shib_idps() requested_shib_idps = login_details["shib_idps"] if requested_shib_idps == "*": shib_idps = app.all_shib_idps elif isinstance(requested_shib_idps, list): # get the display names for each requested shib IDP shib_idps = [] for requested_shib_idp in requested_shib_idps: shib_idp = next( (available_shib_idp for available_shib_idp in app.all_shib_idps if available_shib_idp["idp"] == requested_shib_idp ), None, ) if not shib_idp: raise InternalError( 'Requested shib_idp "{}" does not exist'. format(requested_shib_idp)) shib_idps.append(shib_idp) else: raise InternalError( 'fence provider misconfigured: "shib_idps" must be a list or "*", got {}' .format(requested_shib_idps)) info["urls"] = [{ "name": shib_idp["name"], "url": absolute_login_url(login_details["idp"], fence_idp, shib_idp["idp"]), } for shib_idp in shib_idps] # non-Shibboleth provider else: info["urls"] = [{ "name": login_details["name"], "url": absolute_login_url(login_details["idp"], fence_idp), }] return info try: all_provider_info = [ provider_info(login_details) for login_details in login_options ] except KeyError as e: raise InternalError("login options misconfigured: {}".format(e)) # if several login_options are defined for this default IDP, will # default to the first one: default_provider_info = next( (info for info in all_provider_info if info["idp"] == default_idp), None) if not default_provider_info: raise InternalError("default provider misconfigured") return flask.jsonify({ "default_provider": default_provider_info, "providers": all_provider_info }) # Add identity provider login routes for IDPs enabled in the config. configured_idps = config["OPENID_CONNECT"].keys() if "fence" in configured_idps: blueprint_api.add_resource(FenceLogin, "/fence", strict_slashes=False) blueprint_api.add_resource(FenceCallback, "/fence/login", strict_slashes=False) if "google" in configured_idps: blueprint_api.add_resource(GoogleLogin, "/google", strict_slashes=False) blueprint_api.add_resource(GoogleCallback, "/google/login", strict_slashes=False) if "orcid" in configured_idps: blueprint_api.add_resource(ORCIDLogin, "/orcid", strict_slashes=False) blueprint_api.add_resource(ORCIDCallback, "/orcid/login", strict_slashes=False) if "synapse" in configured_idps: blueprint_api.add_resource(SynapseLogin, "/synapse", strict_slashes=False) blueprint_api.add_resource(SynapseCallback, "/synapse/login", strict_slashes=False) if "microsoft" in configured_idps: blueprint_api.add_resource(MicrosoftLogin, "/microsoft", strict_slashes=False) blueprint_api.add_resource(MicrosoftCallback, "/microsoft/login", strict_slashes=False) if "shibboleth" in configured_idps: blueprint_api.add_resource(ShibbolethLogin, "/shib", strict_slashes=False) blueprint_api.add_resource(ShibbolethCallback, "/shib/login", strict_slashes=False) return blueprint
def make_creds_blueprint(): blueprint = flask.Blueprint("credentials", __name__) blueprint_api = RestfulApi(blueprint) blueprint_api.add_resource(GoogleCredentialsList, "/google", strict_slashes=False) blueprint_api.add_resource(GoogleCredentials, "/google/<access_key>", strict_slashes=False) # TODO: REMOVE DEPRECATED /cdis ENDPOINTS # temporarily leaving them here to give time for users to make switch blueprint_api.add_resource(ApiKeyList, "/api", "/cdis", strict_slashes=False) blueprint_api.add_resource(ApiKey, "/api/<access_key>", "/cdis/<access_key>", strict_slashes=False) blueprint_api.add_resource(AccessKey, "/api/access_token", "/cdis/access_token", strict_slashes=False) blueprint_api.add_resource(OtherCredentialsList, "/<provider>", strict_slashes=False) blueprint_api.add_resource(OtherCredentials, "/<provider>/<access_key>", strict_slashes=False) @blueprint.route("/", methods=["GET"]) @require_auth_header({"credentials"}) def list_sources(): """ List different resources user can have credentials **Example:** .. code-block:: http GET /credentials/ HTTP/1.1 Content-Type: application/json Accept: application/json .. code-block:: JavaScript { "/api": "access to CDIS APIs", "/ceph": "access to Ceph storage", "/cleversafe": "access to cleversafe storage", "/aws-s3", "access to AWS S3 storage" "/google", "access to Google Cloud storage" } """ services = set([ info.get("backend") for _, info in config["STORAGE_CREDENTIALS"].items() if info.get("backend") ]) return flask.jsonify( get_endpoints_descriptions(services, current_session)) return blueprint
def make_login_blueprint(): """ Return: flask.Blueprint: the blueprint used for ``/login`` endpoints Raises: ValueError: if app is not amenably configured """ blueprint = flask.Blueprint("login", __name__) blueprint_api = RestfulApi(blueprint, decorators=[enable_audit_logging]) @blueprint.route("", methods=["GET"]) def default_login(): """ The default root login route. """ default_provider_info, all_provider_info = get_login_providers_info() return flask.jsonify({ "default_provider": default_provider_info, "providers": all_provider_info }) # Add identity provider login routes for IDPs enabled in the config. configured_idps = config.get("OPENID_CONNECT", {}) for idp in set(configured_idps.keys()): logger.info(f"Setting up login blueprint for {idp}") custom_callback_endpoint = None if idp == "fence": login_class = FenceLogin callback_class = FenceCallback elif idp == "google": login_class = GoogleLogin callback_class = GoogleCallback elif idp == "orcid": login_class = ORCIDLogin callback_class = ORCIDCallback elif idp == "ras": login_class = RASLogin callback_class = RASCallback # note that the callback endpoint is "/ras/callback", not "/ras/login" like other IDPs custom_callback_endpoint = f"/{get_idp_route_name(idp)}/callback" elif idp == "synapse": login_class = SynapseLogin callback_class = SynapseCallback elif idp == "microsoft": login_class = MicrosoftLogin callback_class = MicrosoftCallback elif idp == "okta": login_class = OktaLogin callback_class = OktaCallback elif idp == "cognito": login_class = CognitoLogin callback_class = CognitoCallback elif idp == "shibboleth": login_class = ShibbolethLogin callback_class = ShibbolethCallback elif idp == "cilogon": login_class = CilogonLogin callback_class = CilogonCallback else: # generic OIDC implementation login_class = createLoginClass(idp.lower()) callback_class = createCallbackClass(idp.lower(), configured_idps[idp]) # create IDP routes blueprint_api.add_resource( login_class, f"/{get_idp_route_name(idp)}", strict_slashes=False, endpoint=f"{get_idp_route_name(idp)}_login", ) blueprint_api.add_resource( callback_class, custom_callback_endpoint or f"/{get_idp_route_name(idp)}/login", strict_slashes=False, endpoint=f"{get_idp_route_name(idp)}_callback", ) return blueprint
def make_login_blueprint(app): """ Args: app (flask.Flask): a flask app (with `app.config` set up) Return: flask.Blueprint: the blueprint used for ``/login`` endpoints Raises: ValueError: if app is not amenably configured """ try: default_idp = config["ENABLED_IDENTITY_PROVIDERS"]["default"] idps = config["ENABLED_IDENTITY_PROVIDERS"]["providers"] except KeyError as e: logger.warn( "app not configured correctly with ENABLED_IDENTITY_PROVIDERS:" " missing {}".format(str(e)) ) default_idp = None idps = {} # check if google is configured as a client. we will at least need a # a callback if it is google_client_exists = ( "OPENID_CONNECT" in config and "google" in config["OPENID_CONNECT"] ) blueprint = flask.Blueprint("login", __name__) blueprint_api = RestfulApi(blueprint) @blueprint.route("", methods=["GET"]) def default_login(): """ The default root login route. """ def absolute_login_url(provider_id): base_url = config["BASE_URL"].rstrip("/") return base_url + "/login/{}".format(IDP_URL_MAP[provider_id]) def provider_info(idp_id): if not idp_id: return { "id": None, "name": None, "url": None, "desc": None, "secondary": False, } return { "id": idp_id, "name": idps[idp_id]["name"], "url": absolute_login_url(idp_id), "desc": idps[idp_id].get("desc", None), "secondary": idps[idp_id].get("secondary", False), } try: all_provider_info = [provider_info(idp_id) for idp_id in list(idps.keys())] default_provider_info = provider_info(default_idp) except KeyError as e: raise InternalError("identity providers misconfigured: {}".format(str(e))) return flask.jsonify( {"default_provider": default_provider_info, "providers": all_provider_info} ) # Add identity provider login routes for IDPs enabled in the config. if "fence" in idps: blueprint_api.add_resource(FenceLogin, "/fence", strict_slashes=False) blueprint_api.add_resource(FenceCallback, "/fence/login", strict_slashes=False) if "google" in idps: blueprint_api.add_resource(GoogleLogin, "/google", strict_slashes=False) # we can use Google Client and callback here without the login endpoint # if Google is configured as a client but not in the idps if "google" in idps or google_client_exists: blueprint_api.add_resource( GoogleCallback, "/google/login", strict_slashes=False ) if "orcid" in idps: blueprint_api.add_resource(ORCIDLogin, "/orcid", strict_slashes=False) blueprint_api.add_resource(ORCIDCallback, "/orcid/login", strict_slashes=False) if "synapse" in idps: blueprint_api.add_resource(SynapseLogin, "/synapse", strict_slashes=False) blueprint_api.add_resource( SynapseCallback, "/synapse/login", strict_slashes=False ) if "microsoft" in idps: blueprint_api.add_resource(MicrosoftLogin, "/microsoft", strict_slashes=False) blueprint_api.add_resource( MicrosoftCallback, "/microsoft/login", strict_slashes=False ) if "shibboleth" in idps: blueprint_api.add_resource(ShibbolethLogin, "/shib", strict_slashes=False) blueprint_api.add_resource( ShibbolethCallback, "/shib/login", strict_slashes=False ) return blueprint
def make_login_blueprint(app): """ Args: app (flask.Flask): a flask app (with `app.config` set up) Return: flask.Blueprint: the blueprint used for ``/login`` endpoints Raises: ValueError: if app is not amenably configured """ try: default_idp = app.config["ENABLED_IDENTITY_PROVIDERS"]["default"] idps = app.config["ENABLED_IDENTITY_PROVIDERS"]["providers"] except KeyError as e: app.logger.warn( "app not configured correctly with ENABLED_IDENTITY_PROVIDERS:" " missing {}".format(str(e)) ) default_idp = None idps = {} # Mapping from IDP ID (what goes in ``fence/local_settings.py`` in # ``ENABLED_IDENTITY_PROVIDERS``) to the name in the URL on the blueprint # (see below). IDP_URL_MAP = {"fence": "fence", "google": "google", "shibboleth": "shib"} # check if google is configured as a client. we will at least need a # a callback if it is google_client_exists = ( "OPENID_CONNECT" in app.config and "google" in app.config["OPENID_CONNECT"] ) blueprint = flask.Blueprint("login", __name__) blueprint_api = RestfulApi(blueprint) @blueprint.route("", methods=["GET"]) def default_login(): """ The default root login route. """ def absolute_login_url(provider_id): base_url = flask.current_app.config["BASE_URL"].rstrip("/") return base_url + "/login/{}".format(IDP_URL_MAP[provider_id]) def provider_info(idp_id): if not idp_id: return {"id": None, "name": None, "url": None} return { "id": idp_id, "name": idps[idp_id]["name"], "url": absolute_login_url(idp_id), } try: all_provider_info = [provider_info(idp_id) for idp_id in idps.keys()] default_provider_info = provider_info(default_idp) except KeyError as e: raise InternalError("identity providers misconfigured: {}".format(str(e))) return flask.jsonify( {"default_provider": default_provider_info, "providers": all_provider_info} ) # Add identity provider login routes for IDPs enabled in the config. if "fence" in idps: blueprint_api.add_resource(FenceRedirect, "/fence", strict_slashes=False) blueprint_api.add_resource(FenceLogin, "/fence/login", strict_slashes=False) if "google" in idps: blueprint_api.add_resource(GoogleRedirect, "/google", strict_slashes=False) # we can use Google Client and callback here without the login endpoint # if Google is configured as a client but not in the idps if "google" in idps or google_client_exists: blueprint_api.add_resource(GoogleLogin, "/google/login", strict_slashes=False) if "shibboleth" in idps: blueprint_api.add_resource(ShibbolethLoginStart, "/shib", strict_slashes=False) blueprint_api.add_resource( ShibbolethLoginFinish, "/shib/login", strict_slashes=False ) return blueprint