matrix_sdk/test_utils/mocks/
oauth.rs1use ruma::{
18 api::client::discovery::get_authorization_server_metadata::msc2965::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
71impl OauthMockServer<'_> {
73 pub fn mock_server_metadata(&self) -> MockEndpoint<'_, ServerMetadataEndpoint> {
83 let mock = Mock::given(method("GET"))
84 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc2965/auth_metadata"));
85 self.mock_endpoint(mock, ServerMetadataEndpoint)
86 }
87
88 pub fn mock_registration(&self) -> MockEndpoint<'_, RegistrationEndpoint> {
91 let mock = Mock::given(method("POST")).and(path_regex(r"^/oauth2/registration"));
92 self.mock_endpoint(mock, RegistrationEndpoint)
93 }
94
95 pub fn mock_device_authorization(&self) -> MockEndpoint<'_, DeviceAuthorizationEndpoint> {
98 let mock = Mock::given(method("POST")).and(path_regex(r"^/oauth2/device"));
99 self.mock_endpoint(mock, DeviceAuthorizationEndpoint)
100 }
101
102 pub fn mock_token(&self) -> MockEndpoint<'_, TokenEndpoint> {
105 let mock = Mock::given(method("POST")).and(path_regex(r"^/oauth2/token"));
106 self.mock_endpoint(mock, TokenEndpoint)
107 }
108
109 pub fn mock_revocation(&self) -> MockEndpoint<'_, RevocationEndpoint> {
112 let mock = Mock::given(method("POST")).and(path_regex(r"^/oauth2/revoke"));
113 self.mock_endpoint(mock, RevocationEndpoint)
114 }
115}
116
117pub struct ServerMetadataEndpoint;
119
120impl<'a> MockEndpoint<'a, ServerMetadataEndpoint> {
121 pub fn ok(self) -> MatrixMock<'a> {
123 let metadata = MockServerMetadataBuilder::new(&self.server.uri()).build();
124 self.respond_with(ResponseTemplate::new(200).set_body_json(metadata))
125 }
126
127 pub fn ok_https(self) -> MatrixMock<'a> {
134 let issuer = self.server.uri().replace("http://", "https://");
135
136 let metadata = MockServerMetadataBuilder::new(&issuer).build();
137 self.respond_with(ResponseTemplate::new(200).set_body_json(metadata))
138 }
139
140 pub fn ok_without_device_authorization(self) -> MatrixMock<'a> {
143 let metadata = MockServerMetadataBuilder::new(&self.server.uri())
144 .without_device_authorization()
145 .build();
146 self.respond_with(ResponseTemplate::new(200).set_body_json(metadata))
147 }
148
149 pub fn ok_without_registration(self) -> MatrixMock<'a> {
152 let metadata =
153 MockServerMetadataBuilder::new(&self.server.uri()).without_registration().build();
154 self.respond_with(ResponseTemplate::new(200).set_body_json(metadata))
155 }
156}
157
158#[derive(Debug, Clone)]
161pub struct MockServerMetadataBuilder {
162 issuer: Url,
163 with_device_authorization: bool,
164 with_registration: bool,
165}
166
167impl MockServerMetadataBuilder {
168 pub fn new(issuer: &str) -> Self {
171 let issuer = Url::parse(issuer).expect("We should be able to parse the issuer");
172
173 Self { issuer, with_device_authorization: true, with_registration: true }
174 }
175
176 fn without_device_authorization(mut self) -> Self {
178 self.with_device_authorization = false;
179 self
180 }
181
182 fn without_registration(mut self) -> Self {
184 self.with_registration = false;
185 self
186 }
187
188 fn authorization_endpoint(&self) -> Url {
190 self.issuer.join("oauth2/authorize").unwrap()
191 }
192
193 fn token_endpoint(&self) -> Url {
195 self.issuer.join("oauth2/token").unwrap()
196 }
197
198 fn jwks_uri(&self) -> Url {
200 self.issuer.join("oauth2/keys.json").unwrap()
201 }
202
203 fn registration_endpoint(&self) -> Url {
205 self.issuer.join("oauth2/registration").unwrap()
206 }
207
208 fn account_management_uri(&self) -> Url {
210 self.issuer.join("account").unwrap()
211 }
212
213 fn device_authorization_endpoint(&self) -> Url {
215 self.issuer.join("oauth2/device").unwrap()
216 }
217
218 fn revocation_endpoint(&self) -> Url {
220 self.issuer.join("oauth2/revoke").unwrap()
221 }
222
223 pub fn build(&self) -> Raw<AuthorizationServerMetadata> {
225 let mut json_metadata = json!({
226 "issuer": self.issuer,
227 "authorization_endpoint": self.authorization_endpoint(),
228 "token_endpoint": self.token_endpoint(),
229 "response_types_supported": ["code"],
230 "response_modes_supported": ["query", "fragment"],
231 "grant_types_supported": ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code"],
232 "revocation_endpoint": self.revocation_endpoint(),
233 "code_challenge_methods_supported": ["S256"],
234 "account_management_uri": self.account_management_uri(),
235 "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"],
236 "prompt_values_supported": ["create"],
237 });
238 let json_metadata_object = json_metadata.as_object_mut().unwrap();
239
240 if self.with_device_authorization {
241 json_metadata_object.insert(
242 "device_authorization_endpoint".to_owned(),
243 self.device_authorization_endpoint().as_str().into(),
244 );
245 }
246
247 if self.with_registration {
248 json_metadata_object.insert(
249 "registration_endpoint".to_owned(),
250 self.registration_endpoint().as_str().into(),
251 );
252 }
253
254 serde_json::from_value(json_metadata).unwrap()
255 }
256}
257
258pub struct RegistrationEndpoint;
260
261impl<'a> MockEndpoint<'a, RegistrationEndpoint> {
262 pub fn ok(self) -> MatrixMock<'a> {
264 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
265 "client_id": "test_client_id",
266 "client_id_issued_at": 1716375696,
267 })))
268 }
269}
270
271pub struct DeviceAuthorizationEndpoint;
273
274impl<'a> MockEndpoint<'a, DeviceAuthorizationEndpoint> {
275 pub fn ok(self) -> MatrixMock<'a> {
277 let issuer_url = Url::parse(&self.server.uri())
278 .expect("We should be able to parse the wiremock server URI");
279 let verification_uri = issuer_url.join("link").unwrap();
280 let mut verification_uri_complete = issuer_url.join("link").unwrap();
281 verification_uri_complete.set_query(Some("code=N32YVC"));
282
283 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
284 "device_code": "N8NAYD9fOhMulpm37mSthx0xSw2p7vdR",
285 "expires_in": 1200,
286 "interval": 5,
287 "user_code": "N32YVC",
288 "verification_uri": verification_uri,
289 "verification_uri_complete": verification_uri_complete,
290 })))
291 }
292}
293
294pub struct TokenEndpoint;
296
297impl<'a> MockEndpoint<'a, TokenEndpoint> {
298 pub fn ok(self) -> MatrixMock<'a> {
300 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
301 "access_token": "1234",
302 "expires_in": 300,
303 "refresh_token": "ZYXWV",
304 "token_type": "Bearer"
305 })))
306 }
307
308 pub fn access_denied(self) -> MatrixMock<'a> {
310 self.respond_with(ResponseTemplate::new(400).set_body_json(json!({
311 "error": "access_denied",
312 })))
313 }
314
315 pub fn expired_token(self) -> MatrixMock<'a> {
317 self.respond_with(ResponseTemplate::new(400).set_body_json(json!({
318 "error": "expired_token",
319 })))
320 }
321
322 pub fn invalid_grant(self) -> MatrixMock<'a> {
324 self.respond_with(ResponseTemplate::new(400).set_body_json(json!({
325 "error": "invalid_grant",
326 })))
327 }
328}
329
330pub struct RevocationEndpoint;
332
333impl<'a> MockEndpoint<'a, RevocationEndpoint> {
334 pub fn ok(self) -> MatrixMock<'a> {
336 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
337 }
338}