Start authorization route
This commit is contained in:
		
							parent
							
								
									1647ca3838
								
							
						
					
					
						commit
						bd4144a5ba
					
				@ -25,4 +25,5 @@ axum_typed_multipart = "0.8"
 | 
			
		||||
async-trait = "0.1"
 | 
			
		||||
serde = { version = "1", features = [ "derive" ] }
 | 
			
		||||
file-owner = { version = "0.1" }
 | 
			
		||||
tower-cookies = "0.9"
 | 
			
		||||
tower-cookies = "0.9"
 | 
			
		||||
url = "2.4"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										63
									
								
								src/routes/authorize.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/routes/authorize.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
use axum::{
 | 
			
		||||
	RequestPartsExt,
 | 
			
		||||
	extract::{FromRequestParts, State},
 | 
			
		||||
	http::{StatusCode, header::HeaderMap, request::Parts},
 | 
			
		||||
};
 | 
			
		||||
use snafu::prelude::*;
 | 
			
		||||
use url::Url;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    error::*,
 | 
			
		||||
    state::{RoutableAppState, LoggedInUser},
 | 
			
		||||
    state::sessions::*,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: Implement as a typed header
 | 
			
		||||
pub struct OriginalURI(Url);
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
impl FromRequestParts<RoutableAppState> for OriginalURI {
 | 
			
		||||
    type Rejection = Error;
 | 
			
		||||
 | 
			
		||||
    async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> {
 | 
			
		||||
		let headers: HeaderMap = parts.extract().await.unwrap();
 | 
			
		||||
		if let Some(uri) = headers.get("X-Original-URI") {
 | 
			
		||||
			// TODO: error
 | 
			
		||||
			Ok(
 | 
			
		||||
				OriginalURI(Url::parse(uri.to_str().unwrap()).unwrap())
 | 
			
		||||
			)
 | 
			
		||||
	    } else {
 | 
			
		||||
			// TODO: error
 | 
			
		||||
			panic!()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AsRef<Url> for OriginalURI {
 | 
			
		||||
    fn as_ref(&self) -> &Url {
 | 
			
		||||
        &self.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[debug_handler]
 | 
			
		||||
pub async fn route(user: Option<LoggedInUser>, uri: Option<OriginalURI>, state: State<RoutableAppState>) -> StatusCode {
 | 
			
		||||
    if uri.is_none() {
 | 
			
		||||
        panic!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let uri = uri.unwrap();
 | 
			
		||||
    let username = user.map(|u| u.username().clone());
 | 
			
		||||
 | 
			
		||||
    match state.permissions.ssowat_config() {
 | 
			
		||||
        Ok(conf) => {
 | 
			
		||||
            if conf.user_has_permission_for_uri(username.as_ref(), uri.as_ref()) {
 | 
			
		||||
                StatusCode::OK
 | 
			
		||||
            } else {
 | 
			
		||||
                StatusCode::FORBIDDEN
 | 
			
		||||
            }
 | 
			
		||||
        }, Err(e) => {
 | 
			
		||||
            panic!()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -6,6 +6,7 @@ use tower_cookies::CookieManagerLayer;
 | 
			
		||||
 | 
			
		||||
use crate::state::RoutableAppState;
 | 
			
		||||
 | 
			
		||||
mod authorize;
 | 
			
		||||
mod index;
 | 
			
		||||
mod login;
 | 
			
		||||
mod logout;
 | 
			
		||||
@ -16,6 +17,7 @@ pub fn router(subpath: Option<String>, state: RoutableAppState) -> Router {
 | 
			
		||||
        .route("/", get(index::route))
 | 
			
		||||
        .route("/login/", post(login::route))
 | 
			
		||||
        .route("/logout/", get(logout::route))
 | 
			
		||||
        .route("/authorize/", get(authorize::route))
 | 
			
		||||
        .layer(CookieManagerLayer::new())
 | 
			
		||||
        .with_state(state);
 | 
			
		||||
    if let Some(p) = subpath {
 | 
			
		||||
 | 
			
		||||
@ -49,6 +49,12 @@ impl std::fmt::Display for LoggedInUser {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AsRef<Username> for LoggedInUser {
 | 
			
		||||
    fn as_ref(&self) -> &Username {
 | 
			
		||||
        &self.username()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
impl FromRequestParts<RoutableAppState> for LoggedInUser {
 | 
			
		||||
    type Rejection = Error;
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
use snafu::prelude::*;
 | 
			
		||||
use yunohost_api::{YunohostUsers, Username, Password};
 | 
			
		||||
use yunohost_api::{YunohostUsers, Username, Password, YunohostPermissions};
 | 
			
		||||
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ pub const COOKIE_NAME: &'static str = "yunohost.ssowat";
 | 
			
		||||
pub struct AppState {
 | 
			
		||||
    pub sessions: SessionManager,
 | 
			
		||||
    pub users: YunohostUsers,
 | 
			
		||||
    pub permissions: YunohostPermissions,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AppState {
 | 
			
		||||
@ -27,6 +28,8 @@ impl AppState {
 | 
			
		||||
            sessions: SessionManager::new().context(SessionSnafu)?,
 | 
			
		||||
            // Timeout in ms
 | 
			
		||||
            users: YunohostUsers::new(500).await.context(YunohostSnafu)?,
 | 
			
		||||
            // TODO: make async
 | 
			
		||||
            permissions: YunohostPermissions::new().context(YunohostSnafu)?,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								ssowat.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								ssowat.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
socket = /tmp/ssowat.socket
 | 
			
		||||
socket_group = www-data
 | 
			
		||||
socket_mode = "0o770"
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
use regex::Regex;
 | 
			
		||||
use serde::{Serialize, Deserialize};
 | 
			
		||||
use url::Url;
 | 
			
		||||
 | 
			
		||||
@ -23,12 +24,58 @@ impl SSOWatConfig {
 | 
			
		||||
                // Domain not managed
 | 
			
		||||
                return None;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Strip protocol but keep full URL
 | 
			
		||||
            let stripped_uri = AsRef::<str>::as_ref(uri)
 | 
			
		||||
                .trim_start_matches("http")
 | 
			
		||||
                .trim_start_matches("s")
 | 
			
		||||
                .trim_start_matches("://");
 | 
			
		||||
 | 
			
		||||
            // Check which app matches this URI, to find corresponding permission
 | 
			
		||||
            for (key, val) in &self.permissions {
 | 
			
		||||
                for uri_format in &val.uris {
 | 
			
		||||
                    if uri_format.starts_with("re:") {
 | 
			
		||||
                        let uri_format = uri_format.trim_start_matches("re:");
 | 
			
		||||
                        // TODO: generate regex in advance
 | 
			
		||||
                        // TODO: error
 | 
			
		||||
                        let re = Regex::new(uri_format).unwrap();
 | 
			
		||||
                        if re.is_match(stripped_uri) {
 | 
			
		||||
                            return Some(key.clone());
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if stripped_uri.starts_with(uri_format) {
 | 
			
		||||
                            return Some(key.clone());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // No app URI matched
 | 
			
		||||
            return None;
 | 
			
		||||
        } else {
 | 
			
		||||
            // No domain (eg. http://8.8.8.8/)
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        todo!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn user_has_permission_for_uri(&self, username: Option<&Username>, uri: &Url) -> bool {
 | 
			
		||||
        if let Some(permission_name) = self.permission_for_uri(uri) {
 | 
			
		||||
            let permission = self.permissions.get(&permission_name).unwrap();
 | 
			
		||||
            if permission.public {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if let Some(username) = username {
 | 
			
		||||
                permission.users.contains(username)
 | 
			
		||||
            } else {
 | 
			
		||||
                // User is not logged-in. Non-public URIs are not authorized
 | 
			
		||||
                false
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // No permission matching this URI
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user