lib/mattermost/session.rs
1//! This module implement mattermost session management.
2//!
3//! A session may be created via login:
4//! ```
5//! # use httpmock::prelude::*;
6//! # let server = MockServer::start();
7//! # let server_mock = server.mock(|expect, resp_with| {
8//! # expect.method(POST).path("//api/v4/users/login").json_body(
9//! # serde_json::json!({"login_id":"username","password":"passwordtext"}
10//! # ),
11//! # );
12//! # resp_with
13//! # .status(200)
14//! # .header("content-type", "application/json")
15//! # .header("Token", "xyzxyz");
16//! # });
17//! use lib::{Session,BaseSession};
18//! let mut session = Session::new(&server.url("/"))
19//! .with_credentials("username", "passwordtext");
20//! session.login()?;
21//! let token = session.token()?;
22//! # server_mock.assert();
23//! # Ok::<(), anyhow::Error>(())
24//! ```
25//!
26//! Or via a private access token:
27//!
28//! ```
29//! use lib::{Session,BaseSession};
30//! let mut session = Session::new("https://mattermost.example.com")
31//! .with_token("sdqgserdfmkjqBXHZFH:qgjr");
32//! let token = session.token()?;
33//! # Ok::<(), anyhow::Error>(())
34//! ```
35//!
36
37use anyhow::{anyhow, Result};
38use serde::{Deserialize, Serialize};
39use std::mem;
40
41/// Trait implementing function necessary to establish a session (getting a authenticating token).
42pub trait BaseSession {
43 /// Get session token
44 fn token(&self) -> Result<&str>;
45
46 /// Get session `base_uri`
47 fn base_uri(&self) -> &str;
48
49 /// Login to mattermost instance
50 fn login(&mut self) -> Result<()>;
51}
52
53/// Base Session without authentication management
54pub struct Session {
55 #[allow(rustdoc::bare_urls)]
56 /// base URL of the mattermost server like https://mattermost.example.com
57 base_uri: String,
58}
59
60/// Implement [Session] authenticated with a private access token.
61pub struct SessionWithToken {
62 #[allow(rustdoc::bare_urls)]
63 /// base URL of the mattermost server like https://mattermost.example.com
64 pub base_uri: String,
65 /// private access token for current user on the `base_uri` mattermost instance
66 /// (either permanent and given at init or renewable with the help of login function)
67 token: String,
68}
69///
70/// Implement a session authenticated with a login and password
71pub struct SessionWithLogin {
72 #[allow(rustdoc::bare_urls)]
73 /// base URL of the mattermost server like https://mattermost.example.com
74 pub base_uri: String,
75 /// private access token for current user on the `base_uri` mattermost instance
76 /// (either permanent and given at init or renewable with the help of login function)
77 token: Option<String>,
78 /// user login
79 user: String,
80 /// user password
81 password: String,
82}
83
84#[derive(Serialize, Deserialize)]
85struct LoginData {
86 login_id: String,
87 password: String,
88}
89
90impl Session {
91 /// Create new empty [Session] to the `base_uri` mattermost server
92 pub fn new(base_uri: &str) -> Self {
93 Session {
94 base_uri: base_uri.into(),
95 }
96 }
97 /// Add existing token to current [Session]
98 pub fn with_token(&mut self, token: &str) -> SessionWithToken {
99 SessionWithToken {
100 token: token.into(),
101 base_uri: mem::take(&mut self.base_uri),
102 }
103 }
104 /// Add login credentials to current [Session]
105 pub fn with_credentials(&mut self, user_login: &str, password: &str) -> SessionWithLogin {
106 SessionWithLogin {
107 user: user_login.into(),
108 password: password.into(),
109 token: None,
110 base_uri: mem::take(&mut self.base_uri),
111 }
112 }
113}
114
115impl BaseSession for SessionWithToken {
116 fn token(&self) -> Result<&str> {
117 Ok(&self.token)
118 }
119 fn base_uri(&self) -> &str {
120 &self.base_uri
121 }
122 fn login(&mut self) -> Result<()> {
123 Ok(())
124 }
125}
126
127impl BaseSession for SessionWithLogin {
128 fn token(&self) -> Result<&str> {
129 if let Some(token) = &self.token {
130 Ok(token)
131 } else {
132 Err(anyhow!("No token available, did login succeed ?"))
133 }
134 }
135 fn base_uri(&self) -> &str {
136 &self.base_uri
137 }
138
139 fn login(&mut self) -> Result<()> {
140 let uri = self.base_uri.to_owned() + "/api/v4/users/login";
141 let response = ureq::post(&uri).send_json(serde_json::to_value(LoginData {
142 login_id: self.user.clone(),
143 password: self.password.clone(),
144 })?)?;
145 if let Some(token) = response.header("Token") {
146 self.token = Some(token.into());
147 Ok(())
148 } else {
149 Err(anyhow!(
150 "Login authentication failed (response: {})",
151 response.into_string()?
152 ))
153 }
154 }
155}
156
157#[cfg(test)]
158mod should {
159 use super::*;
160 use httpmock::prelude::*;
161 use test_log::test; // Automatically trace tests
162 #[test]
163 fn login_with_success() -> Result<()> {
164 // Start a lightweight mock server.
165 let server = MockServer::start();
166
167 // Create a mock on the server.
168 let server_mock = server.mock(|expect, resp_with| {
169 expect.method(POST).path("/api/v4/users/login").json_body(
170 serde_json::json!({"login_id":"username","password":"passwordtext"}
171 ),
172 );
173 resp_with
174 .status(200)
175 .header("content-type", "application/json")
176 .header("Token", "xyzxyz");
177 });
178
179 let mut session =
180 Session::new(&server.url("")).with_credentials("username", "passwordtext");
181
182 session.login()?;
183
184 // Send an HTTP request to the mock server. This simulates your code.
185 //let token = login(&server.url(""), Some("username"), Some("passwordtext"))?;
186
187 // Ensure the specified mock was called exactly one time (or fail with a detailed error description).
188 server_mock.assert();
189 // Ensure the mock server did respond as specified.
190 assert_eq!(session.token()?, "xyzxyz");
191 assert_eq!(session.base_uri, server.url(""));
192 Ok(())
193 }
194 #[test]
195 fn return_token() -> Result<()> {
196 let session = Session::new("https://mattermost.example.com").with_token("xyzxyz");
197 assert_eq!(session.base_uri, "https://mattermost.example.com");
198 assert_eq!(session.token()?, "xyzxyz");
199 Ok(())
200 }
201}