Skip to content

Commit af04ca1

Browse files
committed
Add support for double puppeting with another as_token
1 parent e0aad71 commit af04ca1

File tree

3 files changed

+46
-8
lines changed

3 files changed

+46
-8
lines changed

mautrix/appservice/api/appservice.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(
5151
client_session: ClientSession = None,
5252
child: bool = False,
5353
real_user: bool = False,
54+
real_user_as_token: bool = False,
5455
bridge_name: str | None = None,
5556
default_retry_count: int = None,
5657
loop: asyncio.AbstractEventLoop | None = None,
@@ -66,6 +67,7 @@ def __init__(
6667
client_session: The aiohttp ClientSession to use.
6768
child: Whether or not this is instance is a child of another AppServiceAPI.
6869
real_user: Whether or not this is a real (non-appservice-managed) user.
70+
real_user_as_token: Whether this real user is actually using another ``as_token``.
6971
bridge_name: The name of the bridge to put in the ``fi.mau.double_puppet_source`` field
7072
in outgoing message events sent through real users.
7173
"""
@@ -85,6 +87,7 @@ def __init__(
8587
self._bot_intent = None
8688
self.state_store = state_store
8789
self.is_real_user = real_user
90+
self.is_real_user_as_token = real_user_as_token
8891
self.bridge_name = bridge_name
8992

9093
if not child:
@@ -113,7 +116,9 @@ def user(self, user: UserID) -> ChildAppServiceAPI:
113116
self.children[user] = child
114117
return child
115118

116-
def real_user(self, mxid: UserID, token: str, base_url: URL | None = None) -> AppServiceAPI:
119+
def real_user(
120+
self, mxid: UserID, token: str, base_url: URL | None = None, as_token: bool = False
121+
) -> AppServiceAPI:
117122
"""
118123
Get the AppServiceAPI for a real (non-appservice-managed) Matrix user.
119124
@@ -122,6 +127,8 @@ def real_user(self, mxid: UserID, token: str, base_url: URL | None = None) -> Ap
122127
token: The access token for the user.
123128
base_url: The base URL of the homeserver client-server API to use. Defaults to the
124129
appservice homeserver URL.
130+
as_token: Whether the token is actually an as_token
131+
(meaning the ``user_id`` query parameter needs to be used).
125132
126133
Returns:
127134
The AppServiceAPI object for the user.
@@ -136,6 +143,7 @@ def real_user(self, mxid: UserID, token: str, base_url: URL | None = None) -> Ap
136143
child = self.real_users[mxid]
137144
child.base_url = base_url or child.base_url
138145
child.token = token or child.token
146+
child.is_real_user_as_token = as_token
139147
except KeyError:
140148
child = type(self)(
141149
base_url=base_url or self.base_url,
@@ -145,6 +153,7 @@ def real_user(self, mxid: UserID, token: str, base_url: URL | None = None) -> Ap
145153
state_store=self.state_store,
146154
client_session=self.session,
147155
real_user=True,
156+
real_user_as_token=as_token,
148157
bridge_name=self.bridge_name,
149158
default_retry_count=self.default_retry_count,
150159
)
@@ -163,7 +172,11 @@ def bot_intent(self) -> as_api.IntentAPI:
163172
return self._bot_intent
164173

165174
def intent(
166-
self, user: UserID = None, token: str | None = None, base_url: str | None = None
175+
self,
176+
user: UserID = None,
177+
token: str | None = None,
178+
base_url: str | None = None,
179+
real_user_as_token: bool = False,
167180
) -> as_api.IntentAPI:
168181
"""
169182
Get the intent API of a child user.
@@ -173,6 +186,8 @@ def intent(
173186
token: The access token to use. Only applicable for non-appservice-managed users.
174187
base_url: The base URL of the homeserver client-server API to use. Only applicable for
175188
non-appservice users. Defaults to the appservice homeserver URL.
189+
real_user_as_token: When providing a token, whether it's actually another as_token
190+
(meaning the ``user_id`` query parameter needs to be used).
176191
177192
Returns:
178193
The IntentAPI object for the given user.
@@ -184,7 +199,10 @@ def intent(
184199
raise ValueError("Can't get child intent of real user")
185200
if token:
186201
return as_api.IntentAPI(
187-
user, self.real_user(user, token, base_url), self.bot_intent(), self.state_store
202+
user,
203+
self.real_user(user, token, base_url, as_token=real_user_as_token),
204+
self.bot_intent(),
205+
self.state_store,
188206
)
189207
return as_api.IntentAPI(user, self.user(user), self.bot_intent(), self.state_store)
190208

@@ -229,7 +247,7 @@ def request(
229247
if isinstance(timestamp, datetime):
230248
timestamp = int(timestamp.replace(tzinfo=timezone.utc).timestamp() * 1000)
231249
query_params["ts"] = timestamp
232-
if not self.is_real_user:
250+
if not self.is_real_user or self.is_real_user_as_token:
233251
query_params["user_id"] = self.identity or self.bot_mxid
234252

235253
return super().request(

mautrix/appservice/api/intent.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,11 @@ async def wrapper(*args, __self=self, __method=method, **kwargs):
144144
setattr(self, method.__name__, wrapper)
145145

146146
def user(
147-
self, user_id: UserID, token: str | None = None, base_url: str | None = None
147+
self,
148+
user_id: UserID,
149+
token: str | None = None,
150+
base_url: str | None = None,
151+
as_token: bool = False,
148152
) -> IntentAPI:
149153
"""
150154
Get the intent API for a specific user.
@@ -162,10 +166,10 @@ def user(
162166
The IntentAPI for the given user.
163167
"""
164168
if not self.bot:
165-
return self.api.intent(user_id, token, base_url)
169+
return self.api.intent(user_id, token, base_url, real_user_as_token=as_token)
166170
else:
167171
self.log.warning("Called IntentAPI#user() of child intent object.")
168-
return self.bot.api.intent(user_id, token, base_url)
172+
return self.bot.api.intent(user_id, token, base_url, real_user_as_token=as_token)
169173

170174
# region User actions
171175

mautrix/bridge/custom_puppet.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,19 @@ def is_real_user(self) -> bool:
152152
return bool(self.custom_mxid and self.access_token)
153153

154154
def _fresh_intent(self) -> IntentAPI:
155+
if self.access_token == "appservice-config" and self.custom_mxid:
156+
_, server = self.az.intent.parse_user_id(self.custom_mxid)
157+
try:
158+
secret = self.login_shared_secret_map[server]
159+
except KeyError:
160+
raise AutologinError(f"No shared secret configured for {server}")
161+
self.log.debug(f"Using as_token for double puppeting {self.custom_mxid}")
162+
return self.az.intent.user(
163+
self.custom_mxid,
164+
secret.decode("utf-8").removeprefix("as_token:"),
165+
self.base_url,
166+
as_token=True,
167+
)
155168
return (
156169
self.az.intent.user(self.custom_mxid, self.access_token, self.base_url)
157170
if self.is_real_user
@@ -172,6 +185,8 @@ async def _login_with_shared_secret(cls, mxid: UserID) -> str:
172185
secret = cls.login_shared_secret_map[server]
173186
except KeyError:
174187
raise AutologinError(f"No shared secret configured for {server}")
188+
if secret.startswith(b"as_token:"):
189+
return "appservice-config"
175190
try:
176191
base_url = cls.homeserver_url_map[server]
177192
except KeyError:
@@ -220,7 +235,8 @@ async def switch_mxid(
220235
"""
221236
if access_token == "auto":
222237
access_token = await self._login_with_shared_secret(mxid)
223-
self.log.debug(f"Logged in for {mxid} using shared secret")
238+
if access_token != "appservice-config":
239+
self.log.debug(f"Logged in for {mxid} using shared secret")
224240

225241
if mxid is not None:
226242
_, mxid_domain = self.az.intent.parse_user_id(mxid)

0 commit comments

Comments
 (0)