def clear_directory(cls, **kwargs):
     kwargs["users"] = kwargs.get(
         'users', []) + [*Config.get_admin_emails()] + cls.saved_users
     kwargs["groups"] = kwargs.get('groups', []) + cls.saved_groups
     kwargs["roles"] = kwargs.get('roles', []) + cls.saved_roles
     kwargs["resources"] = kwargs.get('resources', []) + cls.saved_resources
     clear_cd(Config.get_directory(), **kwargs)
Example #2
0
def _configure_logging(**kwargs):
    root_logger = logging.root
    global _logging_configured, _debug, silence_debug_loggers, _json_logs
    if _logging_configured:
        root_logger.info("Logging was already configured in this interpreter process. The currently "
                         "registered handlers, formatters, filters and log levels will be left as is.")
    else:
        root_logger.setLevel(logging.WARNING)
        if len(root_logger.handlers) == 0:
            logging.basicConfig(**kwargs)
        else:
            # If this happens, the process can likely proceed but the underlying issue needs to be investigated. Some
            # module isn't playing nicely and configured logging before we had a chance to do so. The backtrace
            # included in the log message may look scary but it should aid in finding the culprit.
            root_logger.warning("It appears that logging was already configured in this interpreter process. "
                                "Currently registered handlers, formatters and filters will be left as is.",
                                stack_info=True)
        if _json_logs:
            for handler in root_logger.handlers:
                formatter = FUSJsonFormatter(LOG_FORMAT)
                handler.setFormatter(formatter)
        if Config.log_level() == 0:
            _debug = False
            root_logger.setLevel(logging.WARN)
        elif Config.log_level() == 1:
            root_logger.setLevel(logging.INFO)
        elif Config.log_level() > 1:
            root_logger.setLevel(logging.DEBUG)
            for logger_name in silence_debug_loggers:
                logging.getLogger(logger_name).setLevel(logging.INFO)
        _logging_configured = True
Example #3
0
def authorize():
    query_params = request.args.copy() if request.args else {}
    openid_provider = Config.get_openid_provider()
    query_params["openid_provider"] = openid_provider
    query_params['response_type'] = "code"
    client_id = query_params.get("client_id")
    client_id = client_id if client_id != 'None' else None
    if client_id:
        query_params['audience'] = Config.get_audience()
        auth_params = query_params
    else:
        state = base64.b64encode(json.dumps(query_params).encode()).decode()
        # TODO: set random state
        oauth2_config = Config.get_oauth2_config()
        auth_params = dict(
            client_id=oauth2_config[openid_provider]["client_id"],
            response_type="code",
            scope="openid email profile",
            redirect_uri=oauth2_config[openid_provider]["redirect_uri"],
            state=state,
            audience=Config.get_audience(),
            prompt=query_params.get('prompt')
            if query_params.get('prompt') == 'none' else 'login')

    dest = furl(get_openid_config(openid_provider)["authorization_endpoint"],
                query_params=auth_params)
    return ConnexionResponse(status_code=requests.codes.found,
                             headers=dict(Location=dest.url))
Example #4
0
def logout():
    oauth2_config = Config.get_oauth2_config()
    openid_provider = Config.get_openid_provider()
    query_params = getattr(Config.app.current_request, 'query_params')
    client_id = oauth2_config[openid_provider][
        "client_id"] if not query_params else query_params.get('client_id')
    url = furl(f"https://{openid_provider}/v2/logout",
               query_params=dict(client_id=client_id)).url
    return ConnexionResponse(status_code=requests.codes.ok,
                             headers=dict(Location=url))
Example #5
0
def get_resource_authz_parameters(user: str, resources: Union[List[str], str]):
    """

    Get all policy ids, and send them to lookup policy, group them by policy type.

    :param user:
    :param resource:
    :return:
    """
    policies = []
    _user = User(user)
    try:
        groups = _user.groups
    except cd_client.exceptions.ResourceNotFoundException:
        _user = User.provision_user(user)
        groups = _user.groups

    # Only support a single resource for now
    resource = resources[0] if isinstance(resources, list) else resources
    r_type, r_id, *_ = resource.split(':')[-1].split('/')
    if r_type in ResourceType.get_types():
        r_id = ResourceId(r_type, r_id)
        resource_policies = r_id.check_access(
            [_user] + [Group(object_ref=g) for g in groups])
        if not resource_policies:
            raise ResourceNotFound("ResourceNotFound")
        policies.extend(resource_policies)
    policies.extend(list(_user.get_policy_ids()))
    authz_params = Config.get_directory().get_policies(policies)
    if authz_params.get('ResourcePolicy'):
        authz_params['ResourcePolicy'] = combine(
            [i['policy_document'] for i in authz_params.get('ResourcePolicy')])
    return authz_params
Example #6
0
def serve_oauth_token():
    """
    Part of OIDC
    """
    # TODO: client id/secret mgmt
    openid_provider = Config.get_openid_provider()
    openid_config = get_openid_config(openid_provider)
    return proxy_response(openid_config["token_endpoint"])
 def tearDownClass(cls):
     cls.clear_directory()
     if not is_integration():
         directory_arn = Config.get_directory()._dir_arn
         schema_arn = get_published_schema_from_directory(directory_arn)
         cleanup_directory(directory_arn)
         cleanup_schema(f"{schema_arn}/0")
         if old_directory_name:
             os.environ["FUSILLADE_DIR"] = old_directory_name
Example #8
0
def serve_openid_config():
    """
    Part of OIDC
    """
    auth_host = request.headers['host']
    if auth_host != os.environ["API_DOMAIN_NAME"]:
        raise FusilladeHTTPException(
            status=400,
            title="Bad Request",
            detail=
            f"host: {auth_host}, is not supported. host must be {os.environ['API_DOMAIN_NAME']}."
        )
    openid_config = get_openid_config(Config.get_openid_provider()).copy()
    openid_config.update(**proxied_endpoints)
    return ConnexionResponse(body=openid_config, status_code=requests.codes.ok)
Example #9
0
def get_openid_config(openid_provider: str) -> dict:
    """

    :param openid_provider: the openid provider's domain.
    :return: the openid configuration
    """
    if openid_provider.endswith(gserviceaccount_domain):
        openid_provider = 'accounts.google.com'
    else:
        openid_provider = Config.get_openid_provider()
    res = requests.get(
        f"https://{openid_provider}/.well-known/openid-configuration")
    res.raise_for_status()
    openid_config[openid_provider] = res.json()
    return openid_config[openid_provider]
Example #10
0
def verify_jwt(token: str) -> typing.Optional[typing.Mapping]:
    """
    Verify the JWT from the request. This is function is referenced in fusillade-api.yml
    securitySchemes.BearerAuth.x-bearerInfoFunc. It's used by connexion to authorize api endpoints that use BearAuth
    securitySchema.

    :param token: the Authorization header in the request.
    :return: Decoded and verified token.
    """
    try:
        unverified_token = jwt.decode(token, verify=False)
        token_header = jwt.get_unverified_header(token)
    except jwt.DecodeError:
        logger.debug({"msg": "Failed to decode token."}, exc_info=True)
        raise FusilladeHTTPException(401, 'Unauthorized',
                                     'Failed to decode token.')

    issuer = unverified_token['iss']
    public_key = get_public_key(issuer, token_header["kid"])
    try:
        verified_tok = jwt.decode(
            token,
            key=public_key,
            issuer=issuer,
            audience=Config.get_audience(),
            algorithms=allowed_algorithms,
        )
        logger.debug({"message": "Token Validated"})
    except jwt.PyJWTError as ex:  # type: ignore
        logger.debug({"message": "Failed to validate token."}, exc_info=True)
        raise FusilladeHTTPException(401, 'Unauthorized',
                                     'Authorization token is invalid') from ex
    tokeninfo_endpoint = [
        i for i in verified_tok['aud']
        if i.endswith('userinfo') or i.endswith('tokeninfo')
    ]
    if tokeninfo_endpoint:
        # Use the OIDC tokeninfo endpoint to get info about the user.
        return get_tokeninfo(tokeninfo_endpoint[0], token)
    else:
        # If No OIDC tokeninfo endpoint is present then this is a google service account and there is no info to
        # retrieve
        return verified_tok
Example #11
0
    def create_connexion_app(self):
        app = FlaskApp('fusillade')
        # The Flask/Connection app's logger has its own multi-line formatter and configuration. Rather than suppressing
        # it we let it do its thing, give it a special name and only enable it if Fusillade_DEBUG > 1.
        # Most of the Fusillade web app's logging is done through the FusilladeChaliceApp.app logger not the Flask
        # app's logger.
        app.app.logger_name = 'fus.api'
        debug = Config.log_level() > 1
        app.app.debug = debug
        app.app.logger.info('Flask debug is %s.',
                            'enabled' if debug else 'disabled')

        resolver = RestyResolver("fusillade.api",
                                 collection_endpoint_name="list")
        self.connexion_apis.append(
            app.add_api(self.swagger_spec_path,
                        resolver=resolver,
                        validate_responses=True,
                        arguments=os.environ,
                        options={"swagger_path": self.swagger_spec_path}))
        self.connexion_apis.append(
            app.add_api(self.swagger_internal_spec_path,
                        validate_responses=True))
        return app
def setup_clouddirectory():
    schema_name = Config.get_schema_name()
    schema_arn = publish_schema(schema_name, **get_schema_version())
    admins = Config.get_admin_emails()
    directory_name = Config.get_directory_name()
    return create_directory(directory_name, schema_arn, admins)
Example #13
0
def cb():
    """
    Part of OIDC
    """
    query_params = request.args
    state = json.loads(base64.b64decode(query_params["state"]))
    openid_provider = Config.get_openid_provider()
    openid_config = get_openid_config(openid_provider)
    token_endpoint = openid_config["token_endpoint"]

    client_id = state.get("client_id")
    client_id = client_id if client_id != 'None' else None

    redirect_uri = state.get("redirect_uri")
    redirect_uri = redirect_uri if redirect_uri != 'None' else None
    # TODO need to parse the error message if login is required and redirect to login
    if query_params.get('error'):
        if redirect_uri:
            ConnexionResponse(status_code=requests.codes.unauthorized,
                              headers=dict(Location=redirect_uri))
        else:
            return {
                "query": query_params,
            }

    elif redirect_uri and client_id:
        # OIDC proxy flow
        resp_params = dict(code=query_params["code"], state=state.get("state"))
        dest = furl(state["redirect_uri"]).add(resp_params).url
        return ConnexionResponse(status_code=requests.codes.found,
                                 headers=dict(Location=dest))
    else:
        # Simple flow
        oauth2_config = Config.get_oauth2_config()
        res = requests.post(
            token_endpoint,
            dict(code=query_params["code"],
                 client_id=oauth2_config[openid_provider]["client_id"],
                 client_secret=oauth2_config[openid_provider]["client_secret"],
                 redirect_uri=oauth2_config[openid_provider]["redirect_uri"],
                 grant_type="authorization_code"))
        try:
            res.raise_for_status()
        except requests.exceptions.HTTPError:
            return make_response(res.text, res.status_code,
                                 res.headers.items())
        token_header = jwt.get_unverified_header(res.json()["id_token"])
        public_key = get_public_key(openid_provider, token_header["kid"])
        tok = jwt.decode(res.json()["id_token"],
                         key=public_key,
                         audience=oauth2_config[openid_provider]["client_id"])
        assert tok["email_verified"]
        if redirect_uri:
            # Simple flow - redirect with QS
            resp_params = dict(res.json(),
                               decoded_token=json.dumps(tok),
                               state=state.get("state"))
            dest = furl(state["redirect_uri"]).add(resp_params).url
            return ConnexionResponse(status_code=requests.codes.found,
                                     headers=dict(Location=dest))
        else:
            # Simple flow - JSON
            return {
                "headers": dict(request.headers),
                "query": query_params,
                "token_endpoint": token_endpoint,
                "res": res.json(),
                "tok": tok,
            }
Example #14
0
def revoke():
    """
    Part of OIDC
    """
    openid_config = get_openid_config(Config.get_openid_provider())
    return proxy_response(openid_config["revocation_endpoint"])
Example #15
0
def serve_jwks_json():
    """
    Part of OIDC
    """
    openid_config = get_openid_config(Config.get_openid_provider())
    return proxy_response(openid_config["jwks_uri"])