feat: oauth2 script and start of changeset generation

This commit is contained in:
Matthieu Bessat 2024-06-29 07:43:49 +02:00
parent 979fa48179
commit 78a88901dc
19 changed files with 262 additions and 21 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
.env

11
Cargo.lock generated
View file

@ -128,6 +128,7 @@ dependencies = [
"fully_pub",
"osm-types",
"osmpbf",
"quick-xml",
"rand",
"raylib",
"rstar",
@ -948,6 +949,16 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "quick-xml"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "quote"
version = "1.0.36"

View file

@ -8,6 +8,7 @@ anyhow = "1.0.86"
fully_pub = "0.1.4"
osm-types = "0.1.5"
osmpbf = "0.3.3"
quick-xml = { version = "0.34.0", features = ["serialize"] }
rand = "0.8.5"
raylib = { version = "5.0.1", features = ["wayland"] }
rstar = "0.12.0"

7
data/change.osc Normal file
View file

@ -0,0 +1,7 @@
<osmChange version="0.6" generator="acme osm editor">
<modify>
<node id="1234" changeset="42" version="2" lat="12.1234567" lon="-8.7654321">
<tag k="amenity" v="school"/>
</node>
</modify>
</osmChange>

26
docs/OAUTH2.md Normal file
View file

@ -0,0 +1,26 @@
# OAuth2
OAuth2 can be managed externally by a third-party program.
The goal is to have a valid "Bearer" token stored in a cache file.
`~/.cache/bobosm/session`
We can use [OAuth2C](https://github.com/cloudentity/oauth2c) to external OAuth2C
oauth2c \
--client-id $OAUTH2_ID \
--client-secret $OAUTH2_SECRET \
--grant-type authorization_code \
--redirect-url "https://localhost:9876/callback" \
--scopes "read_prefs,write_api" \
--callback-tls-cert "$(pwd)/tmp/domain.crt" \
--callback-tls-key "$(pwd)/tmp/domain.key" \
--auth-method "client_secret_basic" \
--response-types "code" --response-mode "query" \
"https://www.openstreetmap.org"
This command use callback TLS params to allow for https for the callback server.
curl "https://api.openstreetmap.org/api/0.6/user/details" -H "Authorization: Bearer eqn8EG_A7xxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxx" | xq

1
docs/curl.md Normal file
View file

@ -0,0 +1 @@
# Manual changeset upload with curl

12
oauth2.sh Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/sh
oauth2c \
--client-id $OAUTH2_ID \
--client-secret $OAUTH2_SECRET \
--grant-type authorization_code \
--redirect-url "https://localhost:9876/callback" \
--scopes "read_prefs,write_api" \
--callback-tls-cert "$(pwd)/tmp/domain.crt" \
--callback-tls-key "$(pwd)/tmp/domain.key" \
--auth-method "client_secret_basic" \
--response-types "code" --response-mode "query" \
"https://www.openstreetmap.org"

View file

86
src/changeset.rs Normal file
View file

@ -0,0 +1,86 @@
use std::sync::{Arc, Mutex};
use anyhow::{Context, Result, anyhow};
use serde::{Serialize};
use quick_xml::se::{to_string_with_root, Serializer};
use xdg::BaseDirectories;
use crate::layers::Layer;
#[derive(Debug, Serialize)]
struct TagChanges {
#[serde(rename = "@k")]
key: String,
#[serde(rename = "@v")]
value: String,
}
#[derive(Debug, Serialize)]
struct NodeChanges {
#[serde(rename = "@id")]
id: String,
#[serde(rename = "@changeset")]
changeset: String,
#[serde(rename = "@version")]
version: String,
#[serde(rename = "@lat")]
lat: f32,
#[serde(rename = "@lon")]
lon: f32,
#[serde(rename = "tag")]
tags: Option<Vec<TagChanges>>
}
#[derive(Debug, Serialize)]
struct ModifyChanges {
#[serde(rename = "node")]
nodes: Vec<NodeChanges>
}
#[derive(Debug, Serialize)]
struct Changeset {
#[serde(rename = "@version")]
version: String,
#[serde(rename = "@generator")]
generator: String,
modify: Option<ModifyChanges>
}
fn build_osm_change_xml(changeset: &Changeset) -> Result<String> {
Ok(to_string_with_root("osmChange", changeset)?)
}
/// Will handle all the operations to collect the changes and create a changeset
pub fn try_changeset(
xdg_dirs: &BaseDirectories,
layers: Arc<Mutex<Vec<Layer>>>
) -> Result<()> {
let changeset = Changeset {
version: "0.6".to_string(),
generator: "BobOSM".to_string(),
modify: Some(ModifyChanges {
nodes: vec![
NodeChanges {
id: "0".to_string(),
changeset: "0".to_string(),
version: "0".to_string(),
lat: 23.0,
lon: 32.0,
tags: Some(vec![
TagChanges {
key: "amenity".to_string(),
value: "restaurant".to_string()
}
])
}
]
})
};
// 1. collect changes and build a OsmChange
let xml = build_osm_change_xml(&changeset);
println!("{xml:?}");
Ok(())
}

View file

@ -1,6 +1,5 @@
use std::collections::HashMap;
use fully_pub::fully_pub;
use crate::ToSlice;
use raylib::math::Vector2;
use serde::Deserialize;
@ -11,7 +10,8 @@ type Tags = Option<HashMap<String, String>>;
#[fully_pub]
#[derive(Debug, Deserialize, Clone)]
struct Node {
id: u64,
// id can be negative when they are new
id: i64,
lat: f32,
lon: f32,
tags: Tags
@ -29,15 +29,16 @@ impl Node {
#[fully_pub]
#[derive(Debug, Deserialize, Clone)]
struct Way {
id: u64,
nodes: Vec<u64>,
// id can be negative when they are new
id: i64,
nodes: Vec<i64>,
tags: Tags
}
#[fully_pub]
#[derive(Debug, Deserialize, Clone)]
struct Relation {
id: u64,
id: i64,
tags: Tags
}
@ -60,7 +61,7 @@ struct Data {
#[fully_pub]
#[derive(Debug, Deserialize)]
struct IndexedData {
elements: HashMap<u64, Element>
elements: HashMap<i64, Element>
}
impl Data {

View file

@ -15,10 +15,10 @@ use crate::{data::Tags, layers::Layer, AppState};
pub fn get_objects_in_area(
layers: Arc<Mutex<Vec<Layer>>>,
selection_envelope: AABB<[f32; 2]>
) -> Vec<Vec<u64>> {
let mut elements_ids_per_layer: Vec<Vec<u64>> = vec![];
) -> Vec<Vec<i64>> {
let mut elements_ids_per_layer: Vec<Vec<i64>> = vec![];
for layer in layers.lock().unwrap().iter() {
let mut elements_ids: Vec<u64> = vec![];
let mut elements_ids: Vec<i64> = vec![];
if let Some(tree) = &layer.ways_tree {
// find a better way to verify if it's intersecting
// the better way:
@ -54,11 +54,11 @@ pub fn tags_to_text(tags: &Tags) -> String {
pub fn text_to_tags(tags_as_text: &str) -> Result<Tags> {
let mut tags: HashMap<String, String> = HashMap::new();
for line in tags_as_text.split("\n") {
for line in tags_as_text.trim().split("\n") {
let line_content = line.to_string();
let compos: Vec<&str> = line_content.split("=").collect();
if compos.len() != 2 {
continue;
return Err(anyhow!("Found invalid tags syntax"));
}
let key = compos.get(0).unwrap().trim();
let val = compos.get(1).unwrap().trim();
@ -73,7 +73,7 @@ pub fn edit_tags(
layers: Arc<Mutex<Vec<Layer>>>
) -> Result<()> {
let app_state_ref = app_state.lock().unwrap();
let layers_ref = layers.lock().unwrap();
let mut layers_ref = layers.lock().unwrap();
for (layer_i, layer_selections) in app_state_ref.selected_elements_per_layer.iter().enumerate() {
for e_id in layer_selections {
@ -83,11 +83,15 @@ pub fn edit_tags(
.ok_or(anyhow!("Could not access layer data source"))?
.elements
.get(&e_id)
.ok_or(anyhow!("Could not access selected object source"))?;
.ok_or(anyhow!("Could not access selected object source"))?
.clone();
match object_element {
crate::data::Element::Node(node) => {
let new_tags = edit_tags_external(&xdg_dirs, &node.tags);
// mark the node as modified, using the node id in the indexed_data
println!("{:?}", new_tags);
let layer_mut_ref = layers_ref.get_mut(layer_i).unwrap();
layer_mut_ref.modified_data.insert(node.id.clone());
},
_ => ()
};
@ -132,4 +136,3 @@ pub fn edit_tags_external(xdg_dirs: &BaseDirectories, initial_tags: &Tags) -> Re
Ok(new_tags)
}

View file

@ -1,4 +1,4 @@
use std::{fs, sync::{Arc, Mutex}};
use std::{collections::HashSet, fs, sync::{Arc, Mutex}};
use anyhow::{anyhow, Result, Context};
use fully_pub::fully_pub;
@ -13,7 +13,7 @@ use crate::{data::{self, compute_way_bbox, IndexedData}, ToSlice};
#[fully_pub]
/// represent the displayed point on screen
struct DisplayedPOI {
id: u64,
id: i64,
pos: Vector2,
importance: i32,
name: String
@ -22,7 +22,7 @@ struct DisplayedPOI {
#[fully_pub]
/// represent the displayed way on screen ()
struct DisplayedWay {
id: u64,
id: i64,
positions: Vec<Vector2>,
bbox: (Vector2, Vector2),
importance: u32,
@ -64,6 +64,7 @@ impl Default for LayerKind {
struct Layer {
id: u64,
data_source: Option<IndexedData>,
modified_data: HashSet<i64>,
pois_tree: Option<RTree<DisplayedPOI>>,
ways_tree: Option<RTree<DisplayedWay>>,
name: String,
@ -78,6 +79,7 @@ impl Default for Layer {
id: 0,
pois_tree: None,
data_source: None,
modified_data: HashSet::new(),
ways_tree: None,
name: "".to_string(),
kind: LayerKind::StaticOSMData,
@ -123,7 +125,7 @@ pub fn build_displayed_pois(indexed_data: &IndexedData) -> Result<RTree<Displaye
if importance >= 0 {
d_pois.push(DisplayedPOI {
id: node.id as u64,
id: node.id,
pos: node.to_vec(),
name,
importance
@ -175,7 +177,7 @@ pub fn build_displayed_ways(indexed_data: &IndexedData) -> Result<RTree<Displaye
}
d_ways.push(DisplayedWay {
id: way.id as u64,
id: way.id,
name: final_name,
importance: 0,
positions: nodes_pos,
@ -201,6 +203,7 @@ pub fn load_static_osm_layer(path: &str) -> Result<Layer> {
name: "Static OSM Data".to_string(),
kind: LayerKind::StaticOSMData,
data_source: Some(indexed_data),
modified_data: HashSet::new(),
pois_tree: None,
ways_tree: Some(d_ways),
display: true,
@ -250,6 +253,7 @@ pub fn load_static_cities_layer(path: &str) -> Result<Layer> {
id: rand::thread_rng().gen(),
kind: LayerKind::StaticOSMData,
data_source: None,
modified_data: HashSet::new(),
name: "Cities, Towns".to_string(),
display: true,
pois_tree: Some(tree),

View file

@ -1,4 +1,5 @@
mod data;
mod changeset;
mod layers;
mod editor;
@ -7,7 +8,7 @@ mod test_data;
#[cfg(test)]
mod test_layers;
use std::{collections::HashMap, fs, sync::{Arc, Mutex}, thread::{self, JoinHandle}, time::Duration};
use std::{collections::{HashMap, HashSet}, fs, sync::{Arc, Mutex}, thread::{self, JoinHandle}, time::Duration};
use anyhow::{Context, Result, anyhow};
@ -107,7 +108,7 @@ enum UiMode {
#[fully_pub]
struct AppState {
loading: bool,
selected_elements_per_layer: Vec<Vec<u64>>
selected_elements_per_layer: Vec<Vec<i64>>
}
#[derive(Debug)]
@ -296,6 +297,7 @@ fn main() -> Result<()> {
id: rng.gen(),
kind: LayerKind::DynamicOSMData,
data_source: Some(IndexedData { elements: HashMap::new() }),
modified_data: HashSet::new(),
pois_tree: None,
ways_tree: None,
display: true,
@ -411,6 +413,11 @@ fn main() -> Result<()> {
if rl.is_key_pressed(KeyboardKey::KEY_Q) {
break;
}
if rl.is_key_pressed(KeyboardKey::KEY_C) {
// changeset mode
// for now, generate Osm Change in local data dir
let _ = changeset::try_changeset(&xdg_dirs, layers.clone());
}
if rl.is_key_pressed(KeyboardKey::KEY_E) {
// toggle edit mode
ui_mode = match ui_mode {

10
src/osm_api.rs Normal file
View file

@ -0,0 +1,10 @@
// all the functions to interact with the OSM API
// https://wiki.openstreetmap.org/wiki/API_v0.6
fn upload_changeset() {
// Create: PUT /api/0.6/changeset/create
// Diff upload: POST /api/0.6/changeset/#id/upload
// upload OsmChange file
// Close: PUT /api/0.6/changeset/#id/close
}

View file

21
tmp/domain.crt Normal file
View file

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDhzCCAm+gAwIBAgIUBgiox4nOl3uRyS7lLfjvHgS2/RAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCRlIxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjgxODAwMTJaFw0yNTA2
MjgxODAwMTJaMFQxCzAJBgNVBAYTAkZSMREwDwYDVQQIDAhOb3JtYW5keTEOMAwG
A1UEBwwFUm91ZW4xDjAMBgNVBAoMBU1iZXNzMRIwEAYDVQQDDAlsb2NhbGhvc3Qw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtFcWQ7CFMVY005+hEBsQ5
We179He49FfxpNC97G21BqBmfNlGmDkRsIZG9XCHaYF++cJ09/i2i+i4L5ZlxoI3
Eon3fgjPyOmRC0KvmOBxxtxB6gN3sY6CqgS/wlhOA8a+Ra4hB9nO0gwvWXhUrOcb
h0J0KStWr+4SAlz5ZGNDUYWuoqbV0jHtKglOBnmGXIAO6+3MxfUezY9hoPbdCYih
lyKeF01VEXtzclBFY8uWvfYS96/xpCgN7p5SmTIvolABxZ3fUHnVmh3yQXqDBSM/
+yX2gRVi56XeE/WVEakO7ElwthukmnKDZVxKWHsR2JkTV6O4jCIGbnK0UAOuLDPJ
AgMBAAGjYDBeMB8GA1UdIwQYMBaAFEtrDGAFVvL4372b/JdEP9cjIOZfMAkGA1Ud
EwQCMAAwEQYDVR0RBAowCIIGZG9tYWluMB0GA1UdDgQWBBTKYEUdJVcfkYF4d3BR
8R2zI/NnwjANBgkqhkiG9w0BAQsFAAOCAQEATLpUoSJfzctLlfeDKQ4rHQYh861J
HplyiKybICrGr7ekGpEoCfes1NxVn9gDD5Lo/f+y50Tmy9KNipRJhudeQ5oU6X69
Kb2Hpq2Qy+x3FLYkIt8snUAmNckuYBeLbnfoAYVZy/kMZHzvKd44sgQu86x7O3lv
+Egs/go+b3MyJCTM7zCFXFkfpSCAFUtuZdE0zUaTHSca/7emx5gxF78wGfsIErzV
EEly3Q0STxRpz/71KS6cIHoRjSFs8SIdRvNCpr6N5+QWmG4cgehE0xOysAfBGnNN
UQP04uQvdR1TawA3qnvoRn9lksVs4H3dt5Dzepa2Grw0OkQ0W7+67hEnEA==
-----END CERTIFICATE-----

16
tmp/domain.csr Normal file
View file

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICmTCCAYECAQAwVDELMAkGA1UEBhMCRlIxETAPBgNVBAgMCE5vcm1hbmR5MQ4w
DAYDVQQHDAVSb3VlbjEOMAwGA1UECgwFTWJlc3MxEjAQBgNVBAMMCWxvY2FsaG9z
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK0VxZDsIUxVjTTn6EQG
xDlZ7Xv0d7j0V/Gk0L3sbbUGoGZ82UaYORGwhkb1cIdpgX75wnT3+LaL6LgvlmXG
gjcSifd+CM/I6ZELQq+Y4HHG3EHqA3exjoKqBL/CWE4Dxr5FriEH2c7SDC9ZeFSs
5xuHQnQpK1av7hICXPlkY0NRha6iptXSMe0qCU4GeYZcgA7r7czF9R7Nj2Gg9t0J
iKGXIp4XTVURe3NyUEVjy5a99hL3r/GkKA3unlKZMi+iUAHFnd9QedWaHfJBeoMF
Iz/7JfaBFWLnpd4T9ZURqQ7sSXC2G6SacoNlXEpYexHYmRNXo7iMIgZucrRQA64s
M8kCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQAuehYPVRDsd4didHp+TgAB5GNK
oSSdoPuRY9q1IAFLNexW3iBwakRMu8G4zIIo60g1m7MNiff5CQVpth69IB7f7Li+
EWYZnEPvcP0cAGgu2pyiA+DVDz9sRyD8ArpchmTISeGg7wOx1delJTlmOfJntZDu
42hS4XweAOqZu3tt8MP7p7mxGHKpV7mfmraVFobGcBeNvijxm0NQ8bgMleVFnppw
lVJQL6L4X6NV6fWl8EtXji1eszlC8SBf5M0UAVGkhqKmW8bKQA4gM4O26gssS6H/
j4+pXlF6Hqq0VrDC6XL6+A6GbjhHJTL371o2x9mWXRsLPuwz06Jjcjr+1Snu
-----END CERTIFICATE REQUEST-----

6
tmp/domain.ext Normal file
View file

@ -0,0 +1,6 @@
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.1 = domain

28
tmp/domain.key Normal file
View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCtFcWQ7CFMVY00
5+hEBsQ5We179He49FfxpNC97G21BqBmfNlGmDkRsIZG9XCHaYF++cJ09/i2i+i4
L5ZlxoI3Eon3fgjPyOmRC0KvmOBxxtxB6gN3sY6CqgS/wlhOA8a+Ra4hB9nO0gwv
WXhUrOcbh0J0KStWr+4SAlz5ZGNDUYWuoqbV0jHtKglOBnmGXIAO6+3MxfUezY9h
oPbdCYihlyKeF01VEXtzclBFY8uWvfYS96/xpCgN7p5SmTIvolABxZ3fUHnVmh3y
QXqDBSM/+yX2gRVi56XeE/WVEakO7ElwthukmnKDZVxKWHsR2JkTV6O4jCIGbnK0
UAOuLDPJAgMBAAECggEAA1GLIeExW2v2YAHVU42+1bzH80dXpXhYbxN0TVpBhpMQ
1Un5ReWFnWo2Bgph1NP9rNoXSGyZ4BpbNBTW56dY3cpFJWKf/xiVVwMcjVJkwG13
Owot3gRkQJC8aQtkkXJaYgY9SEPIrmd1opVcCNmyZkyo1Tj36IQcEB8Jtpph9Jic
C4TGJUttMWgiZeEApZCp8REUdoFnV1gUtkMQXEhhV/L2WOh7TjjATnTQ90AIMO+D
aL42R98v7akmu9Cdzy6YdHfjjufOmTUvaYoscDO9T+RPgATKZ5QuLKopxksJ9YYO
zUCpZ0en46OL0NbhWdWdUXz+LyoUcyiWNhngyxMgFQKBgQDwUlpLY9mU9XFu0lzW
aQBQuIolf5nnqeQE/ZujqkX1w9bEntwgQ0wjp0hh1Tg2oPpkNq4Sp0CsFckWBPvp
jm8acLXYwv054QGfCiQInRvfWsR4agAcN9y/TfnYUlrjdxmU303DI9gv9+B27aKD
HQXLX7dHRk0RqH6CO1SGrUh0HwKBgQC4YH14A58cGMeKN6pF6m9OAzq5m+zXgZDV
Cz/o+CciAFwzW9V+CXQ+kFw9cz0b74Ii5GHgyz77/orEyJyIiTJTDzMqqTAg6aZn
OLYC9A12HQrLfaffXLsWIwXIdiQsrTY96yYOz3kAHXL1XeMlrN0jksYF6RD9iemr
A6wZzkWbFwKBgDRCBCZ+qPKuSKNLlSp+nLXw2wF6dNIebFn1d+GoBhyCIHKTBNTz
LVxXZPL5NYcTjD88cK/XtMV220oxfUH1Wg2K0tA23m+2kO6vetRwrX3tM+nnSuzX
7OWgpCK5DdCGtoZb5IH5imor4aCa3graxcbKooUaMWoUlKXnFuNOTQILAoGATdSl
glD9DG5FZUpEPYlN/P+N/aYdn4dZLSW3j8+ZLVg7k46Tm8W/5V0pIuOPi0hahgIk
czE44EU+LP2GXniT3s4OylZrM6mODgq5gpulRxPRZ78ea0KI0zpriYidkqJ3wEGQ
ajYYnPzRd1Rvu4qyOv8NSpdtDan3ErLVJnuVlHcCgYB8CuscAfciIQZNxiIPea4g
O9rGZ50nEmqXVfDOaGfAFZE6p3ZkV5UDZgVsh/4XkUC6wrxrFZsemU8RKOngshGF
SbQSGZNpSeT3dDHVurko8y7p0o0P//nUzIgN5owq/HnIZlUl9475FyJ1Ysj5BH2X
k4dxRmaijNXdzHjF/Ua8ww==
-----END PRIVATE KEY-----