2021-07-06 06:03:16 -06:00
|
|
|
# Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
from datetime import datetime
|
|
|
|
from typing import List
|
|
|
|
|
|
|
|
import attr
|
|
|
|
|
2021-10-27 10:25:18 -06:00
|
|
|
from synapse.config._base import (
|
|
|
|
Config,
|
|
|
|
RootConfig,
|
|
|
|
find_config_files,
|
|
|
|
read_config_files,
|
|
|
|
)
|
2021-07-06 06:03:16 -06:00
|
|
|
from synapse.config.database import DatabaseConfig
|
|
|
|
from synapse.storage.database import DatabasePool, LoggingTransaction, make_conn
|
|
|
|
from synapse.storage.engines import create_engine
|
|
|
|
|
|
|
|
|
|
|
|
class ReviewConfig(RootConfig):
|
|
|
|
"A config class that just pulls out the database config"
|
|
|
|
config_classes = [DatabaseConfig]
|
|
|
|
|
|
|
|
|
|
|
|
@attr.s(auto_attribs=True)
|
|
|
|
class UserInfo:
|
|
|
|
user_id: str
|
|
|
|
creation_ts: int
|
|
|
|
emails: List[str] = attr.Factory(list)
|
|
|
|
private_rooms: List[str] = attr.Factory(list)
|
|
|
|
public_rooms: List[str] = attr.Factory(list)
|
|
|
|
ips: List[str] = attr.Factory(list)
|
|
|
|
|
|
|
|
|
2022-01-17 09:43:25 -07:00
|
|
|
def get_recent_users(
|
|
|
|
txn: LoggingTransaction, since_ms: int, exclude_app_service: bool
|
|
|
|
) -> List[UserInfo]:
|
2021-07-06 06:03:16 -06:00
|
|
|
"""Fetches recently registered users and some info on them."""
|
|
|
|
|
|
|
|
sql = """
|
|
|
|
SELECT name, creation_ts FROM users
|
|
|
|
WHERE
|
|
|
|
? <= creation_ts
|
|
|
|
AND deactivated = 0
|
|
|
|
"""
|
|
|
|
|
2022-01-17 09:43:25 -07:00
|
|
|
if exclude_app_service:
|
|
|
|
sql += " AND appservice_id IS NULL"
|
|
|
|
|
2021-07-06 06:03:16 -06:00
|
|
|
txn.execute(sql, (since_ms / 1000,))
|
|
|
|
|
|
|
|
user_infos = [UserInfo(user_id, creation_ts) for user_id, creation_ts in txn]
|
|
|
|
|
|
|
|
for user_info in user_infos:
|
|
|
|
user_info.emails = DatabasePool.simple_select_onecol_txn(
|
|
|
|
txn,
|
|
|
|
table="user_threepids",
|
|
|
|
keyvalues={"user_id": user_info.user_id, "medium": "email"},
|
|
|
|
retcol="address",
|
|
|
|
)
|
|
|
|
|
|
|
|
sql = """
|
|
|
|
SELECT room_id, canonical_alias, name, join_rules
|
|
|
|
FROM local_current_membership
|
|
|
|
INNER JOIN room_stats_state USING (room_id)
|
|
|
|
WHERE user_id = ? AND membership = 'join'
|
|
|
|
"""
|
|
|
|
|
|
|
|
txn.execute(sql, (user_info.user_id,))
|
|
|
|
for room_id, canonical_alias, name, join_rules in txn:
|
|
|
|
if join_rules == "public":
|
|
|
|
user_info.public_rooms.append(canonical_alias or name or room_id)
|
|
|
|
else:
|
|
|
|
user_info.private_rooms.append(canonical_alias or name or room_id)
|
|
|
|
|
|
|
|
user_info.ips = DatabasePool.simple_select_onecol_txn(
|
|
|
|
txn,
|
|
|
|
table="user_ips",
|
|
|
|
keyvalues={"user_id": user_info.user_id},
|
|
|
|
retcol="ip",
|
|
|
|
)
|
|
|
|
|
|
|
|
return user_infos
|
|
|
|
|
|
|
|
|
2021-11-10 10:55:32 -07:00
|
|
|
def main() -> None:
|
2021-07-06 06:03:16 -06:00
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument(
|
|
|
|
"-c",
|
|
|
|
"--config-path",
|
|
|
|
action="append",
|
|
|
|
metavar="CONFIG_FILE",
|
|
|
|
help="The config files for Synapse.",
|
|
|
|
required=True,
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-s",
|
|
|
|
"--since",
|
|
|
|
metavar="duration",
|
|
|
|
help="Specify how far back to review user registrations for, defaults to 7d (i.e. 7 days).",
|
|
|
|
default="7d",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-e",
|
|
|
|
"--exclude-emails",
|
|
|
|
action="store_true",
|
2022-01-17 09:43:25 -07:00
|
|
|
help="Exclude users that have validated email addresses.",
|
2021-07-06 06:03:16 -06:00
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-u",
|
|
|
|
"--only-users",
|
|
|
|
action="store_true",
|
|
|
|
help="Only print user IDs that match.",
|
|
|
|
)
|
2022-01-17 09:43:25 -07:00
|
|
|
parser.add_argument(
|
|
|
|
"-a",
|
|
|
|
"--exclude-app-service",
|
|
|
|
help="Exclude appservice users.",
|
|
|
|
action="store_true",
|
|
|
|
)
|
2021-07-06 06:03:16 -06:00
|
|
|
|
|
|
|
config = ReviewConfig()
|
|
|
|
|
|
|
|
config_args = parser.parse_args(sys.argv[1:])
|
|
|
|
config_files = find_config_files(search_paths=config_args.config_path)
|
|
|
|
config_dict = read_config_files(config_files)
|
|
|
|
config.parse_config_dict(
|
|
|
|
config_dict,
|
|
|
|
)
|
|
|
|
|
2021-10-27 10:25:18 -06:00
|
|
|
since_ms = time.time() * 1000 - Config.parse_duration(config_args.since)
|
2021-07-06 06:03:16 -06:00
|
|
|
exclude_users_with_email = config_args.exclude_emails
|
2022-01-17 09:43:25 -07:00
|
|
|
exclude_users_with_appservice = config_args.exclude_app_service
|
2021-07-06 06:03:16 -06:00
|
|
|
include_context = not config_args.only_users
|
|
|
|
|
|
|
|
for database_config in config.database.databases:
|
|
|
|
if "main" in database_config.databases:
|
|
|
|
break
|
|
|
|
|
|
|
|
engine = create_engine(database_config.config)
|
|
|
|
|
|
|
|
with make_conn(database_config, engine, "review_recent_signups") as db_conn:
|
2021-11-10 10:55:32 -07:00
|
|
|
# This generates a type of Cursor, not LoggingTransaction.
|
2022-01-17 09:43:25 -07:00
|
|
|
user_infos = get_recent_users(db_conn.cursor(), since_ms, exclude_users_with_appservice) # type: ignore[arg-type]
|
2021-07-06 06:03:16 -06:00
|
|
|
|
|
|
|
for user_info in user_infos:
|
|
|
|
if exclude_users_with_email and user_info.emails:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if include_context:
|
|
|
|
print_public_rooms = ""
|
|
|
|
if user_info.public_rooms:
|
|
|
|
print_public_rooms = "(" + ", ".join(user_info.public_rooms[:3])
|
|
|
|
|
|
|
|
if len(user_info.public_rooms) > 3:
|
|
|
|
print_public_rooms += ", ..."
|
|
|
|
|
|
|
|
print_public_rooms += ")"
|
|
|
|
|
|
|
|
print("# Created:", datetime.fromtimestamp(user_info.creation_ts))
|
|
|
|
print("# Email:", ", ".join(user_info.emails) or "None")
|
|
|
|
print("# IPs:", ", ".join(user_info.ips))
|
|
|
|
print(
|
|
|
|
"# Number joined public rooms:",
|
|
|
|
len(user_info.public_rooms),
|
|
|
|
print_public_rooms,
|
|
|
|
)
|
|
|
|
print("# Number joined private rooms:", len(user_info.private_rooms))
|
|
|
|
print("#")
|
|
|
|
|
|
|
|
print(user_info.user_id)
|
|
|
|
|
|
|
|
if include_context:
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|