matrix_sdk/test_utils/mocks/
oauth.rs1use ruma::{
18 api::client::discovery::get_authorization_server_metadata::v1::AuthorizationServerMetadata,
19 serde::Raw,
20};
21use serde_json::json;
22use url::Url;
23use wiremock::{
24 matchers::{method, path_regex},
25 Mock, MockBuilder, ResponseTemplate,
26};
27
28use super::{MatrixMock, MatrixMockServer, MockEndpoint};
29
30pub struct OAuthMockServer<'a> {
57 server: &'a MatrixMockServer,
58}
59
60impl<'a> OAuthMockServer<'a> {
61 pub(super) fn new(server: &'a MatrixMockServer) -> Self {
62 Self { server }
63 }
64
65 fn mock_endpoint<T>(&self, mock: MockBuilder, endpoint: T) -> MockEndpoint<'a, T> {
67 self.server.mock_endpoint(mock, endpoint)
68 }
69
70 pub fn server_metadata(&self) -> AuthorizationServerMetadata {
72 MockServerMetadataBuilder::new(&self.server.uri())
73 .build()
74 .deserialize()
75 .expect("mock OAuth 2.0 server metadata should deserialize successfully")
76 }
77}
78
79impl OAuthMockServer<'_> {
81 pub fn mock_server_metadata(&self) -> MockEndpoint<'_, ServerMetadataEndpoint> {
91 let mock = Mock::given(method("GET"))
92 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc2965/auth_metadata"));
93 self.mock_endpoint(mock, ServerMetadataEndpoint)
94 }
95
96 pub fn mock_registration(&self) -> MockEndpoint<'_, RegistrationEndpoint> {
99 let mock = Mock::given(method("POST")).and(path_regex(r"^/oauth2/registration"));
100 self.mock_endpoint(mock, RegistrationEndpoint)
101 }
102
103 pub fn mock_device_authorization(&self) -> MockEndpoint<'_, DeviceAuthorizationEndpoint> {
106 let mock = Mock::given(method("POST")).and(path_regex(r"^/oauth2/device"));
107 self.mock_endpoint(mock, DeviceAuthorizationEndpoint)
108 }
109
110 pub fn mock_token(&self) -> MockEndpoint<'_, TokenEndpoint> {
113 let mock = Mock::given(method("POST")).and(path_regex(r"^/oauth2/token"));
114 self.mock_endpoint(mock, TokenEndpoint)
115 }
116
117 pub fn mock_revocation(&self) -> MockEndpoint<'_, RevocationEndpoint> {
120 let mock = Mock::given(method("POST")).and(path_regex(r"^/oauth2/revoke"));
121 self.mock_endpoint(mock, RevocationEndpoint)
122 }
123}
124
125pub struct ServerMetadataEndpoint;
127
128impl<'a> MockEndpoint<'a, ServerMetadataEndpoint> {
129 pub fn ok(self) -> MatrixMock<'a> {
131 let metadata = MockServerMetadataBuilder::new(&self.server.uri()).build();
132 self.respond_with(ResponseTemplate::new(200).set_body_json(metadata))
133 }
134
135 pub fn ok_https(self) -> MatrixMock<'a> {
142 let issuer = self.server.uri().replace("http://", "https://");
143
144 let metadata = MockServerMetadataBuilder::new(&issuer).build();
145 self.respond_with(ResponseTemplate::new(200).set_body_json(metadata))
146 }
147
148 pub fn ok_without_device_authorization(self) -> MatrixMock<'a> {
151 let metadata = MockServerMetadataBuilder::new(&self.server.uri())
152 .without_device_authorization()
153 .build();
154 self.respond_with(ResponseTemplate::new(200).set_body_json(metadata))
155 }
156
157 pub fn ok_without_registration(self) -> MatrixMock<'a> {
160 let metadata =
161 MockServerMetadataBuilder::new(&self.server.uri()).without_registration().build();
162 self.respond_with(ResponseTemplate::new(200).set_body_json(metadata))
163 }
164}
165
166#[derive(Debug, Clone)]
169pub struct MockServerMetadataBuilder {
170 issuer: Url,
171 with_device_authorization: bool,
172 with_registration: bool,
173}
174
175impl MockServerMetadataBuilder {
176 pub fn new(issuer: &str) -> Self {
179 let issuer = Url::parse(issuer).expect("We should be able to parse the issuer");
180
181 Self { issuer, with_device_authorization: true, with_registration: true }
182 }
183
184 fn without_device_authorization(mut self) -> Self {
186 self.with_device_authorization = false;
187 self
188 }
189
190 fn without_registration(mut self) -> Self {
192 self.with_registration = false;
193 self
194 }
195
196 fn authorization_endpoint(&self) -> Url {
198 self.issuer.join("oauth2/authorize").unwrap()
199 }
200
201 fn token_endpoint(&self) -> Url {
203 self.issuer.join("oauth2/token").unwrap()
204 }
205
206 fn jwks_uri(&self) -> Url {
208 self.issuer.join("oauth2/keys.json").unwrap()
209 }
210
211 fn registration_endpoint(&self) -> Url {
213 self.issuer.join("oauth2/registration").unwrap()
214 }
215
216 fn account_management_uri(&self) -> Url {
218 self.issuer.join("account").unwrap()
219 }
220
221 fn device_authorization_endpoint(&self) -> Url {
223 self.issuer.join("oauth2/device").unwrap()
224 }
225
226 fn revocation_endpoint(&self) -> Url {
228 self.issuer.join("oauth2/revoke").unwrap()
229 }
230
231 pub fn build(&self) -> Raw<AuthorizationServerMetadata> {
233 let mut json_metadata = json!({
234 "issuer": self.issuer,
235 "authorization_endpoint": self.authorization_endpoint(),
236 "token_endpoint": self.token_endpoint(),
237 "response_types_supported": ["code"],
238 "response_modes_supported": ["query", "fragment"],
239 "grant_types_supported": ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code"],
240 "revocation_endpoint": self.revocation_endpoint(),
241 "code_challenge_methods_supported": ["S256"],
242 "account_management_uri": self.account_management_uri(),
243 "account_management_actions_supported": ["org.matrix.profile", "org.matrix.sessions_list", "org.matrix.session_view", "org.matrix.session_end", "org.matrix.deactivateaccount", "org.matrix.cross_signing_reset"],
244 "prompt_values_supported": ["create"],
245 });
246 let json_metadata_object = json_metadata.as_object_mut().unwrap();
247
248 if self.with_device_authorization {
249 json_metadata_object.insert(
250 "device_authorization_endpoint".to_owned(),
251 self.device_authorization_endpoint().as_str().into(),
252 );
253 }
254
255 if self.with_registration {
256 json_metadata_object.insert(
257 "registration_endpoint".to_owned(),
258 self.registration_endpoint().as_str().into(),
259 );
260 }
261
262 serde_json::from_value(json_metadata).unwrap()
263 }
264}
265
266pub struct RegistrationEndpoint;
268
269impl<'a> MockEndpoint<'a, RegistrationEndpoint> {
270 pub fn ok(self) -> MatrixMock<'a> {
272 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
273 "client_id": "test_client_id",
274 "client_id_issued_at": 1716375696,
275 })))
276 }
277}
278
279pub struct DeviceAuthorizationEndpoint;
281
282impl<'a> MockEndpoint<'a, DeviceAuthorizationEndpoint> {
283 pub fn ok(self) -> MatrixMock<'a> {
285 let issuer_url = Url::parse(&self.server.uri())
286 .expect("We should be able to parse the wiremock server URI");
287 let verification_uri = issuer_url.join("link").unwrap();
288 let mut verification_uri_complete = issuer_url.join("link").unwrap();
289 verification_uri_complete.set_query(Some("code=N32YVC"));
290
291 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
292 "device_code": "N8NAYD9fOhMulpm37mSthx0xSw2p7vdR",
293 "expires_in": 1200,
294 "interval": 5,
295 "user_code": "N32YVC",
296 "verification_uri": verification_uri,
297 "verification_uri_complete": verification_uri_complete,
298 })))
299 }
300}
301
302pub struct TokenEndpoint;
304
305impl<'a> MockEndpoint<'a, TokenEndpoint> {
306 pub fn ok(self) -> MatrixMock<'a> {
308 self.ok_with_tokens("1234", "ZYXWV")
309 }
310
311 pub fn ok_with_tokens(self, access_token: &str, refresh_token: &str) -> MatrixMock<'a> {
313 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
314 "access_token": access_token,
315 "expires_in": 300,
316 "refresh_token": refresh_token,
317 "token_type": "Bearer"
318 })))
319 }
320
321 pub fn access_denied(self) -> MatrixMock<'a> {
323 self.respond_with(ResponseTemplate::new(400).set_body_json(json!({
324 "error": "access_denied",
325 })))
326 }
327
328 pub fn expired_token(self) -> MatrixMock<'a> {
330 self.respond_with(ResponseTemplate::new(400).set_body_json(json!({
331 "error": "expired_token",
332 })))
333 }
334
335 pub fn invalid_grant(self) -> MatrixMock<'a> {
337 self.respond_with(ResponseTemplate::new(400).set_body_json(json!({
338 "error": "invalid_grant",
339 })))
340 }
341}
342
343pub struct RevocationEndpoint;
345
346impl<'a> MockEndpoint<'a, RevocationEndpoint> {
347 pub fn ok(self) -> MatrixMock<'a> {
349 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
350 }
351}