Skip to content

Commit

Permalink
fix: api user crud and request validation with api cred implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
CA-MKSingh committed Oct 2, 2024
1 parent cee5569 commit 0b15f0c
Show file tree
Hide file tree
Showing 13 changed files with 343 additions and 26 deletions.
138 changes: 137 additions & 1 deletion assets/postman/Shield.postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,136 @@
{
"name": "Client",
"item": [
{
"name": "API User",
"item": [
{
"name": "Create API User",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"name\": \"Portal preprod - 1\",\r\n \"role\": \"client_admin\",\r\n \"access\": \"write\",\r\n \"expires\": \"1h\"\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BASE_URL}}/realms/{{REALM_ID}}/clients/{{CLIENT_ID}}/api-users",
"host": [
"{{BASE_URL}}"
],
"path": [
"realms",
"{{REALM_ID}}",
"clients",
"{{CLIENT_ID}}",
"api-users"
]
}
},
"response": []
},
{
"name": "Update API User",
"request": {
"method": "PATCH",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n // \"name\": \"Portal preprod - 2\",\r\n // \"role\": \"client_admin\",\r\n // \"access\": \"write\",\r\n // \"expires\": \"1d\",\r\n \"lock\": false\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BASE_URL}}/realms/{{REALM_ID}}/clients/{{CLIENT_ID}}/api-users/01924d07-c09a-72a2-bdf6-dcab4549df73",
"host": [
"{{BASE_URL}}"
],
"path": [
"realms",
"{{REALM_ID}}",
"clients",
"{{CLIENT_ID}}",
"api-users",
"01924d07-c09a-72a2-bdf6-dcab4549df73"
]
}
},
"response": []
},
{
"name": "Get API Users",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BASE_URL}}/realms/{{REALM_ID}}/clients/{{CLIENT_ID}}/api-users",
"host": [
"{{BASE_URL}}"
],
"path": [
"realms",
"{{REALM_ID}}",
"clients",
"{{CLIENT_ID}}",
"api-users"
]
}
},
"response": []
},
{
"name": "Delete API User",
"request": {
"method": "DELETE",
"header": [],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BASE_URL}}/realms/{{REALM_ID}}/clients/{{CLIENT_ID}}/api-users/01924cc8-958a-7223-acfd-a49e7223da71",
"host": [
"{{BASE_URL}}"
],
"path": [
"realms",
"{{REALM_ID}}",
"clients",
"{{CLIENT_ID}}",
"api-users",
"01924cc8-958a-7223-acfd-a49e7223da71"
]
}
},
"response": []
}
]
},
{
"name": "Get Clients",
"request": {
Expand Down Expand Up @@ -912,7 +1042,13 @@
"name": "Get Health",
"request": {
"method": "GET",
"header": [],
"header": [
{
"key": "Api-Key",
"value": "01924d07-c09a-72a2-bdf6-dcab4549df73.B/vqBBCnR8Pt9sM4ZzXqLxU6QLlz7dQDe3i2yMmAwT9o/uXeKxZEYOzO6xCTvzPhHp7ach59GaT9ugrGZ+Bvmg==",
"type": "text"
}
],
"url": {
"raw": "{{BASE_URL}}/health",
"host": [
Expand Down
24 changes: 24 additions & 0 deletions entity/src/middlewares/api_user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use crate::{models::api_user, utils::check_locked_at_constraint};
use async_trait::async_trait;
use sea_orm::{entity::prelude::*, sqlx::types::chrono::Utc, ActiveValue};

#[async_trait]
impl ActiveModelBehavior for api_user::ActiveModel {
/// Will be triggered before insert / update
async fn before_save<C>(mut self, _db: &C, _insert: bool) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
if let ActiveValue::Set(ref locked_at) = self.locked_at {
check_locked_at_constraint(locked_at)?
}

if let ActiveValue::Set(ref expires) = self.expires {
if expires < &Utc::now().fixed_offset() {
return Err(DbErr::Custom("Expires must be greater than created_at".to_owned()));
}
}

Ok(self)
}
}
1 change: 1 addition & 0 deletions entity/src/middlewares/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod api_user;
pub mod client;
pub mod realm;
pub mod resource;
Expand Down
3 changes: 1 addition & 2 deletions entity/src/models/api_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct Model {
pub created_by: Uuid,
pub updated_by: Uuid,
pub expires: DateTimeWithTimeZone,
pub locked_at: Option<DateTimeWithTimeZone>,
pub created_at: DateTimeWithTimeZone,
pub updated_at: DateTimeWithTimeZone,
}
Expand Down Expand Up @@ -71,5 +72,3 @@ impl Related<super::realm::Entity> for Entity {
Relation::Realm.def()
}
}

impl ActiveModelBehavior for ActiveModel {}
2 changes: 2 additions & 0 deletions entity/src/models/sea_orm_active_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "api_user_access")]
#[serde(rename_all = "snake_case")]
pub enum ApiUserAccess {
#[sea_orm(string_value = "admin")]
Admin,
Expand All @@ -17,6 +18,7 @@ pub enum ApiUserAccess {
}
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "api_user_role")]
#[serde(rename_all = "snake_case")]
pub enum ApiUserRole {
#[sea_orm(string_value = "client_admin")]
ClientAdmin,
Expand Down
2 changes: 2 additions & 0 deletions migration/src/m20220101_000007_create_api_user_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ impl MigrationTrait for Migration {
.on_delete(ForeignKeyAction::Cascade),
)
.col(ColumnDef::new(ApiUser::Expires).timestamp_with_time_zone().not_null())
.col(ColumnDef::new(ApiUser::LockedAt).timestamp_with_time_zone())
.col(
ColumnDef::new(ApiUser::CreatedAt)
.timestamp_with_time_zone()
Expand Down Expand Up @@ -122,6 +123,7 @@ pub enum ApiUser {
Role,
Access,
Expires,
LockedAt,
CreatedBy,
UpdatedBy,
CreatedAt,
Expand Down
3 changes: 1 addition & 2 deletions src/handlers/auth.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use chrono::Utc;
use entity::{client, resource, resource_group, session, user};
use sea_orm::{ActiveModelTrait, Set};
use sea_orm::{prelude::Uuid, ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, Set};
use std::sync::Arc;

use crate::{
Expand All @@ -16,7 +16,6 @@ use crate::{
utils::role_checker::{is_current_realm_admin, is_master_realm_admin},
};
use axum::{extract::Path, Extension, Json};
use sea_orm::{prelude::Uuid, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter};
use serde::{Deserialize, Serialize};
use tracing::debug;

Expand Down
84 changes: 74 additions & 10 deletions src/handlers/client/api_user.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use std::sync::Arc;

use axum::{extract::Path, Extension, Json};
use chrono::Utc;
use entity::api_user;
use sea_orm::{ActiveModelTrait, Set};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use uuid::Uuid;

use crate::{
mappers::client::api_user::CreateApiUserRequest,
mappers::{
client::api_user::{CreateApiUserRequest, CreateApiUserResponse, UpdateApiUserRequest, UpdateApiUserResponse},
DeleteResponse,
},
packages::{
db::AppState,
errors::{AuthenticateError, Error},
Expand All @@ -18,20 +22,29 @@ use crate::{
},
};

pub async fn get_api_users(user: TokenUser, Path((realm_id, _client_id)): Path<(Uuid, Uuid)>) -> Result<Json<Vec<api_user::Model>>, Error> {
pub async fn get_api_users(
user: TokenUser,
Extension(state): Extension<Arc<AppState>>,
Path((realm_id, client_id)): Path<(Uuid, Uuid)>,
) -> Result<Json<Vec<api_user::Model>>, Error> {
if !is_master_realm_admin(&user) && !is_current_realm_admin(&user, &realm_id.to_string()) {
return Err(Error::Authenticate(AuthenticateError::NoResource));
}

todo!()
let api_users = api_user::Entity::find()
.filter(api_user::Column::RealmId.eq(realm_id))
.filter(api_user::Column::ClientId.eq(client_id))
.all(&state.db)
.await?;
Ok(Json(api_users))
}

pub async fn create_api_user(
user: TokenUser,
Extension(state): Extension<Arc<AppState>>,
Path((realm_id, client_id)): Path<(Uuid, Uuid)>,
Json(payload): Json<CreateApiUserRequest>,
) -> Result<Json<api_user::Model>, Error> {
) -> Result<Json<CreateApiUserResponse>, Error> {
if !is_master_realm_admin(&user) && !is_current_realm_admin(&user, &realm_id.to_string()) {
return Err(Error::Authenticate(AuthenticateError::NoResource));
}
Expand All @@ -54,21 +67,72 @@ pub async fn create_api_user(
};

let api_user = api_user_model.insert(&state.db).await?;
Ok(Json(api_user))
Ok(Json(CreateApiUserResponse::from(api_user)))
}

pub async fn update_api_user(user: TokenUser, Path((realm_id, _client_id)): Path<(Uuid, Uuid)>) -> Result<Json<api_user::Model>, Error> {
pub async fn update_api_user(
user: TokenUser,
Extension(state): Extension<Arc<AppState>>,
Path((realm_id, _client_id, api_user_id)): Path<(Uuid, Uuid, Uuid)>,
Json(payload): Json<UpdateApiUserRequest>,
) -> Result<Json<UpdateApiUserResponse>, Error> {
if !is_master_realm_admin(&user) && !is_current_realm_admin(&user, &realm_id.to_string()) {
return Err(Error::Authenticate(AuthenticateError::NoResource));
}

todo!()
let api_user = api_user::Entity::find_by_id(api_user_id).one(&state.db).await?;
if api_user.is_none() {
return Err(Error::not_found());
}

let api_user = api_user.unwrap();
let api_user_model = api_user::ActiveModel {
id: Set(api_user.id),
realm_id: Set(api_user.realm_id),
client_id: Set(api_user.client_id),
name: Set(match payload.name {
Some(name) => name,
None => api_user.name,
}),
description: Set(match payload.description {
Some(description) => Some(description),
None => api_user.description,
}),
role: Set(match payload.role {
Some(role) => role,
None => api_user.role,
}),
access: Set(match payload.access {
Some(access) => access,
None => api_user.access,
}),
expires: Set(match payload.expires {
Some(expires) => expires.to_datetime(),
None => api_user.expires,
}),
locked_at: Set(match payload.lock {
Some(true) => Some(Utc::now().into()),
Some(false) => None,
None => api_user.locked_at,
}),
..Default::default()
};

let api_user = api_user_model.update(&state.db).await?;
Ok(Json(UpdateApiUserResponse::from(api_user)))
}

pub async fn delete_api_user(user: TokenUser, Path((realm_id, _client_id)): Path<(Uuid, Uuid)>) -> Result<Json<api_user::Model>, Error> {
pub async fn delete_api_user(
user: TokenUser,
Extension(state): Extension<Arc<AppState>>,
Path((realm_id, _client_id, api_user_id)): Path<(Uuid, Uuid, Uuid)>,
) -> Result<Json<DeleteResponse>, Error> {
if !is_master_realm_admin(&user) && !is_current_realm_admin(&user, &realm_id.to_string()) {
return Err(Error::Authenticate(AuthenticateError::NoResource));
}

todo!()
let delete_result = api_user::Entity::delete_by_id(api_user_id).exec(&state.db).await?;
Ok(Json(DeleteResponse {
ok: delete_result.rows_affected == 1,
}))
}
Loading

0 comments on commit 0b15f0c

Please sign in to comment.