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)
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
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))
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))
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
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
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)
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]
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
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)
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, }
def revoke(): """ Part of OIDC """ openid_config = get_openid_config(Config.get_openid_provider()) return proxy_response(openid_config["revocation_endpoint"])
def serve_jwks_json(): """ Part of OIDC """ openid_config = get_openid_config(Config.get_openid_provider()) return proxy_response(openid_config["jwks_uri"])