Add code to authenticate to NextCloud server

This commit is contained in:
Matteo Settenvini 2022-07-02 18:32:41 +02:00
parent 24751c4912
commit ce5503da89
Signed by: matteo
GPG Key ID: 8576CC1AD97D42DF
7 changed files with 614 additions and 3 deletions

View File

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
# SPDX-License-Identifier: CC0-1.0
# Custom Dictionary Words
montecristosoftware
reqwest
webbrowser

12
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
// SPDX-License-Identifier: CC0-1.0
{
"cSpell.customDictionaries": {
"custom-dictionary-workspace": {
"name": "custom-dictionary-workspace",
"path": "${workspaceFolder:cooking-schedule}/.cspell/custom-dictionary-workspace.txt",
"addWords": true,
"scope": "workspace"
}
}
}

373
Cargo.lock generated
View File

@ -2,6 +2,23 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -38,6 +55,12 @@ version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -53,14 +76,55 @@ dependencies = [
"envmnt",
]
[[package]]
name = "clap"
version = "3.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "combine"
version = "4.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "cooking-schedule"
version = "0.0.0"
dependencies = [
"anyhow",
"clap",
"directories",
"reqwest",
"rusty-hook",
"serde",
"serde_json",
"tokio",
"toml",
"webbrowser",
]
[[package]]
@ -79,6 +143,61 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "darling"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "directories"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "encoding_rs"
version = "0.8.31"
@ -159,6 +278,12 @@ version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-io"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
name = "futures-sink"
version = "0.3.21"
@ -178,9 +303,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
dependencies = [
"futures-core",
"futures-io",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
@ -192,6 +320,17 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "h2"
version = "0.3.13"
@ -297,6 +436,12 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.2.3"
@ -339,6 +484,26 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "jni"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
dependencies = [
"cesu8",
"combine",
"jni-sys",
"log",
"thiserror",
"walkdir",
]
[[package]]
name = "jni-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "js-sys"
version = "0.3.58"
@ -417,6 +582,62 @@ dependencies = [
"tempfile",
]
[[package]]
name = "ndk"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4"
dependencies = [
"bitflags",
"jni-sys",
"ndk-sys",
"num_enum",
"thiserror",
]
[[package]]
name = "ndk-context"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
[[package]]
name = "ndk-glue"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f"
dependencies = [
"lazy_static",
"libc",
"log",
"ndk",
"ndk-context",
"ndk-macro",
"ndk-sys",
]
[[package]]
name = "ndk-macro"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
dependencies = [
"darling",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ndk-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97"
dependencies = [
"jni-sys",
]
[[package]]
name = "nias"
version = "0.5.0"
@ -433,6 +654,27 @@ dependencies = [
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.12.0"
@ -484,6 +726,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "os_str_bytes"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -508,6 +756,16 @@ version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "proc-macro-crate"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
dependencies = [
"thiserror",
"toml",
]
[[package]]
name = "proc-macro2"
version = "1.0.40"
@ -535,6 +793,17 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "remove_dir_all"
version = "0.5.3"
@ -599,6 +868,15 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.20"
@ -637,6 +915,20 @@ name = "serde"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
@ -677,6 +969,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.98"
@ -702,6 +1000,41 @@ dependencies = [
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -856,6 +1189,17 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.0"
@ -948,6 +1292,26 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webbrowser"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a3cffdb686fbb24d9fb8f03a213803277ed2300f11026a3afe1f108dc021b"
dependencies = [
"jni",
"ndk-glue",
"url",
"web-sys",
"widestring",
"winapi",
]
[[package]]
name = "widestring"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
[[package]]
name = "winapi"
version = "0.3.9"
@ -964,6 +1328,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View File

@ -3,6 +3,7 @@
[package]
name = "cooking-schedule"
description = "Build a schedule of dishes to cook with the help of NextCloud Cookbook"
version = "0.0.0"
edition = "2021"
authors = ["Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>"]
@ -15,13 +16,33 @@ path = "src/main.rs"
[dev-dependencies.rusty-hook]
version = "0.11"
[dependencies.anyhow]
version = "1.0"
[dependencies.clap]
version = "3.2"
features = ["cargo"]
[dependencies.directories]
version = "4.0"
[dependencies.reqwest]
version = "0.11"
features = ["json"]
features = ["json", "blocking"]
[dependencies.serde]
version = "1.0"
features = ["derive"]
[dependencies.serde_json]
version = "1.0"
[dependencies.toml]
version = "0.5"
[dependencies.tokio]
version = "1"
features = ["rt-multi-thread", "net", "macros"]
[dependencies.webbrowser]
version = "0.7"

118
src/config/credentials.rs Normal file
View File

@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
use {
anyhow::{anyhow, Result},
reqwest::{StatusCode, Url},
serde::{Deserialize, Serialize},
std::collections::HashMap,
std::io::ErrorKind,
std::path::{Path, PathBuf},
std::time::Duration,
};
#[derive(Deserialize, Serialize, Default)]
pub struct Credentials {
#[serde(skip)]
config_file: PathBuf,
servers: HashMap<String, Server>,
}
#[derive(Deserialize, Serialize)]
pub struct Server {
url: String,
login_name: String,
password: String,
}
impl Credentials {
pub fn from_directory(config_dir: &Path) -> Result<Self> {
let credentials_file = config_dir.join("credentials.toml");
match std::fs::read_to_string(&credentials_file) {
Ok(content) => {
let mut credentials: Credentials = toml::from_str(&content)?;
credentials.config_file = credentials_file;
Ok(credentials)
}
Err(err) if err.kind() == ErrorKind::NotFound => {
let mut credentials = Credentials::default();
credentials.config_file = credentials_file;
credentials.write_back()?;
Ok(credentials)
}
Err(err) => Err(anyhow!(err)),
}
}
pub fn add(&mut self, server: Url) -> Result<()> {
let http_client = reqwest::blocking::Client::new();
#[derive(Deserialize)]
struct LoginFlow {
poll: LoginPollEndpoint,
login: String,
}
#[derive(Deserialize)]
struct LoginPollEndpoint {
token: String,
endpoint: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct LoginResult {
server: String,
login_name: String,
app_password: String,
}
let login_url = server.join("index.php/login/v2")?;
let login_flow = http_client
.post(login_url.clone())
.send()?
.json::<LoginFlow>()?;
// User auth flow happens in browser
webbrowser::open(&login_flow.login)?;
// We start polling for the end of the flow
loop {
let response = http_client
.post(&login_flow.poll.endpoint)
.form(&[("token", &login_flow.poll.token)])
.send()?;
match response.status() {
StatusCode::OK => {
let login_result = response.json::<LoginResult>()?;
let new_server = Server {
url: login_result.server,
login_name: login_result.login_name,
password: login_result.app_password,
};
let host_name = server
.host_str()
.ok_or(anyhow!("No hostname for provided URL, is it valid?"))?;
self.servers.insert(host_name.to_owned(), new_server);
break;
}
StatusCode::NOT_FOUND => {
std::thread::sleep(Duration::from_secs(1));
// ...then keep polling
}
_ => {
response.error_for_status()?;
}
}
}
self.write_back()
}
fn write_back(&self) -> Result<()> {
std::fs::write(&self.config_file, toml::to_string(self)?)?;
Ok(())
}
}

40
src/config/mod.rs Normal file
View File

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
pub mod credentials;
use {
self::credentials::Credentials,
anyhow::Result,
directories::ProjectDirs,
serde::Deserialize,
std::io::{Error, ErrorKind},
std::path::PathBuf,
};
#[derive(Deserialize)]
pub struct Config {
#[serde(skip_deserializing)]
pub credentials: Credentials,
}
impl Config {
pub fn new() -> Self {
let config_dir = Self::ensure_config_dir().unwrap();
Config {
credentials: Credentials::from_directory(&config_dir).unwrap(),
}
}
fn ensure_config_dir() -> Result<PathBuf> {
let config_dir = ProjectDirs::from("eu", "montecristosoftware", "cooking-schedule")
.ok_or(Error::new(
ErrorKind::Other,
"Unable to determine application configuration folder",
))?
.config_dir()
.to_path_buf();
std::fs::create_dir_all(&config_dir)?;
Ok(config_dir)
}
}

View File

@ -1,6 +1,47 @@
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
fn main() {
println!("Hello, world!");
mod config;
use {
self::config::Config,
clap::{arg, command, ArgMatches, Command},
reqwest::Url,
};
fn parse_args() -> ArgMatches {
command!()
.propagate_version(true)
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(
Command::new("init")
.about("Authenticate against the provided NextCloud server")
.arg(arg!(<server> "NextCloud server to connect to")),
)
.get_matches()
}
#[tokio::main(flavor = "multi_thread")]
async fn main() -> anyhow::Result<()> {
let args = parse_args();
let mut configuration = Config::new();
match args.subcommand() {
Some(("init", sub_matches)) => {
let server = sub_matches
.get_one::<String>("server")
.expect("Mandatory parameter <server>");
tokio::task::block_in_place(move || -> anyhow::Result<()> {
configuration
.credentials
.add(Url::parse(server)?)
.expect("Unable to authenticate to NextCloud instance");
Ok(())
})?;
}
_ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"),
};
Ok(())
}