From d1b9e816ddcbc8e3ccdcda714ceb930172bcea77 Mon Sep 17 00:00:00 2001
From: Ash <spook123@gmail.com>
Date: Fri, 27 Mar 2020 17:07:48 +0800
Subject: [PATCH] move secrets to a separate config

---
 Cargo.toml              |   1 +
 src/config.rs           |  13 +++++-
 src/ldap.rs             |  13 ++----
 src/main.rs             |  19 ++++----
 src/reaction_roles.rs   |   2 +-
 src/token_management.rs |   8 ++--
 src/user_management.rs  | 100 +++++++++++++---------------------------
 src/voting.rs           |  47 ++++++++-----------
 8 files changed, 84 insertions(+), 119 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 6b2dc22..bcabb5a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,3 +22,4 @@ diesel = { version = "1.4.3", features = ["sqlite"] }
 ldap3 = "0.6"
 url = "^2.1"
 regex = "^1.3"
+toml = "0.5"
diff --git a/src/config.rs b/src/config.rs
index 86287a5..b14edaf 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,6 +1,7 @@
 use indexmap::IndexMap;
 use serde::Deserialize;
 use serde_yaml;
+use toml;
 use serenity::model::id;
 use std::fs;
 
@@ -9,6 +10,11 @@ lazy_static! {
     pub static ref CONFIG: UccbotConfig = serde_yaml::from_str(&CONFIG_FILE).unwrap();
 }
 
+lazy_static! {
+    static ref SECRETS_FILE: String = fs::read_to_string("secrets.toml").unwrap();
+    pub static ref SECRETS: UccbotSecrets = toml::from_str(&SECRETS_FILE).unwrap();
+}
+
 #[derive(Debug, Deserialize)]
 pub struct UccbotConfig {
     pub server_id: u64,
@@ -32,7 +38,6 @@ pub struct UccbotConfig {
     pub react_role_messages: Vec<ReactionMapping>,
     #[serde(default = "ldap_bind_address")]
     pub bind_address: String,
-    pub ldap_pass: String,
 }
 
 pub fn ldap_bind_address() -> String {
@@ -52,6 +57,12 @@ impl UccbotConfig {
     }
 }
 
+#[derive(Debug, Deserialize)]
+pub struct UccbotSecrets {
+    pub ldap_pass: String,
+    pub discord_token: String,
+}
+
 pub type ReactRoleMap = IndexMap<String, id::RoleId>;
 
 #[derive(Debug, Deserialize, Clone)]
diff --git a/src/ldap.rs b/src/ldap.rs
index bf9aa12..44a6ebd 100644
--- a/src/ldap.rs
+++ b/src/ldap.rs
@@ -1,5 +1,5 @@
 use ldap3::{LdapConn, LdapConnSettings, Scope, SearchEntry};
-use crate::config::CONFIG;
+use crate::config::{CONFIG, SECRETS};
 
 #[derive(Debug)]
 pub struct LDAPUser {
@@ -14,7 +14,7 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> {
         .expect("Unable to connect to LDAP");
     ldap.simple_bind(
         "cn=ucc-discord-bot,cn=Users,dc=ad,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au",
-        &CONFIG.ldap_pass,
+        &SECRETS.ldap_pass,
     )
     .expect("Unable to attempt to bind to LDAP")
     .success()
@@ -29,8 +29,8 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> {
         .expect("LDAP error")
         .success()
         .expect("LDAP search error");
-    if rs.len() != 1 {
-        return None;
+    if rs.is_empty() {
+        return None
     }
     let result = SearchEntry::construct(rs[0].clone()).attrs;
     Some(LDAPUser {
@@ -50,10 +50,7 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> {
 }
 
 pub fn ldap_exists(username: &str) -> bool {
-    match ldap_search(username) {
-        Some(_) => true,
-        None => false,
-    }
+    ldap_search(username).is_some()
 }
 
 #[derive(Debug)]
diff --git a/src/main.rs b/src/main.rs
index 439e38e..e4ded92 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,7 +13,7 @@ extern crate diesel;
 extern crate ldap3;
 
 use simplelog::*;
-use std::fs::{read_to_string, File};
+use std::fs::File;
 
 use chrono::prelude::Utc;
 use serenity::{
@@ -32,7 +32,7 @@ mod token_management;
 mod user_management;
 mod voting;
 
-use config::CONFIG;
+use config::{CONFIG, SECRETS};
 use reaction_roles::{add_role_by_reaction, remove_role_by_reaction};
 use util::get_string_from_react;
 
@@ -130,12 +130,12 @@ impl EventHandler for Handler {
             Ok(message) => match get_message_type(&message) {
                 MessageType::RoleReactMessage if add_reaction.user_id.0 != CONFIG.bot_id => {
                     add_role_by_reaction(&ctx, message, add_reaction);
-                    return;
+                    return
                 }
                 _ if message.author.id.0 != CONFIG.bot_id
                     || add_reaction.user_id == CONFIG.bot_id =>
                 {
-                    return;
+                    return
                 }
                 MessageType::Motion => voting::reaction_add(ctx, add_reaction),
                 MessageType::LogReact => {
@@ -146,7 +146,7 @@ impl EventHandler for Handler {
                             "The logreact message {} just tried to use is too old",
                             react_user.name
                         );
-                        return;
+                        return
                     }
                     info!(
                         "The react {} just added is {:?}. In full: {:?}",
@@ -172,12 +172,12 @@ impl EventHandler for Handler {
             Ok(message) => match get_message_type(&message) {
                 MessageType::RoleReactMessage if removed_reaction.user_id != CONFIG.bot_id => {
                     remove_role_by_reaction(&ctx, message, removed_reaction);
-                    return;
+                    return
                 }
                 _ if message.author.id.0 != CONFIG.bot_id
                     || removed_reaction.user_id == CONFIG.bot_id =>
                 {
-                    return;
+                    return
                 }
                 MessageType::Motion => voting::reaction_remove(ctx, removed_reaction),
                 _ => {}
@@ -222,13 +222,10 @@ fn main() {
     ])
     .unwrap();
 
-    // Configure the client with your Discord bot token in the environment.
-    let token = read_to_string("discord_token").unwrap();
-
     // Create a new instance of the Client, logging in as a bot. This will
     // automatically prepend your bot token with "Bot ", which is a requirement
     // by Discord for bot users.
-    let mut client = Client::new(&token, Handler).expect("Err creating client");
+    let mut client = Client::new(&SECRETS.discord_token, Handler).expect("Err creating client");
 
     // Finally, start a single shard, and start listening to events.
     //
diff --git a/src/reaction_roles.rs b/src/reaction_roles.rs
index f0f1b38..fbc188d 100644
--- a/src/reaction_roles.rs
+++ b/src/reaction_roles.rs
@@ -97,7 +97,7 @@ pub fn sync_all_role_reactions(ctx: &Context) {
         for react in &message.reactions {
             let react_as_string = get_string_from_react(&react.reaction_type);
             if mapping.contains_key(&react_as_string) {
-                continue;
+                continue
             }
             info!(
                 "    message #{}: Removing non-role react '{}'",
diff --git a/src/token_management.rs b/src/token_management.rs
index d32a067..49a5039 100644
--- a/src/token_management.rs
+++ b/src/token_management.rs
@@ -58,6 +58,8 @@ impl std::fmt::Display for TokenError {
     }
 }
 
+pub static TOKEN_LIFETIME: i64 = 300; // 5 minutes
+
 pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String, TokenError> {
     guard!(let Some(token) = text_decrypt(encrypted_token) else {
         return Err(TokenError::TokenInvalid)
@@ -73,15 +75,15 @@ pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String,
     let token_username = token_components[2];
     if token_discord_user != discord_user.id.0.to_string() {
         warn!("... attempt failed : DiscordID mismatch");
-        return Err(TokenError::DiscordIdMismatch);
+        return Err(TokenError::DiscordIdMismatch)
     }
     let time_delta_seconds = Utc::now().timestamp() - token_timestamp.timestamp();
-    if time_delta_seconds > 5 * 60 {
+    if time_delta_seconds > TOKEN_LIFETIME {
         warn!(
             "... attempt failed : token expired ({} seconds old)",
             time_delta_seconds
         );
-        return Err(TokenError::TokenExpired);
+        return Err(TokenError::TokenExpired)
     }
     info!(
         "... verification successful (token {} seconds old)",
diff --git a/src/user_management.rs b/src/user_management.rs
index 2c31a17..4dca819 100644
--- a/src/user_management.rs
+++ b/src/user_management.rs
@@ -34,7 +34,7 @@ pub fn new_member(ctx: &Context, mut new_member: Member) {
 
     if let Err(why) = new_member.add_role(&ctx.http, CONFIG.unregistered_member_role) {
         error!("Error adding user role: {:?}", why);
-    };
+    }
 }
 
 fn member_nickname(member: &database::Member) -> String {
@@ -78,7 +78,7 @@ impl Commands {
                 &ctx.http,
                 format!("Usage: {}register <username>", CONFIG.command_prefix)
             );
-            return;
+            return
         }
         if RESERVED_NAMES.contains(&account_name) || database::username_exists(account_name) {
             send_message!(
@@ -88,7 +88,7 @@ impl Commands {
                     .choose(&mut rand::thread_rng())
                     .expect("We couldn't get any sass")
             );
-            return;
+            return
         }
         if !ldap_exists(account_name) {
             send_message!(
@@ -99,7 +99,7 @@ impl Commands {
                     account_name
                 )
             );
-            return;
+            return
         }
         send_message!(
             msg.channel_id,
@@ -200,7 +200,7 @@ impl Commands {
                 &ctx.http,
                 "Sorry, I couldn't find that profile (you need to !register for a profile)"
             );
-            return;
+            return
         }
         let member = possible_member.unwrap();
         let result = msg.channel_id.send_message(&ctx.http, |m| {
@@ -250,29 +250,15 @@ impl Commands {
     }
     pub fn set_info(ctx: Context, msg: Message, info: &str) {
         if info.trim().is_empty() {
-            msg.channel_id
-                .send_message(&ctx.http, |m| {
-                    m.embed(|embed| {
-                        embed.colour(serenity::utils::Colour::LIGHT_GREY);
-                        embed.title("Usage");
-                        embed.description(
-                            format!(
-                                "`{}set <field> <info>` or `{}clear <field>`",
-                                CONFIG.command_prefix,
-                                CONFIG.command_prefix,
-                            )
-                        );
-                        embed.field("Biography", format!("`{}set bio <info>`\nBe friendly! Provide a little introduction to yourself.", CONFIG.command_prefix), false);
-                        embed.field("Git", format!("`{}set git <url>`\nA link to your git forge profile. Also takes a github username for convinience", CONFIG.command_prefix), false);
-                        embed.field("Photo", format!("`{}set photo <url>`\nPut a face to a name! Provide a profile photo.", CONFIG.command_prefix), false);
-                        embed.field("Website", format!("`{}set web <info>`\nGot a personal website? Share it here :)", CONFIG.command_prefix), false);
-                        embed.field("Studying", format!("`{}set study <info>`\nYou're (probably) a Uni student, what's your major?", CONFIG.command_prefix), false);
-                        embed
-                    });
-                    m
-                })
-                .expect("Failed to send usage help embed");
-            return;
+            send_message!(
+                msg.channel_id,
+                &ctx.http,
+                format!(
+                    "Usage: {}set <bio|git|web|photo> <value>",
+                    CONFIG.command_prefix
+                )
+            );
+            return
         }
         let info_content: Vec<_> = info.splitn(2, ' ').collect();
         let mut property = String::from(info_content[0]);
@@ -280,43 +266,23 @@ impl Commands {
         if info_content.len() == 1
             || !vec!["bio", "git", "web", "photo", "study"].contains(&property.as_str())
         {
-            msg.channel_id
-                .send_message(&ctx.http, |m| {
-                    m.embed(|embed| {
-                        embed.colour(serenity::utils::Colour::LIGHT_GREY);
-                        embed.title("Usage");
-                        embed.field(
-                            match property.as_str() {
-                                "bio" => "Biography",
-                                "git" => "Git Forge Profile",
-                                "photo" => "Profile Photo",
-                                "web" => "Personal Website",
-                                "study" => "Area of study",
-                                _ => "???",
-                            },
-                            format!(
-                                "`{}set {} <info>` or `{}clear {}`\n{}",
-                                CONFIG.command_prefix,
-                                property,
-                                CONFIG.command_prefix,
-                                property,
-                                match property.as_str() {
-                                    "bio" => "Some information about yourself :)",
-                                    "git" => "A url to your git{hub,lab} account",
-                                    "photo" => "A url to a profile photo online",
-                                    "web" => "A url to your website/webpage",
-                                    "study" => "Your degree title",
-                                    _ => "Whatever you want, because this does absolutely nothing.",
-                                }
-                            ),
-                            false,
-                        );
-                        embed
-                    });
-                    m
-                })
-                .expect("Failed to send usage embed");
-            return;
+            send_message!(
+                msg.channel_id,
+                &ctx.http,
+                format!(
+                    "Usage: {}set {} {}",
+                    CONFIG.command_prefix,
+                    property,
+                    match property.as_str() {
+                        "bio" => "some information about yourself :)",
+                        "git" => "a url to your git{hub,lab} account",
+                        "photo" => "a url to a profile photo online",
+                        "web" => "a url to your website/webpage",
+                        _ => "whatever you want, because this does absolutely nothing. Try !set to see what you can do"
+                    }
+                )
+            );
+            return
         }
         let mut value = info_content[1].to_string();
 
@@ -333,7 +299,7 @@ impl Commands {
                         &ctx.http,
                         "That ain't a URL where I come from..."
                     );
-                    return;
+                    return
                 }
             }
         }
@@ -400,7 +366,7 @@ impl Commands {
             Commands::set_info(ctx, msg, "");
             return;
         }
-        let clear_property = match field {
+        match field {
             "bio" => database::set_member_bio(&msg.author.id.0, None),
             "git" => database::set_member_git(&msg.author.id.0, None),
             "photo" => database::set_member_photo(&msg.author.id.0, None),
diff --git a/src/voting.rs b/src/voting.rs
index bbb5c7d..5f2e05f 100644
--- a/src/voting.rs
+++ b/src/voting.rs
@@ -15,7 +15,7 @@ impl Commands {
         let motion = content;
         if !motion.is_empty() {
             create_motion(&ctx, &msg, motion);
-            return;
+            return
         }
         send_message!(
             msg.channel_id,
@@ -34,7 +34,7 @@ impl Commands {
         let topic = content;
         if !topic.is_empty() {
             create_poll(&ctx, &msg, topic);
-            return;
+            return
         }
         send_message!(
             msg.channel_id,
@@ -193,11 +193,24 @@ fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo {
 fn set_cached_motion(id: serenity::model::id::MessageId, motion_info: MotionInfo) {
     if let Some(motion) = MOTIONS_CACHE.lock().unwrap().get_mut(&id) {
         *motion = motion_info;
-        return;
+        return
     }
     warn!("{}", "Couldn't find motion in cache to set");
 }
 
+macro_rules! tiebreaker {
+    ($ctx: expr, $vote: expr, $motion_info: expr) => {
+        if $motion_info.votes.get($vote).unwrap().iter().any(|u| {
+            u.has_role($ctx, CONFIG.server_id, CONFIG.tiebreaker_role)
+                .unwrap()
+        }) {
+            0.25
+        } else {
+            0.0
+        }
+    }
+}
+
 fn update_motion(
     ctx: &Context,
     msg: &mut Message,
@@ -211,31 +224,9 @@ fn update_motion(
     let against_votes = motion_info.votes.get(&CONFIG.against_vote).unwrap().len() as isize - 1;
     let abstain_votes = motion_info.votes.get(&CONFIG.abstain_vote).unwrap().len() as isize - 1;
 
-    let has_tiebreaker = |users: &Vec<serenity::model::user::User>| {
-        users.iter().any(|u| {
-            u.has_role(ctx, CONFIG.server_id, CONFIG.tiebreaker_role)
-                .unwrap()
-        })
-    };
-
-    let for_strength = for_votes as f32
-        + (if has_tiebreaker(motion_info.votes.get(&CONFIG.for_vote).unwrap()) {
-            0.25
-        } else {
-            0.0
-        });
-    let against_strength = against_votes as f32
-        + (if has_tiebreaker(motion_info.votes.get(&CONFIG.against_vote).unwrap()) {
-            0.25
-        } else {
-            0.0
-        });
-    let abstain_strength = abstain_votes as f32
-        + (if has_tiebreaker(motion_info.votes.get(&CONFIG.abstain_vote).unwrap()) {
-            0.25
-        } else {
-            0.0
-        });
+    let for_strength = for_votes as f32 + tiebreaker!(ctx, &CONFIG.for_vote, motion_info);
+    let against_strength = against_votes as f32 + tiebreaker!(ctx, &CONFIG.against_vote, motion_info);
+    let abstain_strength = abstain_votes as f32 + tiebreaker!(ctx, &CONFIG.abstain_vote, motion_info);
 
     let old_embed = msg.embeds[0].clone();
     let topic = old_embed.clone().title.unwrap();
-- 
GitLab