schul_cloud_api/
api.rs

1use crate::errors::Errors;
2use crate::networking::connection::{Config, Connection, MessageSource};
3use crate::networking::data::channel_info::Channel;
4use crate::networking::data::companies::Company;
5use crate::networking::data::conversation_info::Conversation;
6use crate::networking::data::messages::{File, Message};
7use crate::networking::data::user::UserInfo;
8use base64::Engine;
9use openssl::error::ErrorStack;
10use openssl::pkey::Private;
11use openssl::rsa::{Padding, Rsa};
12use openssl::symm::{decrypt, Cipher};
13use serde_json::Value;
14use std::collections::HashMap;
15
16const BASE64ENGINE: base64::engine::GeneralPurpose = base64::engine::GeneralPurpose::new(&base64::alphabet::STANDARD, base64::engine::GeneralPurposeConfig::new());
17const MESSAGES_PER_REQUEST: u32 = 64;
18const CONVERSATIONS_PER_REQUEST: u32 = 64;
19
20pub struct Api {
21    conn: Connection,
22    user_info: Option<UserInfo>,
23    logged_in: bool,
24    private_key: Option<Rsa<Private>>,
25    channel_infos: Vec<Channel>,
26    conversation_infos: Vec<Conversation>,
27    key_store: HashMap<ChatID, Vec<u8>>,
28    message_store: HashMap<ChatID, Vec<Message>>,
29    companies: Vec<Company>,
30    passphrase: Option<String>,
31}
32
33impl Api {
34    async fn get_private_key(&mut self, passphrase: String) -> Result<Rsa<Private>, Errors> {
35        if let Some(key) = self.private_key.clone() {
36            return Ok(key);
37        }
38        let key_info = self.conn.get_private_key().await?;
39        let private_key_json: Result<Value, serde_json::Error> = serde_json::from_str(&*key_info.private_key);
40        if let Err(e) = private_key_json {
41            return Err(Errors::JsonDeserializeError(e));
42        }
43        let private_key_json = private_key_json.unwrap();
44        let encrypted_pem_key = private_key_json.get("private");
45        if encrypted_pem_key.is_none() {
46            return Err(Errors::OtherErrors("Couldn't load private key from field \"private\"".to_string()));
47        }
48        let encrypted_pem_key = encrypted_pem_key.unwrap();
49        let encrypted_pem_key: Option<&str> = encrypted_pem_key.as_str();
50        if encrypted_pem_key.is_none() {
51            return Err(Errors::OtherErrors("Private key is not in PEM format".to_string()));
52        }
53        let encrypted_pem_key = encrypted_pem_key.unwrap();
54        let key = Rsa::private_key_from_pem_passphrase(encrypted_pem_key.as_bytes(), passphrase.as_bytes());
55        if let Err(e) = key {
56            return Err(Errors::EncryptionError(e))
57        }
58        let key = key.unwrap();
59        self.private_key = Some(key.clone());
60        Ok(key)
61    }
62    async fn decrypt_key(&mut self, key: String) -> Result<Vec<u8>, Errors> {
63        if self.passphrase.is_none() {
64            return Err(Errors::ValueError("Passphrase is None! Passphrase needs to be set before accessing anything encrypted".to_string()))
65        }
66        let encrypted_data = BASE64ENGINE.decode(key);
67        if let Err(e) = encrypted_data {
68            return Err(Errors::Base64Error(e));
69        }
70        let encrypted_data = encrypted_data.unwrap();
71        let mut result_buf: Vec<u8> = Vec::new();
72        result_buf.resize(encrypted_data.clone().len(), 0);
73        let private_key = self.get_private_key(self.passphrase.clone().unwrap()).await?;
74        let decrypted_key_length = private_key.private_decrypt(&*encrypted_data, &mut result_buf, Padding::PKCS1_OAEP);
75        if let Err(e) = decrypted_key_length {
76            return Err(Errors::EncryptionError(e));
77        }
78        let decrypted_key_length = decrypted_key_length.unwrap();
79        result_buf.resize(decrypted_key_length, 0);
80        Ok(result_buf)
81    }
82    async fn decrypt(&mut self, chat: ChatID, data: Vec<u8>, iv: Option<String>) -> Result<Vec<u8>, Errors> {
83        if !self.key_store.contains_key(&chat) {
84            if chat.r#type == ChatType::Channel {
85                let channel = self.channel_infos.iter()
86                    .find(|channel| channel.id == chat.id);
87                if channel.is_none() {
88                    return Err(Errors::ValueError("Invalid channel id".to_string()));
89                }
90                let channel = channel.unwrap();
91                if !channel.encrypted.clone() {
92                    return Ok(data);
93                }
94                if channel.key.is_none() {
95                    return Err(Errors::ValueError("Key is none for channel \"".to_string() + &*channel.name + "\""))
96                }
97                let decrypted_key = self.decrypt_key(channel.key.clone().unwrap()).await?;
98                self.key_store.insert(chat.clone(), decrypted_key);
99            } else if chat.r#type == ChatType::Conversation {
100                let conversation = self.conversation_infos
101                    .iter().find(|conversation| conversation.id == chat.id);
102                if conversation.is_none() {
103                    return Err(Errors::ValueError("Invalid conversation id".to_string()));
104                }
105                let conversation = conversation.unwrap();
106                if !conversation.encrypted.clone() {
107                    return Ok(data);
108                }
109                let decrypted_key = self.decrypt_key(conversation.key.clone().unwrap()).await?;
110                self.key_store.insert(chat.clone(), decrypted_key);
111            } else {
112                return Err(Errors::ValueError(format!("Unknown chat type: {:?}", chat.r#type)))
113            }
114        }
115
116        //actual decryption
117        let key = self.key_store.get(&chat).expect("Key is none although key is set in keystore.");
118        let decrypted: Result<Vec<u8>, ErrorStack> = match iv {
119            None => decrypt(Cipher::aes_256_cbc(), key, None, &*data),
120            Some(iv) => match hex::decode(iv) {
121                Err(e) => return Err(Errors::HexError(e)),
122                Ok(iv) => decrypt(Cipher::aes_256_cbc(), key, Some(&*iv), &*data)
123            }
124        };
125        match decrypted {
126            Err(e) => Err(Errors::EncryptionError(e)),
127            Ok(v) => Ok(v)
128        }
129    }
130    async fn decrypt_message(&mut self, chat: ChatID, message: Message) -> Result<Option<String>, Errors>{
131        if message.encrypted.is_none() || !message.encrypted.unwrap() || message.text.is_none() || message.text.clone().unwrap().len() == 0{
132            return Ok(message.text);
133        }
134        let data = match hex::decode(message.text.unwrap()) {
135            Err(e) => return Err(Errors::HexError(e)),
136            Ok(d) => d
137        };
138        let decrypted = self.decrypt(chat, data, message.iv).await?;
139        match String::from_utf8(decrypted) {
140            Err(e) => Err(Errors::StringDecodeError(e)),
141            Ok(s) => Ok(Some(s))
142        }
143    }
144}
145impl Api {
146    pub fn new() -> Self {
147        Api {
148            conn: Connection::new(Config::new("https://api.stashcat.com/".to_string())),
149            user_info: None,
150            logged_in: false,
151            private_key: None,
152            channel_infos: Vec::new(),
153            conversation_infos: Vec::new(),
154            key_store: HashMap::new(),
155            message_store: HashMap::new(),
156            companies: Vec::new(),
157            passphrase: None,
158        }
159    }
160    pub async fn new_logged_in(device_id: String, client_key: String) -> Result<Self, Errors> {
161        let mut api = Api {
162            conn: Connection::new(Config{
163                base_url: "https://api.stashcat.com/".to_string(),
164                device_id,
165                client_key: Some(client_key),
166            }),
167            user_info: None,
168            logged_in: true,
169            private_key: None,
170            channel_infos: Vec::new(),
171            conversation_infos: Vec::new(),
172            key_store: HashMap::new(),
173            message_store: HashMap::new(),
174            companies: Vec::new(),
175            passphrase: None,
176        };
177        api.update_user_info().await?;
178        api.post_login().await?;
179        Ok(api)
180    }
181    pub async fn email_password_login(&mut self, email: String, password: String, app_name: String) -> Result<(), Errors> {
182        let dat = self.conn.email_password_login(email, password, app_name).await?;
183        self.user_info = Some(dat);
184        self.post_login().await?;
185        Ok(())
186    }
187    pub async fn update_user_info(&mut self) -> Result<(), Errors>{
188        self.user_info = Some(self.conn.get_user_info().await?);
189        Ok(())
190    }
191    async fn post_login(&mut self) -> Result<(), Errors>{
192        self.logged_in = true;
193        self.companies = self.conn.get_companies().await?;
194        for company in self.companies.clone() {
195            self.channel_infos.extend(self.conn.get_channels(company.id).await?);
196        };
197        let mut requested_conversations: u64= 0;
198        while self.conversation_infos.clone().len() == requested_conversations as usize {
199            self.conversation_infos.extend(self.conn.get_conversations(CONVERSATIONS_PER_REQUEST, requested_conversations, "0".to_string(), "[\"favorite_desc\",\"last_action_desc\"]".to_string()).await?);
200            requested_conversations += u64::from(CONVERSATIONS_PER_REQUEST);
201        };
202        Ok(())
203    }
204    pub fn set_passphrase(&mut self, passphrase: String) {self.passphrase = Some(passphrase)}
205
206    pub fn get_channel_ids(&self) -> Vec<String> {
207        self.channel_infos.iter().map(|channel| channel.id.clone()).collect()
208    }
209    pub fn get_conversation_ids(&self) -> Vec<String> {
210        self.conversation_infos
211            .iter()
212            .map(|conversation| conversation.id.clone())
213            .collect()
214    }
215    pub fn get_channels(&self) -> Vec<Channel> {
216        self.channel_infos.clone()
217    }
218    pub fn get_conversations(&self) -> Vec<Conversation> {
219        self.conversation_infos.clone()
220    }
221    pub async fn get_messages(&mut self, chat: ChatID) -> Result<Vec<Message>, Errors>{
222        if self.message_store.contains_key(&chat) {
223            return Ok(self.message_store.get(&chat).expect("Message Store contains Chat but has no data").clone());
224        }
225        let mut requested_message_count: u64 = 0;
226        let mut messages: Vec<Message> = Vec::new();
227        while messages.len() == requested_message_count as usize{
228            let mut new_messages: Vec<Message> = self.conn.get_messages(chat.id.clone(),
229                                                                        match chat.r#type {
230                                                                            ChatType::Channel => MessageSource::Channel,
231                                                                            ChatType::Conversation => MessageSource::Conversation
232                                                                        },
233                                                                        MESSAGES_PER_REQUEST,
234                                                                        requested_message_count,
235            ).await?;
236            requested_message_count += MESSAGES_PER_REQUEST as u64;
237            new_messages.extend(messages);
238            messages = new_messages;
239        }
240        self.message_store.insert(chat, messages.clone());
241        Ok(messages)
242    }
243    pub async fn get_decrypted_messages(&mut self, source: ChatID) -> Result<Vec<Option<String>>, Errors> {
244        let messages = self.get_messages(source.clone()).await?;
245        let mut decrypted: Vec<Option<String>> = Vec::new();
246        for message in messages {
247            decrypted.push(self.decrypt_message(source.clone(), message.clone()).await?);
248        }
249        Ok(decrypted)
250    }
251    pub async fn download_file(&mut self, chat: ChatID, file: File) -> Result<Vec<u8>, Errors> {
252        let encrypted = self.conn.download_file(file.id).await?;
253        if !file.encrypted {
254            return Ok(encrypted);
255        }
256        Ok(self.decrypt(chat, encrypted, file.e2e_iv).await?)
257    }
258    pub fn is_logged_in(&self) -> bool {self.logged_in}
259    pub fn has_passphrase_set(&self) -> bool {!self.passphrase.is_none()}
260    pub fn get_chat_name(&self, chat: ChatID) -> Result<String, Errors> {
261        match chat.r#type {
262            ChatType::Conversation => {
263                let conversation = self.conversation_infos.iter().find(|conversation| conversation.id == chat.id);
264                match conversation {
265                    None => Err(Errors::ValueError(format!("No conversation found with id \"{}\"", chat.id))),
266                    Some(conversation) => match conversation.name.clone() {
267                        Some(s) => Ok(s),
268                        None => Err(Errors::ValueError(format!("Conversation with id {} has no name", chat.id)))
269                    }
270                }
271            }
272            ChatType::Channel => {
273                let channel = self.channel_infos.iter().find(|channel| channel.id == chat.id);
274                match channel {
275                    None => Err(Errors::ValueError(format!("No channel found with id \"{}\"", chat.id))),
276                    Some(channel) => Ok(channel.name.clone())
277                }
278            }
279        }
280    }
281}
282#[derive(Hash, Debug, Clone, Eq, PartialEq)]
283pub struct  ChatID {
284    r#type: ChatType,
285    id: String,
286}
287impl ChatID {
288    pub fn new(r#type: ChatType, id: String) -> Self {
289        ChatID {
290            r#type,
291            id,
292        }
293    }
294    pub fn from_channel(channel: Channel) -> Self {
295        Self::new(ChatType::Channel, channel.id)
296    }
297    pub fn from_conversation(conversation: Conversation) -> Self {
298        Self::new(ChatType::Conversation, conversation.id)
299    }
300}
301#[derive(Hash, Debug, Clone, Eq, PartialEq)]
302pub enum ChatType {
303    Conversation,
304    Channel,
305}