def is_service_up(self): pre_healthcheck.send(sender=self, request=self.request) status = not (general_config().health_check is not None and not general_config().health_check.get_service_status(self.cfg.get('name').lower())) if not status: self.analytics.increment("proxy.%s.service.fail" % self.cfg.get('name').lower()) self.err = {"error": 500, "message": "service_failure"} post_healthcheck.send(sender=self, request=self.request, status=status, error=self.err) return status
def handle(self, *args, **options): cfg_file_name = options['cfg_file'] if cfg_file_name is None: cfg_file_name = settings.API_CONF_FILE cfg_file = os.path.abspath(os.path.join(os.path.dirname(cfg_dir), cfg_file_name)) print("Validating configuration file: %s" % cfg_file) try: config = load_config(cfg_file) except IOError as e: print(colored("IOError: Could not open file for reading %s" % e, "red", None, ['bold'])) sys.exit(-1) except Exception as e: print(colored("File doesn't seem to valid JSON %s" % e, "red", None, ['bold'])) sys.exit(-1) print(colored("File Exists", "green")) print(colored("File is valid JSON", "green")) add_proxies_from_data(config) if general_config().errors_during_init: print(colored("JSON file has errors during init", "red", None, ['bold'])) sys.exit(-1) else: print(colored("JSON file is verified correct", "green", None, ['bold'])) sys.exit(0)
def dispatch(self, request, *args, **kwargs): health_check_results = None if str2bool(request.GET.get('details', "False")): if general_config().health_check is not None: health_check_results = {} for endpoint_url in self.endpoints: endpoint = self.endpoints.get(endpoint_url) if endpoint is not None: health_check_results[endpoint.get('name').lower()] = general_config().health_check.get_service_status(endpoint.get('name').lower()) res = {"success": True, "number_of_endpoints": len(self.endpoints), "domain_name": self.domain_name, "version": __version__, "startup_time": self.startup_time } if health_check_results is not None: res['endpoint_statuses'] = health_check_results return HttpResponse(json.dumps(res, default=dthandler), content_type="application/json")
def add_proxies_from_data(config): urlpatterns = [] init_global_config(config.get('general', {})) for key in config.get('endpoints').keys(): endpoint_cfg = config.get('endpoints').get(key) endpoint_cfg['pattern'] = key urlpatterns += patterns('proxy.views', url(key, ProxyView().as_view(cfg=endpoint_cfg)) ) general_config().cache.enable_caching_for_endpoint(key, endpoint_cfg) health_check_url = config.get('general', {}).get("self_health_check_url", "^proxy-check$") urlpatterns += patterns('proxy.views', url(health_check_url, SimpleProxyHealthCheck().as_view(cfg=config))) invalidation_endpoint = general_config().cache.add_invalidation_endpoint() if invalidation_endpoint is not None: urlpatterns += invalidation_endpoint return urlpatterns
def authorize(self, request, *args, **kwargs): cfg = general_config() application_object = cfg.application_object if application_object is None: logger.error("You are trying to use the proxy.authentication.AppKeyProvider with no application_object set!") return AuthenticationError(error_code=500, message="Internal error") else: try: obj = application_cache().get(request.REQUEST.get('client_id', "MISSING-CLIENT-ID-xyz")) if obj is None: return AuthenticationError(error_code=401, message="Invalid client_id") return obj except Exception as e: return AuthenticationError(error_code=401, message="Invalid client_id") return AuthenticationError(error_code=401, message="Invalid client_id")
def invalidate_cache_view(request, application): if not application.super_application: return HttpResponse(json.dumps({"error": 403, "message": "auth_error"}), content_type="application/json", status=403) uncache_type = request.POST.get('uncache_type') if uncache_type == 'user_request': # user use case try: user_id = request.POST.get('user_id') u = general_config().user_provider.find(id=user_id, force_no_cache=True) if u is None: return HttpResponse(json.dumps({"error": 404, "message": "user not found with id " + user_id}), content_type="application/json", status=404) cache_key = UserCacheKeyMaker(u) cache_key.invalidate_with_key() return HttpResponse(json.dumps({"message": "OK"}), content_type="application/json", status=200) except Exception as e: logger.exception("Could not uncache", e) return HttpResponse(json.dumps({"error": "500"}), content_type="application/json", status=500) else: # TODO: Add the normal use case for API Caching (right now we only do user caching) pass return HttpResponse(json.dumps({"message": "OK"}), content_type="application/json", status=200)
def get_with_key(self): for hash_key in self.build_key(): result = general_config().cache.store.retrieve(hash_key) if result is not None: return result
def as_view(cls, **initkwargs): initkwargs['endpoints'] = initkwargs.get('cfg').get('endpoints', []) initkwargs['domain_name'] = general_config().domain_name initkwargs['startup_time'] = datetime.datetime.now(tz=utc) view = super(SimpleProxyHealthCheck, cls).as_view(**initkwargs) return view
def testCacheStartup(self): self.assertIsInstance(general_config().cache.store, SimpleInMemoryCacheStore)
def dispatch(self, request, *args, **kwargs): self.request = request self.err = None custom_headers = {} if general_config().domain_name is not None: custom_headers = {'X_FORWARDED_HOST': general_config().domain_name} self.get_url_to_call() if not self.auth_flow(custom_headers, *args, **kwargs): return HttpResponse(json.dumps(self.err), content_type="application/json", status=self.err.get('error')) if not self.is_allowed(): return HttpResponse(json.dumps(self.err), content_type="application/json", status=self.err.get('error')) if self.cached: cache_key_maker = CacheKeyMaker() cache_key_maker.set_request(self.request) for rule in self.cache_rules: rule.run_rule(cache_key_maker) cache_key = cache_key_maker.build_key() response = general_config().cache.store.retrieve(cache_key) if response is not None: logger.info("Cache HIT for %s" % cache_key) return response else: logger.info("Cache MISS for %s" % cache_key) if not self.is_service_up(): return HttpResponse(json.dumps(self.err), content_type="application/json", status=self.err.get('error')) try: request.GET = request.GET.copy() # make request objects mutable for the transformers for transformer in self.request_transformers: request = transformer.transform_request(request) if request.method.lower() in ['post', 'put']: self.web_call = partial(self.web_call, data=request.body, params=request.GET.lists()) else: self.web_call = partial(self.web_call, params=request.GET.lists()) if self.cfg.get('pass_headers', False): self.web_call = partial(self.web_call, headers=get_all_http_request_headers(request, custom_headers)) else: self.web_call = partial(self.web_call, headers=get_content_request_headers_only(request, custom_headers)) response = self.web_call(self.url_to_call, timeout=general_config().timeout) django_resp = HttpResponse(response.text, status=response.status_code) for header in response.headers.keys(): if header not in hop_headers: if header != "content-encoding": django_resp[header] = response.headers.get(header) django_resp['content-length'] = len(response.content) for transformer in self.response_transformers: django_resp = transformer.transform_response(django_resp) self.analytics.increment("proxy.%s.dispatch.server.success" % (self.cfg.get('name').lower(),)) if self.cached: general_config().cache.store.store(cache_key, django_resp) return django_resp except TransformerException as e: self.analytics.increment("proxy.%s.dispatch.server.transformer_exception.fail" % (self.cfg.get('name').lower(), )) err = e.to_dict() return HttpResponse(json.dumps(err, sort_keys=False), content_type="application/json", status=e.error_code) except FaceOffException as e: self.analytics.increment("proxy.%s.dispatch.server.faceoff_exception.fail" % (self.cfg.get('name').lower(), )) err = e.to_dict() return HttpResponse(json.dumps(err, sort_keys=False), content_type="application/json", status=e.error_code) except (SSLError, Timeout) as e: # Timeouts are SSLErrors if url is SSL err = OrderedDict({"error": "408", "message": "service_time_out"}) self.analytics.increment("proxy.%s.dispatch.server.service_time_out.fail" % (self.cfg.get('name').lower(), )) logger.warning("Service timed out for %s" % self.cfg.get('name') ) return HttpResponse(json.dumps(err, sort_keys=False), content_type="application/json", status=408) except ConnectionError as e: err = OrderedDict({"error": "1", "message": "connection_error"}) self.analytics.increment("proxy.%s.dispatch.server.connection_error.fail" % (self.cfg.get('name').lower(), )) logger.warning("Service connection errored for %s" % self.cfg.get('name') ) return HttpResponse(json.dumps(err, sort_keys=False), content_type="application/json", status=502) except Exception as e: err = OrderedDict({"error": "500", "message": "service_error"}) self.analytics.increment("proxy.%s.dispatch.server.generic_service_exception.fail" % (self.cfg.get('name').lower(), )) return HttpResponse(json.dumps(err, sort_keys=False), content_type="application/json", status=500)
def init_global_config(general_json_config): if hasattr(general_config(), "configuration_complete"): return start = datetime.now() errors = "no" logger.info(colored("-------------------------------------------------", "green")) logger.info(colored("Face/Off Version %s" % proxy.__version__, "green")) logger.info(colored("I'd like to take his face... off.", "green")) logger.info(colored("-------------------------------------------------", "green")) global_faceoff_config = general_config() global_faceoff_config.domain_name = general_json_config.get('domain_name', None) env_domain = os.environ.get("FACEOFF_DOMAIN_NAME", None) overrode_domain_from_env = False if env_domain is not None: global_faceoff_config.domain_name = env_domain overrode_domain_from_env = True if global_faceoff_config.domain_name is None: logger.warning("No domain name in your config, which could cause problems with facaded APIs that need it") else: logger.info("Configuring Face/Off with domain name: %s, Overrode from FACEOFF_DOMAIN_NAME env variable: %s" % (global_faceoff_config.domain_name, overrode_domain_from_env)) global_faceoff_config.timeout = float(general_json_config.get('timeout', '.500')) logger.info("Configuring Face/Off with timeout of %s" % global_faceoff_config.timeout) application_object_name = general_json_config.get('application_object', None) if application_object_name is not None: try: application_object = load_class_from_name(application_object_name) global_faceoff_config.application_object = application_object logger.info("Configuring Face/Off with application object: %s " % application_object_name) except Exception as e: errors = "some" logger.error("Could not load application object with name %s" % (application_object_name,)) else: global_faceoff_config.application_object = None logger.warning("No application object! Can't use AppKeyProvider authentication handle") analytic_classes = general_json_config.get('analytics', []) analytics = ChainedAnalytics() for analytic_class in analytic_classes: if isinstance(analytic_class, dict): function = analytic_class.get('function') parameters = analytic_class.get('parameters') elif isinstance(analytic_class, str): function = analytic_class parameters = {} else: logger.error("Could not load analytics class with config %s" % (analytic_class,)) errors = "some" continue try: analytics.add_analytic(load_class_from_name(function)(**parameters)) except Exception as e: errors = "some" logger.error("Could not load analytics class with name %s because of %s" % (analytic_class, e)) global_faceoff_config.analytics = analytics logger.info("Configuring Face/Off with analytics classes: %s", analytic_classes) try: user_provider_config = general_json_config.get('user_provider', {}) user_function = user_provider_config.get('function') parameters = user_provider_config.get('parameters', {}) global_faceoff_config.user_provider = load_class_from_name(user_function)(**parameters) logger.info("Configuring Face/Off with user provider config: %s", user_provider_config) except Exception as e: if general_json_config.get('user_provider') is None: logger.warning("There is no user provider class, this means Face/Off can't protect endpoints by consumer keys") else: errors = "some" logger.error("Could not load user provider class with config %s" % (general_json_config.get('user_provider'))) user_provider_servers = general_json_config.get('user_provider_servers', []) global_faceoff_config.user_provider_servers = user_provider_servers health_check = general_json_config.get('health_check_storage', {}).get('implementation', None) if health_check is not None: redis_health_check_config = general_json_config.get('health_check_storage') global_faceoff_config.health_check = load_class_from_name(redis_health_check_config.get('implementation'))(**general_json_config.get('health_check_storage')) else: logger.warning("You have no healthcheck implementation. This probably means you have no healthchecks!") global_faceoff_config.health_check = None health_check_url = general_json_config.get("self_health_check_url", "proxy-check") logger.info("Proxy self-check URL is %s", health_check_url) if "cache" in general_json_config: global_faceoff_config.cache = CacheManager(general_json_config.get('cache')) else: global_faceoff_config.cache = NoCacheManager() application_cache().load_from_db() # fill application_cache logger.info("Application in memory cache loaded with %s applications" % (len(application_cache().all()),)) stop = datetime.now() if errors == "some": color = "red" else: color = "green" global_faceoff_config.configuration_complete = True global_faceoff_config.errors_during_init = errors == "some" logger.info(colored("-------------------------------------------------", color)) logger.info(colored("Face/Off started in %sms with %s errors" % ((stop - start).microseconds, errors), color)) logger.info(colored("-------------------------------------------------", color))
def init_analytics(cls, **initkwargs): initkwargs["analytics"] = general_config().analytics return initkwargs
def authorization(request): try: analytics = general_config().analytics client_id = request.GET.get('client_id') response_type = request.GET.get('response_type') state = request.GET.get('state', '') auth_failed = False try: provider = AppKeyProvider() a = provider.authorize(request) redirect_uri = request.GET.get('redirect_uri', None) if redirect_uri is not None: if redirect_uri != a.redirect_uri: # require them to be equal for now redirect_uri = None except Exception as e: logger.error("AUTH ERROR WAS %s" % e) auth_failed = True if isinstance(a, AuthenticationError): auth_failed = True if auth_failed: err = {"error": "4030", "message": "Client id not valid"} analytics.increment("proxy.access_tokens.views.authorization.client_id_not_valid.4030.fail") return HttpResponse(json.dumps(err), content_type="application/json", status=403) if response_type == "token": if 'facebook_access_token' in request.REQUEST or 'device_id' in request.REQUEST or 'ias_user_id' in request.REQUEST: user = None try: user = general_config().user_provider.find(facebook_access_token=request.REQUEST.get('facebook_access_token'), device_id=request.REQUEST.get('device_id'), ias_user_id=request.REQUEST.get('ias_user_id'), headers=provider.get_headers(a)) except Exception as e: err = {"error": "500", "message": "connection error"} analytics.increment("proxy.access_tokens.views.authorization.user_provider_connection_error.500.fail") return HttpResponse(json.dumps(err), content_type="application/json", status=500) if user is None: err = {"error": "4032", "message": "user not found"} analytics.increment("proxy.access_tokens.views.authorization.user_not_found.4032.fail") return HttpResponse(json.dumps(err), content_type="application/json", status=403) if 'facebook_access_token' in request.REQUEST: analytics.increment("proxy.access_tokens.views.authorization.param.facebook_access_token") if 'device_id' in request.REQUEST: analytics.increment("proxy.access_tokens.views.authorization.param.device_id") if 'ias_user_id' in request.REQUEST: analytics.increment("proxy.access_tokens.views.authorization.param.ias_user_id") t = AccessToken() t.user = user.get('id') t.application = a t.is_active = True t.save() analytics.increment("proxy.access_tokens.views.authorization.token_created.success") if redirect_uri is not None: return redirect("%s#access_token=%s&state=%s&token_type=bearer" % (redirect_uri, t.token, state)) else: resp_dict = {'access_token': t.token, 'state': state, 'token_type': 'bearer'} return HttpResponse(json.dumps(resp_dict), content_type='application/json') else: analytics.increment("proxy.access_tokens.views.authorization.parameter_missing.422.fail") return HttpResponse("Required Parameter Missing", status=422) else: analytics.increment("proxy.access_tokens.views.authorization.token_auth_not_implmented.501.fail") err = {"error": "501", "message": "Token auth not implemented yet"} return HttpResponse(json.dumps(err), content_type="application/json", status=501) except Exception as e: analytics.increment("proxy.access_tokens.views.authorization.general_failure.%s.500.fail" % e.__class__.__name__) err = {"error": "500", "message": "Server Error"} return HttpResponse(json.dumps(err), content_type="application/json", status=500)
def store_with_key(self, value): for hash_key in self.build_key(): general_config().cache.store.store(hash_key, value)
def testFaceoffCustomHeaders(self): response = self.client.get("/custom_headers") self.assertIn(b"HTTP_X_FORWARDED_HOST", response.content) self.assertEqual("HTTP_X_FORWARDED_HOST:%s" % general_config().domain_name, response.content.decode('utf-8'))
def invalidate_with_key(self): general_config().cache.store.invalidate(self.build_key())
def get_with_key(self): return general_config().cache.store.retrieve(self.build_key())
def store_with_key(self, value): general_config().cache.store.store(self.build_key(), value)
def invalidate_with_key(self): for hash_key in self.build_key(): general_config().cache.store.invalidate(hash_key)