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}