add some kind of authorization #5
					 6 changed files with 128 additions and 24 deletions
				
			
		add route to give user access to a zone
				commit
				
					
					
						2a3354456a
					
				
			
		| 
						 | 
				
			
			@ -24,5 +24,10 @@ async fn rocket() -> rocket::Rocket {
 | 
			
		|||
    rocket::ignite()
 | 
			
		||||
        .manage(app_config)
 | 
			
		||||
        .attach(DbConn::fairing())
 | 
			
		||||
        .mount("/api/v1", routes![get_zone_records, create_auth_token, create_user])
 | 
			
		||||
        .mount("/api/v1", routes![
 | 
			
		||||
            get_zone_records,
 | 
			
		||||
            create_user_zone,
 | 
			
		||||
            create_auth_token,
 | 
			
		||||
            create_user
 | 
			
		||||
        ])
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut};
 | 
			
		|||
 | 
			
		||||
use rocket::{Request, State, http::Status, request::{FromParam, FromRequest, Outcome}};
 | 
			
		||||
 | 
			
		||||
use serde::{Serialize, Deserialize};
 | 
			
		||||
use serde::{Deserialize, Deserializer, Serialize};
 | 
			
		||||
 | 
			
		||||
use tokio::{net::TcpStream as TokioTcpStream, task};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -250,13 +250,14 @@ impl From<trust_dns_types::Record> for Record {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct AbsoluteName(Name);
 | 
			
		||||
 | 
			
		||||
impl<'r> FromParam<'r> for AbsoluteName {
 | 
			
		||||
    type Error = ProtoError;
 | 
			
		||||
 | 
			
		||||
    fn from_param(param: &'r str) -> Result<Self, Self::Error> {
 | 
			
		||||
        let mut name = Name::from_utf8(¶m).unwrap();
 | 
			
		||||
        let mut name = Name::from_utf8(¶m)?;
 | 
			
		||||
        if !name.is_fqdn() {
 | 
			
		||||
            name.set_fqdn(true);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -264,6 +265,21 @@ impl<'r> FromParam<'r> for AbsoluteName {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'de> Deserialize<'de> for AbsoluteName {
 | 
			
		||||
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 | 
			
		||||
    where
 | 
			
		||||
        D: Deserializer<'de>
 | 
			
		||||
    {
 | 
			
		||||
        use serde::de::Error;
 | 
			
		||||
 | 
			
		||||
        String::deserialize(deserializer)
 | 
			
		||||
            .and_then(|string|
 | 
			
		||||
                AbsoluteName::from_param(&string)
 | 
			
		||||
                    .map_err(|e| Error::custom(e.to_string()))
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Deref for AbsoluteName {
 | 
			
		||||
    type Target = Name;
 | 
			
		||||
    fn deref(&self) -> &Self::Target {
 | 
			
		||||
| 
						 | 
				
			
			@ -271,6 +287,7 @@ impl Deref for AbsoluteName {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pub struct DnsClient(AsyncClient);
 | 
			
		||||
 | 
			
		||||
impl Deref for DnsClient {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,13 +56,14 @@ impl<'r> Responder<'r, 'static> for ErrorResponse {
 | 
			
		|||
impl From<UserError> for ErrorResponse {
 | 
			
		||||
    fn from(e: UserError) -> Self {
 | 
			
		||||
        match e {
 | 
			
		||||
            UserError::NotFound => ErrorResponse::new(Status::Unauthorized, "Provided credentials or token do not match any existing user".into()),
 | 
			
		||||
            UserError::UserExists => ErrorResponse::new(Status::Conflict, "User already exists".into()),
 | 
			
		||||
            UserError::BadCreds => ErrorResponse::new(Status::Unauthorized, "Provided credentials or token do not match any existing user".into()),
 | 
			
		||||
            UserError::UserConflict => ErrorResponse::new(Status::Conflict, "This user already exists".into()),
 | 
			
		||||
            UserError::NotFound => ErrorResponse::new(Status::NotFound, "User does not exist".into()),
 | 
			
		||||
            UserError::BadToken => ErrorResponse::new(Status::BadRequest, "Malformed token".into()),
 | 
			
		||||
            UserError::ExpiredToken => ErrorResponse::new(Status::Unauthorized, "The provided token has expired".into()),
 | 
			
		||||
            UserError::MalformedHeader => ErrorResponse::new(Status::BadRequest, "Malformed authorization header".into()),
 | 
			
		||||
            UserError::PermissionDenied => ErrorResponse::new(Status::Forbidden, "Bearer is not authorized to access the resource".into()),
 | 
			
		||||
            UserError::ZoneNotFound => ErrorResponse::new(Status::NotFound, "DNS zone does not exists".into()),
 | 
			
		||||
            UserError::ZoneNotFound => ErrorResponse::new(Status::NotFound, "DNS zone does not exist".into()),
 | 
			
		||||
            UserError::DbError(e) => make_500(e),
 | 
			
		||||
            UserError::PasswordError(e) => make_500(e)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ use crate::schema::*;
 | 
			
		|||
use crate::DbConn;
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::models::errors::{ErrorResponse, make_500};
 | 
			
		||||
use crate::models::dns::AbsoluteName;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const BEARER: &str = "Bearer ";
 | 
			
		||||
| 
						 | 
				
			
			@ -77,6 +78,11 @@ pub struct CreateUserRequest {
 | 
			
		|||
    pub role: Option<Role>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct CreateUserZoneRequest {
 | 
			
		||||
    pub zone: AbsoluteName,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// pub struct LdapUserAssociation {
 | 
			
		||||
//     user_id: Uuid,
 | 
			
		||||
//     ldap_id: String
 | 
			
		||||
| 
						 | 
				
			
			@ -115,6 +121,14 @@ impl UserInfo {
 | 
			
		|||
        matches!(self.role, Role::Admin)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn check_admin(&self) -> Result<(), UserError> {
 | 
			
		||||
        if self.is_admin() {
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(UserError::PermissionDenied)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_zone(&self, conn: &diesel::SqliteConnection, zone_name: &str) -> Result<Zone, UserError> {
 | 
			
		||||
        use crate::schema::user_zone::dsl::*;
 | 
			
		||||
        use crate::schema::zone::dsl::*;
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +144,38 @@ impl UserInfo {
 | 
			
		|||
 | 
			
		||||
        Ok(res_zone)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_zone(&self, conn: &diesel::SqliteConnection, request: CreateUserZoneRequest) -> Result<Zone, UserError> {
 | 
			
		||||
        use crate::schema::user_zone::dsl::*;
 | 
			
		||||
        use crate::schema::zone::dsl::*;
 | 
			
		||||
 | 
			
		||||
        let zone_name = request.zone.to_utf8();
 | 
			
		||||
        let current_zone: Zone = zone.filter(name.eq(zone_name))
 | 
			
		||||
            .get_result(conn)
 | 
			
		||||
            .map_err(|e| match e {
 | 
			
		||||
                DieselError::NotFound => UserError::ZoneNotFound,
 | 
			
		||||
                other => UserError::DbError(other)
 | 
			
		||||
            })?;
 | 
			
		||||
 | 
			
		||||
        let new_user_zone = UserZone {
 | 
			
		||||
            zone_id: current_zone.id.clone(),
 | 
			
		||||
            user_id: self.id.clone()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let res = diesel::insert_into(user_zone)
 | 
			
		||||
            .values(new_user_zone)
 | 
			
		||||
            .execute(conn);
 | 
			
		||||
 | 
			
		||||
        match res {
 | 
			
		||||
            // If user has already access to the zone, safely ignore the conflit
 | 
			
		||||
            // TODO: use 'on conflict do nothing' in postgres when we get there
 | 
			
		||||
            Err(DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _)) => (),
 | 
			
		||||
            Err(e) => return Err(e.into()),
 | 
			
		||||
            Ok(_) => ()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(current_zone)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[rocket::async_trait]
 | 
			
		||||
| 
						 | 
				
			
			@ -179,7 +225,8 @@ impl<'r> FromRequest<'r> for UserInfo {
 | 
			
		|||
pub enum UserError {
 | 
			
		||||
    ZoneNotFound,
 | 
			
		||||
    NotFound,
 | 
			
		||||
    UserExists,
 | 
			
		||||
    UserConflict,
 | 
			
		||||
    BadCreds,
 | 
			
		||||
    BadToken,
 | 
			
		||||
    ExpiredToken,
 | 
			
		||||
    MalformedHeader,
 | 
			
		||||
| 
						 | 
				
			
			@ -188,22 +235,18 @@ pub enum UserError {
 | 
			
		|||
    PasswordError(HasherError),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<DieselError> for UserError {
 | 
			
		||||
    fn from(e: DieselError) -> Self {
 | 
			
		||||
        match e {
 | 
			
		||||
            DieselError::NotFound => UserError::NotFound,
 | 
			
		||||
            DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) => UserError::UserExists,
 | 
			
		||||
            other => UserError::DbError(other)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<HasherError> for UserError {
 | 
			
		||||
    fn from(e: HasherError) -> Self {
 | 
			
		||||
        UserError::PasswordError(e)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<DieselError> for UserError {
 | 
			
		||||
    fn from(e: DieselError) -> Self {
 | 
			
		||||
        UserError::DbError(e)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl LocalUser {
 | 
			
		||||
    pub fn create_user(conn: &diesel::SqliteConnection, user_request: CreateUserRequest) -> Result<UserInfo, UserError> {
 | 
			
		||||
        use crate::schema::localuser::dsl::*;
 | 
			
		||||
| 
						 | 
				
			
			@ -239,6 +282,9 @@ impl LocalUser {
 | 
			
		|||
                .execute(conn)?;
 | 
			
		||||
 | 
			
		||||
            Ok(())
 | 
			
		||||
        }).map_err(|e| match e {
 | 
			
		||||
            DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) => UserError::UserConflict,
 | 
			
		||||
            other => UserError::DbError(other)
 | 
			
		||||
        })?;
 | 
			
		||||
 | 
			
		||||
        Ok(res)
 | 
			
		||||
| 
						 | 
				
			
			@ -255,10 +301,14 @@ impl LocalUser {
 | 
			
		|||
 | 
			
		||||
        let (client_user, client_localuser): (User, LocalUser) = user.inner_join(localuser)
 | 
			
		||||
            .filter(username.eq(request_username))
 | 
			
		||||
            .get_result(conn)?;
 | 
			
		||||
            .get_result(conn)
 | 
			
		||||
            .map_err(|e| match e {
 | 
			
		||||
                DieselError::NotFound => UserError::BadCreds,
 | 
			
		||||
                other => UserError::DbError(other)
 | 
			
		||||
            })?;
 | 
			
		||||
 | 
			
		||||
        if !check_password(&request_password, &client_localuser.password)? {
 | 
			
		||||
            return Err(UserError::NotFound);
 | 
			
		||||
            return Err(UserError::BadCreds);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(UserInfo {
 | 
			
		||||
| 
						 | 
				
			
			@ -274,7 +324,11 @@ impl LocalUser {
 | 
			
		|||
 | 
			
		||||
        let (client_user, client_localuser): (User, LocalUser) = user.inner_join(localuser)
 | 
			
		||||
            .filter(id.eq(request_user_id))
 | 
			
		||||
            .get_result(conn)?;
 | 
			
		||||
            .get_result(conn)
 | 
			
		||||
            .map_err(|e| match e {
 | 
			
		||||
                DieselError::NotFound => UserError::NotFound,
 | 
			
		||||
                other => UserError::DbError(other)
 | 
			
		||||
            })?;
 | 
			
		||||
 | 
			
		||||
        Ok(UserInfo {
 | 
			
		||||
            id: client_user.id,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,15 @@ use rocket::http::Status;
 | 
			
		|||
use crate::config::Config;
 | 
			
		||||
use crate::DbConn;
 | 
			
		||||
use crate::models::errors::{ErrorResponse, make_500};
 | 
			
		||||
use crate::models::users::{LocalUser, CreateUserRequest, AuthClaims, AuthTokenRequest, AuthTokenResponse};
 | 
			
		||||
use crate::models::users::{
 | 
			
		||||
    UserInfo,
 | 
			
		||||
    LocalUser,
 | 
			
		||||
    CreateUserRequest,
 | 
			
		||||
    CreateUserZoneRequest,
 | 
			
		||||
    AuthClaims,
 | 
			
		||||
    AuthTokenRequest,
 | 
			
		||||
    AuthTokenResponse
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[post("/users/me/token", data = "<auth_request>")]
 | 
			
		||||
| 
						 | 
				
			
			@ -27,9 +35,9 @@ pub async fn create_auth_token(
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[post("/users", data = "<user_request>")]
 | 
			
		||||
pub async fn create_user<'r>(conn: DbConn, user_request: Json<CreateUserRequest>) -> Result<Response<'r>, ErrorResponse>{
 | 
			
		||||
pub async fn create_user<'r>(conn: DbConn, user_request: Json<CreateUserRequest>) -> Result<Response<'r>, ErrorResponse> {
 | 
			
		||||
    // TODO: Check current user if any to check if user has permission to create users (with or without role)
 | 
			
		||||
    let _user_info = conn.run(|c| {
 | 
			
		||||
    conn.run(|c| {
 | 
			
		||||
        LocalUser::create_user(&c, user_request.into_inner())
 | 
			
		||||
    }).await?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,3 +45,22 @@ pub async fn create_user<'r>(conn: DbConn, user_request: Json<CreateUserRequest>
 | 
			
		|||
        .status(Status::Created)
 | 
			
		||||
        .ok()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[post("/users/<user_id>/zones", data = "<user_zone_request>")]
 | 
			
		||||
pub async fn create_user_zone<'r>(
 | 
			
		||||
    conn: DbConn,
 | 
			
		||||
    user_info: Result<UserInfo, ErrorResponse>,
 | 
			
		||||
    user_id: String,
 | 
			
		||||
    user_zone_request: Json<CreateUserZoneRequest>
 | 
			
		||||
) -> Result<Response<'r>, ErrorResponse>{
 | 
			
		||||
    user_info?.check_admin()?;
 | 
			
		||||
 | 
			
		||||
    conn.run(move |c| {
 | 
			
		||||
        let user_info = LocalUser::get_user_by_uuid(&c, user_id)?;
 | 
			
		||||
        user_info.add_zone(&c, user_zone_request.into_inner())
 | 
			
		||||
    }).await?;
 | 
			
		||||
 | 
			
		||||
    Response::build()
 | 
			
		||||
        .status(Status::Created)
 | 
			
		||||
        .ok()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,7 @@ pub async fn get_zone_records(
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    let response = {
 | 
			
		||||
        let query = client.query((*zone).clone(), DNSClass::IN, RecordType::AXFR);
 | 
			
		||||
        let query = client.query(zone.clone(), DNSClass::IN, RecordType::AXFR);
 | 
			
		||||
        query.await.map_err(make_500)?
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue