Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
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