def __init__(self, cfg, reactor=twisted.internet.reactor): self.reactor = reactor self.config_file = get_config_file_path() self.cfg = cfg logger.info("Starting Sydent server") self.pidfile = self.cfg.get('general', "pidfile.path") self.db = SqliteDatabase(self).db self.server_name = self.cfg.get('general', 'server.name') if self.server_name == '': self.server_name = os.uname()[1] logger.warn(( "You had not specified a server name. I have guessed that this server is called '%s' " + "and saved this in the config file. If this is incorrect, you should edit server.name in " + "the config file.") % (self.server_name, )) self.cfg.set('general', 'server.name', self.server_name) self.save_config() if self.cfg.has_option("general", "sentry_dsn"): # Only import and start sentry SDK if configured. import sentry_sdk sentry_sdk.init(dsn=self.cfg.get("general", "sentry_dsn"), ) with sentry_sdk.configure_scope() as scope: scope.set_tag("sydent_server_name", self.server_name) if self.cfg.has_option("general", "prometheus_port"): import prometheus_client prometheus_client.start_http_server( port=self.cfg.getint("general", "prometheus_port"), addr=self.cfg.get("general", "prometheus_addr"), ) self.enable_v1_associations = parse_cfg_bool( self.cfg.get("general", "enable_v1_associations")) self.delete_tokens_on_bind = parse_cfg_bool( self.cfg.get("general", "delete_tokens_on_bind")) # See if a pepper already exists in the database # Note: This MUST be run before we start serving requests, otherwise lookups for # 3PID hashes may come in before we've completed generating them hashing_metadata_store = HashingMetadataStore(self) lookup_pepper = hashing_metadata_store.get_lookup_pepper() if not lookup_pepper: # No pepper defined in the database, generate one lookup_pepper = generateAlphanumericTokenOfLength(5) # Store it in the database and rehash 3PIDs hashing_metadata_store.store_lookup_pepper( sha256_and_url_safe_base64, lookup_pepper) self.validators = Validators() self.validators.email = EmailValidator(self) self.validators.msisdn = MsisdnValidator(self) self.keyring = Keyring() self.keyring.ed25519 = SydentEd25519(self).signing_key self.keyring.ed25519.alg = 'ed25519' self.sig_verifier = Verifier(self) self.servlets = Servlets() self.servlets.v1 = V1Servlet(self) self.servlets.v2 = V2Servlet(self) self.servlets.emailRequestCode = EmailRequestCodeServlet(self) self.servlets.emailValidate = EmailValidateCodeServlet(self) self.servlets.msisdnRequestCode = MsisdnRequestCodeServlet(self) self.servlets.msisdnValidate = MsisdnValidateCodeServlet(self) self.servlets.lookup = LookupServlet(self) self.servlets.bulk_lookup = BulkLookupServlet(self) self.servlets.hash_details = HashDetailsServlet(self, lookup_pepper) self.servlets.lookup_v2 = LookupV2Servlet(self, lookup_pepper) self.servlets.pubkey_ed25519 = Ed25519Servlet(self) self.servlets.pubkeyIsValid = PubkeyIsValidServlet(self) self.servlets.ephemeralPubkeyIsValid = EphemeralPubkeyIsValidServlet( self) self.servlets.threepidBind = ThreePidBindServlet(self) self.servlets.threepidUnbind = ThreePidUnbindServlet(self) self.servlets.replicationPush = ReplicationPushServlet(self) self.servlets.getValidated3pid = GetValidated3pidServlet(self) self.servlets.storeInviteServlet = StoreInviteServlet(self) self.servlets.blindlySignStuffServlet = BlindlySignStuffServlet(self) self.servlets.termsServlet = TermsServlet(self) self.servlets.accountServlet = AccountServlet(self) self.servlets.registerServlet = RegisterServlet(self) self.servlets.logoutServlet = LogoutServlet(self) self.threepidBinder = ThreepidBinder(self) self.sslComponents = SslComponents(self) self.clientApiHttpServer = ClientApiHttpServer(self) self.replicationHttpsServer = ReplicationHttpsServer(self) self.replicationHttpsClient = ReplicationHttpsClient(self) self.pusher = Pusher(self) # A dedicated validation session store just to clean up old sessions every N minutes self.cleanupValSession = ThreePidValSessionStore(self) cb = task.LoopingCall(self.cleanupValSession.deleteOldSessions) cb.clock = self.reactor cb.start(10 * 60.0)
class Sydent: def __init__(self, cfg, reactor=twisted.internet.reactor): self.reactor = reactor self.config_file = get_config_file_path() self.cfg = cfg logger.info("Starting Sydent server") self.pidfile = self.cfg.get('general', "pidfile.path") self.db = SqliteDatabase(self).db self.server_name = self.cfg.get('general', 'server.name') if self.server_name == '': self.server_name = os.uname()[1] logger.warn(( "You had not specified a server name. I have guessed that this server is called '%s' " + "and saved this in the config file. If this is incorrect, you should edit server.name in " + "the config file.") % (self.server_name, )) self.cfg.set('general', 'server.name', self.server_name) self.save_config() if self.cfg.has_option("general", "sentry_dsn"): # Only import and start sentry SDK if configured. import sentry_sdk sentry_sdk.init(dsn=self.cfg.get("general", "sentry_dsn"), ) with sentry_sdk.configure_scope() as scope: scope.set_tag("sydent_server_name", self.server_name) if self.cfg.has_option("general", "prometheus_port"): import prometheus_client prometheus_client.start_http_server( port=self.cfg.getint("general", "prometheus_port"), addr=self.cfg.get("general", "prometheus_addr"), ) self.enable_v1_associations = parse_cfg_bool( self.cfg.get("general", "enable_v1_associations")) self.delete_tokens_on_bind = parse_cfg_bool( self.cfg.get("general", "delete_tokens_on_bind")) self.username_obfuscate_characters = int( self.cfg.get( "email", "email.third_party_invite_username_obfuscate_characters")) self.domain_obfuscate_characters = int( self.cfg.get( "email", "email.third_party_invite_domain_obfuscate_characters")) # See if a pepper already exists in the database # Note: This MUST be run before we start serving requests, otherwise lookups for # 3PID hashes may come in before we've completed generating them hashing_metadata_store = HashingMetadataStore(self) lookup_pepper = hashing_metadata_store.get_lookup_pepper() if not lookup_pepper: # No pepper defined in the database, generate one lookup_pepper = generateAlphanumericTokenOfLength(5) # Store it in the database and rehash 3PIDs hashing_metadata_store.store_lookup_pepper( sha256_and_url_safe_base64, lookup_pepper) self.validators = Validators() self.validators.email = EmailValidator(self) self.validators.msisdn = MsisdnValidator(self) self.keyring = Keyring() self.keyring.ed25519 = SydentEd25519(self).signing_key self.keyring.ed25519.alg = 'ed25519' self.sig_verifier = Verifier(self) self.servlets = Servlets() self.servlets.v1 = V1Servlet(self) self.servlets.v2 = V2Servlet(self) self.servlets.emailRequestCode = EmailRequestCodeServlet(self) self.servlets.emailValidate = EmailValidateCodeServlet(self) self.servlets.msisdnRequestCode = MsisdnRequestCodeServlet(self) self.servlets.msisdnValidate = MsisdnValidateCodeServlet(self) self.servlets.lookup = LookupServlet(self) self.servlets.bulk_lookup = BulkLookupServlet(self) self.servlets.hash_details = HashDetailsServlet(self, lookup_pepper) self.servlets.lookup_v2 = LookupV2Servlet(self, lookup_pepper) self.servlets.pubkey_ed25519 = Ed25519Servlet(self) self.servlets.pubkeyIsValid = PubkeyIsValidServlet(self) self.servlets.ephemeralPubkeyIsValid = EphemeralPubkeyIsValidServlet( self) self.servlets.threepidBind = ThreePidBindServlet(self) self.servlets.threepidUnbind = ThreePidUnbindServlet(self) self.servlets.replicationPush = ReplicationPushServlet(self) self.servlets.getValidated3pid = GetValidated3pidServlet(self) self.servlets.storeInviteServlet = StoreInviteServlet(self) self.servlets.blindlySignStuffServlet = BlindlySignStuffServlet(self) self.servlets.termsServlet = TermsServlet(self) self.servlets.accountServlet = AccountServlet(self) self.servlets.registerServlet = RegisterServlet(self) self.servlets.logoutServlet = LogoutServlet(self) self.threepidBinder = ThreepidBinder(self) self.sslComponents = SslComponents(self) self.clientApiHttpServer = ClientApiHttpServer(self) self.replicationHttpsServer = ReplicationHttpsServer(self) self.replicationHttpsClient = ReplicationHttpsClient(self) self.pusher = Pusher(self) # A dedicated validation session store just to clean up old sessions every N minutes self.cleanupValSession = ThreePidValSessionStore(self) cb = task.LoopingCall(self.cleanupValSession.deleteOldSessions) cb.clock = self.reactor cb.start(10 * 60.0) # workaround for https://github.com/getsentry/sentry-python/issues/803: we # disable automatic GC and run it periodically instead. gc.disable() cb = task.LoopingCall(run_gc) cb.clock = self.reactor cb.start(1.0) def save_config(self): fp = open(self.config_file, 'w') self.cfg.write(fp) fp.close() def run(self): self.clientApiHttpServer.setup() self.replicationHttpsServer.setup() self.pusher.setup() internalport = self.cfg.get('http', 'internalapi.http.port') if internalport: try: interface = self.cfg.get('http', 'internalapi.http.bind_address') except configparser.NoOptionError: interface = '::1' self.internalApiHttpServer = InternalApiHttpServer(self) self.internalApiHttpServer.setup(interface, int(internalport)) if self.pidfile: with open(self.pidfile, 'w') as pidfile: pidfile.write(str(os.getpid()) + "\n") self.reactor.run() def ip_from_request(self, request): if (self.cfg.get('http', 'obey_x_forwarded_for') and request.requestHeaders.hasHeader("X-Forwarded-For")): return request.requestHeaders.getRawHeaders("X-Forwarded-For")[0] return request.getClientIP()
def __init__( self, sydent_config: SydentConfig, reactor: SydentReactor = twisted.internet.reactor, # type: ignore[assignment] use_tls_for_federation: bool = True, ): self.config = sydent_config self.reactor = reactor self.use_tls_for_federation = use_tls_for_federation logger.info("Starting Sydent server") self.db: sqlite3.Connection = SqliteDatabase(self).db if self.config.general.sentry_enabled: import sentry_sdk sentry_sdk.init( dsn=self.config.general.sentry_dsn, release=get_version_string() ) with sentry_sdk.configure_scope() as scope: scope.set_tag("sydent_server_name", self.config.general.server_name) # workaround for https://github.com/getsentry/sentry-python/issues/803: we # disable automatic GC and run it periodically instead. gc.disable() cb = task.LoopingCall(run_gc) cb.clock = self.reactor cb.start(1.0) # See if a pepper already exists in the database # Note: This MUST be run before we start serving requests, otherwise lookups for # 3PID hashes may come in before we've completed generating them hashing_metadata_store = HashingMetadataStore(self) lookup_pepper = hashing_metadata_store.get_lookup_pepper() if not lookup_pepper: # No pepper defined in the database, generate one lookup_pepper = generateAlphanumericTokenOfLength(5) # Store it in the database and rehash 3PIDs hashing_metadata_store.store_lookup_pepper( sha256_and_url_safe_base64, lookup_pepper ) self.validators: Validators = Validators( EmailValidator(self), MsisdnValidator(self) ) self.keyring: Keyring = Keyring(self.config.crypto.signing_key) self.keyring.ed25519.alg = "ed25519" self.sig_verifier: Verifier = Verifier(self) self.servlets: Servlets = Servlets(self, lookup_pepper) self.threepidBinder: ThreepidBinder = ThreepidBinder(self) self.sslComponents: SslComponents = SslComponents(self) self.clientApiHttpServer = ClientApiHttpServer(self) self.replicationHttpsServer = ReplicationHttpsServer(self) self.replicationHttpsClient: ReplicationHttpsClient = ReplicationHttpsClient( self ) self.pusher: Pusher = Pusher(self)
class Sydent: def __init__(self, cfg, reactor=twisted.internet.reactor): self.reactor = reactor self.config_file = get_config_file_path() self.cfg = cfg logger.info("Starting Sydent server") self.pidfile = self.cfg.get('general', "pidfile.path"); self.db = SqliteDatabase(self).db self.server_name = self.cfg.get('general', 'server.name') if self.server_name == '': self.server_name = os.uname()[1] logger.warn(("You had not specified a server name. I have guessed that this server is called '%s' " + "and saved this in the config file. If this is incorrect, you should edit server.name in " + "the config file.") % (self.server_name,)) self.cfg.set('general', 'server.name', self.server_name) self.save_config() if self.cfg.has_option("general", "sentry_dsn"): # Only import and start sentry SDK if configured. import sentry_sdk sentry_sdk.init( dsn=self.cfg.get("general", "sentry_dsn"), ) with sentry_sdk.configure_scope() as scope: scope.set_tag("sydent_server_name", self.server_name) if self.cfg.has_option("general", "prometheus_port"): import prometheus_client prometheus_client.start_http_server( port=self.cfg.getint("general", "prometheus_port"), addr=self.cfg.get("general", "prometheus_addr"), ) if self.cfg.has_option("general", "templates.path"): # Get the possible brands by looking at directories under the # templates.path directory. root_template_path = self.cfg.get("general", "templates.path") if os.path.exists(root_template_path): self.valid_brands = { p for p in os.listdir(root_template_path) if os.path.isdir(os.path.join(root_template_path, p)) } else: # This is a legacy code-path and assumes that verify_response_template, # email.template, and email.invite_template are defined. self.valid_brands = set() self.enable_v1_associations = parse_cfg_bool( self.cfg.get("general", "enable_v1_associations") ) self.delete_tokens_on_bind = parse_cfg_bool( self.cfg.get("general", "delete_tokens_on_bind") ) self.default_web_client_location = self.cfg.get( "email", "email.default_web_client_location" ) self.username_obfuscate_characters = int(self.cfg.get( "email", "email.third_party_invite_username_obfuscate_characters" )) self.domain_obfuscate_characters = int(self.cfg.get( "email", "email.third_party_invite_domain_obfuscate_characters" )) # See if a pepper already exists in the database # Note: This MUST be run before we start serving requests, otherwise lookups for # 3PID hashes may come in before we've completed generating them hashing_metadata_store = HashingMetadataStore(self) lookup_pepper = hashing_metadata_store.get_lookup_pepper() if not lookup_pepper: # No pepper defined in the database, generate one lookup_pepper = generateAlphanumericTokenOfLength(5) # Store it in the database and rehash 3PIDs hashing_metadata_store.store_lookup_pepper(sha256_and_url_safe_base64, lookup_pepper) self.validators = Validators() self.validators.email = EmailValidator(self) self.validators.msisdn = MsisdnValidator(self) self.keyring = Keyring() self.keyring.ed25519 = SydentEd25519(self).signing_key self.keyring.ed25519.alg = 'ed25519' self.sig_verifier = Verifier(self) self.servlets = Servlets() self.servlets.v1 = V1Servlet(self) self.servlets.v2 = V2Servlet(self) self.servlets.emailRequestCode = EmailRequestCodeServlet(self) self.servlets.emailValidate = EmailValidateCodeServlet(self) self.servlets.msisdnRequestCode = MsisdnRequestCodeServlet(self) self.servlets.msisdnValidate = MsisdnValidateCodeServlet(self) self.servlets.lookup = LookupServlet(self) self.servlets.bulk_lookup = BulkLookupServlet(self) self.servlets.hash_details = HashDetailsServlet(self, lookup_pepper) self.servlets.lookup_v2 = LookupV2Servlet(self, lookup_pepper) self.servlets.pubkey_ed25519 = Ed25519Servlet(self) self.servlets.pubkeyIsValid = PubkeyIsValidServlet(self) self.servlets.ephemeralPubkeyIsValid = EphemeralPubkeyIsValidServlet(self) self.servlets.threepidBind = ThreePidBindServlet(self) self.servlets.threepidUnbind = ThreePidUnbindServlet(self) self.servlets.replicationPush = ReplicationPushServlet(self) self.servlets.getValidated3pid = GetValidated3pidServlet(self) self.servlets.storeInviteServlet = StoreInviteServlet(self) self.servlets.blindlySignStuffServlet = BlindlySignStuffServlet(self) self.servlets.termsServlet = TermsServlet(self) self.servlets.accountServlet = AccountServlet(self) self.servlets.registerServlet = RegisterServlet(self) self.servlets.logoutServlet = LogoutServlet(self) self.threepidBinder = ThreepidBinder(self) self.sslComponents = SslComponents(self) self.clientApiHttpServer = ClientApiHttpServer(self) self.replicationHttpsServer = ReplicationHttpsServer(self) self.replicationHttpsClient = ReplicationHttpsClient(self) self.pusher = Pusher(self) # A dedicated validation session store just to clean up old sessions every N minutes self.cleanupValSession = ThreePidValSessionStore(self) cb = task.LoopingCall(self.cleanupValSession.deleteOldSessions) cb.clock = self.reactor cb.start(10 * 60.0) # workaround for https://github.com/getsentry/sentry-python/issues/803: we # disable automatic GC and run it periodically instead. gc.disable() cb = task.LoopingCall(run_gc) cb.clock = self.reactor cb.start(1.0) def save_config(self): fp = open(self.config_file, 'w') self.cfg.write(fp) fp.close() def run(self): self.clientApiHttpServer.setup() self.replicationHttpsServer.setup() self.pusher.setup() internalport = self.cfg.get('http', 'internalapi.http.port') if internalport: try: interface = self.cfg.get('http', 'internalapi.http.bind_address') except configparser.NoOptionError: interface = '::1' self.internalApiHttpServer = InternalApiHttpServer(self) self.internalApiHttpServer.setup(interface, int(internalport)) if self.pidfile: with open(self.pidfile, 'w') as pidfile: pidfile.write(str(os.getpid()) + "\n") self.reactor.run() def ip_from_request(self, request): if (self.cfg.get('http', 'obey_x_forwarded_for') and request.requestHeaders.hasHeader("X-Forwarded-For")): return request.requestHeaders.getRawHeaders("X-Forwarded-For")[0] return request.getClientIP() def brand_from_request(self, request): """ If the brand GET parameter is passed, returns that as a string, otherwise returns None. :param request: The incoming request. :type request: twisted.web.http.Request :return: The brand to use or None if no hint is found. :rtype: str or None """ if b"brand" in request.args: return request.args[b"brand"][0].decode("utf-8") return None def get_branded_template(self, brand, template_name, deprecated_template_name): """ Calculate a (maybe) branded template filename to use. If the deprecated email.template setting is defined, always use it. Otherwise, attempt to use the hinted brand from the request if the brand is valid. Otherwise, fallback to the default brand. :param brand: The hint of which brand to use. :type brand: str or None :param template_name: The name of the template file to load. :type template_name: str :param deprecated_template_name: The deprecated setting to use, if provided. :type deprecated_template_name: Tuple[str] :return: The template filename to use. :rtype: str """ # If the deprecated setting is defined, return it. try: return self.cfg.get(*deprecated_template_name) except configparser.NoOptionError: pass # If a brand hint is provided, attempt to use it if it is valid. if brand: if brand not in self.valid_brands: brand = None # If the brand hint is not valid, or not provided, fallback to the default brand. if not brand: brand = self.cfg.get("general", "brand.default") root_template_path = self.cfg.get("general", "templates.path") return os.path.join(root_template_path, brand, template_name)
class Sydent: def __init__( self, sydent_config: SydentConfig, reactor: SydentReactor = twisted.internet.reactor, # type: ignore[assignment] use_tls_for_federation: bool = True, ): self.config = sydent_config self.reactor = reactor self.use_tls_for_federation = use_tls_for_federation logger.info("Starting Sydent server") self.db: sqlite3.Connection = SqliteDatabase(self).db if self.config.general.sentry_enabled: import sentry_sdk sentry_sdk.init( dsn=self.config.general.sentry_dsn, release=get_version_string() ) with sentry_sdk.configure_scope() as scope: scope.set_tag("sydent_server_name", self.config.general.server_name) # workaround for https://github.com/getsentry/sentry-python/issues/803: we # disable automatic GC and run it periodically instead. gc.disable() cb = task.LoopingCall(run_gc) cb.clock = self.reactor cb.start(1.0) # See if a pepper already exists in the database # Note: This MUST be run before we start serving requests, otherwise lookups for # 3PID hashes may come in before we've completed generating them hashing_metadata_store = HashingMetadataStore(self) lookup_pepper = hashing_metadata_store.get_lookup_pepper() if not lookup_pepper: # No pepper defined in the database, generate one lookup_pepper = generateAlphanumericTokenOfLength(5) # Store it in the database and rehash 3PIDs hashing_metadata_store.store_lookup_pepper( sha256_and_url_safe_base64, lookup_pepper ) self.validators: Validators = Validators( EmailValidator(self), MsisdnValidator(self) ) self.keyring: Keyring = Keyring(self.config.crypto.signing_key) self.keyring.ed25519.alg = "ed25519" self.sig_verifier: Verifier = Verifier(self) self.servlets: Servlets = Servlets(self, lookup_pepper) self.threepidBinder: ThreepidBinder = ThreepidBinder(self) self.sslComponents: SslComponents = SslComponents(self) self.clientApiHttpServer = ClientApiHttpServer(self) self.replicationHttpsServer = ReplicationHttpsServer(self) self.replicationHttpsClient: ReplicationHttpsClient = ReplicationHttpsClient( self ) self.pusher: Pusher = Pusher(self) def run(self) -> None: self.clientApiHttpServer.setup() self.replicationHttpsServer.setup() self.pusher.setup() self.maybe_start_prometheus_server() # A dedicated validation session store just to clean up old sessions every N minutes self.cleanupValSession = ThreePidValSessionStore(self) cb = task.LoopingCall(self.cleanupValSession.deleteOldSessions) cb.clock = self.reactor cb.start(10 * 60.0) if self.config.http.internal_port is not None: internalport = self.config.http.internal_port interface = self.config.http.internal_bind_address self.internalApiHttpServer = InternalApiHttpServer(self) self.internalApiHttpServer.setup(interface, internalport) if self.config.general.pidfile: with open(self.config.general.pidfile, "w") as pidfile: pidfile.write(str(os.getpid()) + "\n") self.reactor.run() def maybe_start_prometheus_server(self) -> None: if self.config.general.prometheus_enabled: import prometheus_client prometheus_client.start_http_server( port=self.config.general.prometheus_port, addr=self.config.general.prometheus_addr, ) def ip_from_request(self, request: Request) -> Optional[str]: if self.config.http.obey_x_forwarded_for and request.requestHeaders.hasHeader( "X-Forwarded-For" ): # Type safety: hasHeaders returning True means that getRawHeaders # returns a nonempty list return request.requestHeaders.getRawHeaders("X-Forwarded-For")[0] # type: ignore[index] client = request.getClientAddress() if isinstance(client, (address.IPv4Address, address.IPv6Address)): return client.host else: return None def brand_from_request(self, request: Request) -> Optional[str]: """ If the brand GET parameter is passed, returns that as a string, otherwise returns None. :param request: The incoming request. :return: The brand to use or None if no hint is found. """ if b"brand" in request.args: return request.args[b"brand"][0].decode("utf-8") return None def get_branded_template( self, brand: Optional[str], template_name: str, ) -> str: """ Calculate a branded template filename to use. Attempt to use the hinted brand from the request if the brand is valid. Otherwise, fallback to the default brand. :param brand: The hint of which brand to use. :type brand: str or None :param template_name: The name of the template file to load. :type template_name: str :return: The template filename to use. :rtype: str """ # If a brand hint is provided, attempt to use it if it is valid. if brand: if brand not in self.config.general.valid_brands: brand = None # If the brand hint is not valid, or not provided, fallback to the default brand. if not brand: brand = self.config.general.default_brand root_template_path = self.config.general.templates_path # Grab jinja template if it exists if os.path.exists( os.path.join(root_template_path, brand, template_name + ".j2") ): return os.path.join(brand, template_name + ".j2") else: return os.path.join(root_template_path, brand, template_name)