Spiegel von
https://github.com/dani-garcia/vaultwarden.git
synchronisiert 2024-11-24 05:30:28 +01:00
Some fixes for emergency access (#4715)
- Add missing `Headers` parameter for some functions This allowed any request from allowing these endpoints by not validating the user correctly. - Changed the functions to retreive the emergency access record by using the user uuid which calls the endpoint, instead of validating afterwards. This is more secure and prevents the need of an if check.
Dieser Commit ist enthalten in:
Ursprung
247d0706ff
Commit
d04b94b77d
3 geänderte Dateien mit 116 neuen und 95 gelöschten Zeilen
|
@ -515,15 +515,12 @@ async fn post_rotatekey(data: Json<KeyData>, headers: Headers, mut conn: DbConn,
|
||||||
|
|
||||||
// Update emergency access data
|
// Update emergency access data
|
||||||
for emergency_access_data in data.emergency_access_keys {
|
for emergency_access_data in data.emergency_access_keys {
|
||||||
let mut saved_emergency_access = match EmergencyAccess::find_by_uuid(&emergency_access_data.id, &mut conn).await
|
let mut saved_emergency_access =
|
||||||
{
|
match EmergencyAccess::find_by_uuid_and_grantor_uuid(&emergency_access_data.id, user_uuid, &mut conn).await
|
||||||
Some(emergency_access) => emergency_access,
|
{
|
||||||
None => err!("Emergency access doesn't exist"),
|
Some(emergency_access) => emergency_access,
|
||||||
};
|
None => err!("Emergency access doesn't exist or is not owned by the user"),
|
||||||
|
};
|
||||||
if &saved_emergency_access.grantor_uuid != user_uuid {
|
|
||||||
err!("The emergency access is not owned by the user")
|
|
||||||
}
|
|
||||||
|
|
||||||
saved_emergency_access.key_encrypted = Some(emergency_access_data.key_encrypted);
|
saved_emergency_access.key_encrypted = Some(emergency_access_data.key_encrypted);
|
||||||
saved_emergency_access.save(&mut conn).await?
|
saved_emergency_access.save(&mut conn).await?
|
||||||
|
|
|
@ -93,10 +93,10 @@ async fn get_grantees(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/emergency-access/<emer_id>")]
|
#[get("/emergency-access/<emer_id>")]
|
||||||
async fn get_emergency_access(emer_id: &str, mut conn: DbConn) -> JsonResult {
|
async fn get_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_enabled()?;
|
check_emergency_access_enabled()?;
|
||||||
|
|
||||||
match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
match EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await {
|
||||||
Some(emergency_access) => Ok(Json(
|
Some(emergency_access) => Ok(Json(
|
||||||
emergency_access.to_json_grantee_details(&mut conn).await.expect("Grantee user should exist but does not!"),
|
emergency_access.to_json_grantee_details(&mut conn).await.expect("Grantee user should exist but does not!"),
|
||||||
)),
|
)),
|
||||||
|
@ -117,20 +117,31 @@ struct EmergencyAccessUpdateData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/emergency-access/<emer_id>", data = "<data>")]
|
#[put("/emergency-access/<emer_id>", data = "<data>")]
|
||||||
async fn put_emergency_access(emer_id: &str, data: Json<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult {
|
async fn put_emergency_access(
|
||||||
post_emergency_access(emer_id, data, conn).await
|
emer_id: &str,
|
||||||
|
data: Json<EmergencyAccessUpdateData>,
|
||||||
|
headers: Headers,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> JsonResult {
|
||||||
|
post_emergency_access(emer_id, data, headers, conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>", data = "<data>")]
|
#[post("/emergency-access/<emer_id>", data = "<data>")]
|
||||||
async fn post_emergency_access(emer_id: &str, data: Json<EmergencyAccessUpdateData>, mut conn: DbConn) -> JsonResult {
|
async fn post_emergency_access(
|
||||||
|
emer_id: &str,
|
||||||
|
data: Json<EmergencyAccessUpdateData>,
|
||||||
|
headers: Headers,
|
||||||
|
mut conn: DbConn,
|
||||||
|
) -> JsonResult {
|
||||||
check_emergency_access_enabled()?;
|
check_emergency_access_enabled()?;
|
||||||
|
|
||||||
let data: EmergencyAccessUpdateData = data.into_inner();
|
let data: EmergencyAccessUpdateData = data.into_inner();
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let mut emergency_access =
|
||||||
Some(emergency_access) => emergency_access,
|
match EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emergency_access) => emergency_access,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
let new_type = match EmergencyAccessType::from_str(&data.r#type.into_string()) {
|
let new_type = match EmergencyAccessType::from_str(&data.r#type.into_string()) {
|
||||||
Some(new_type) => new_type as i32,
|
Some(new_type) => new_type as i32,
|
||||||
|
@ -155,17 +166,21 @@ async fn post_emergency_access(emer_id: &str, data: Json<EmergencyAccessUpdateDa
|
||||||
async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||||
check_emergency_access_enabled()?;
|
check_emergency_access_enabled()?;
|
||||||
|
|
||||||
let grantor_user = headers.user;
|
let emergency_access = match (
|
||||||
|
EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await,
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &headers.user.uuid, &mut conn).await,
|
||||||
Some(emer) => {
|
) {
|
||||||
if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) {
|
(Some(grantor_emer), None) => {
|
||||||
err!("Emergency access not valid.")
|
info!("Grantor deleted emergency access {emer_id}");
|
||||||
}
|
grantor_emer
|
||||||
emer
|
|
||||||
}
|
}
|
||||||
None => err!("Emergency access not valid."),
|
(None, Some(grantee_emer)) => {
|
||||||
|
info!("Grantee deleted emergency access {emer_id}");
|
||||||
|
grantee_emer
|
||||||
|
}
|
||||||
|
_ => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
|
|
||||||
emergency_access.delete(&mut conn).await?;
|
emergency_access.delete(&mut conn).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -269,14 +284,11 @@ async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, mu
|
||||||
async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||||
check_emergency_access_enabled()?;
|
check_emergency_access_enabled()?;
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let mut emergency_access =
|
||||||
Some(emer) => emer,
|
match EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emer) => emer,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
if emergency_access.grantor_uuid != headers.user.uuid {
|
|
||||||
err!("Emergency access not valid.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if emergency_access.status != EmergencyAccessStatus::Invited as i32 {
|
if emergency_access.status != EmergencyAccessStatus::Invited as i32 {
|
||||||
err!("The grantee user is already accepted or confirmed to the organization");
|
err!("The grantee user is already accepted or confirmed to the organization");
|
||||||
|
@ -342,10 +354,13 @@ async fn accept_invite(emer_id: &str, data: Json<AcceptData>, headers: Headers,
|
||||||
None => err!("Invited user not found"),
|
None => err!("Invited user not found"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
// We need to search for the uuid in combination with the email, since we do not yet store the uuid of the grantee in the database.
|
||||||
Some(emer) => emer,
|
// The uuid of the grantee gets stored once accepted.
|
||||||
None => err!("Emergency access not valid."),
|
let mut emergency_access =
|
||||||
};
|
match EmergencyAccess::find_by_uuid_and_grantee_email(emer_id, &headers.user.email, &mut conn).await {
|
||||||
|
Some(emer) => emer,
|
||||||
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
// get grantor user to send Accepted email
|
// get grantor user to send Accepted email
|
||||||
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await {
|
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await {
|
||||||
|
@ -388,10 +403,11 @@ async fn confirm_emergency_access(
|
||||||
let data: ConfirmData = data.into_inner();
|
let data: ConfirmData = data.into_inner();
|
||||||
let key = data.key;
|
let key = data.key;
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let mut emergency_access =
|
||||||
Some(emer) => emer,
|
match EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &confirming_user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emer) => emer,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
if emergency_access.status != EmergencyAccessStatus::Accepted as i32
|
if emergency_access.status != EmergencyAccessStatus::Accepted as i32
|
||||||
|| emergency_access.grantor_uuid != confirming_user.uuid
|
|| emergency_access.grantor_uuid != confirming_user.uuid
|
||||||
|
@ -434,14 +450,13 @@ async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
|
||||||
check_emergency_access_enabled()?;
|
check_emergency_access_enabled()?;
|
||||||
|
|
||||||
let initiating_user = headers.user;
|
let initiating_user = headers.user;
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let mut emergency_access =
|
||||||
Some(emer) => emer,
|
match EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &initiating_user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emer) => emer,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
if emergency_access.status != EmergencyAccessStatus::Confirmed as i32
|
if emergency_access.status != EmergencyAccessStatus::Confirmed as i32 {
|
||||||
|| emergency_access.grantee_uuid != Some(initiating_user.uuid)
|
|
||||||
{
|
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,14 +488,13 @@ async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
|
||||||
async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_enabled()?;
|
check_emergency_access_enabled()?;
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let mut emergency_access =
|
||||||
Some(emer) => emer,
|
match EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emer) => emer,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
if emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32
|
if emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32 {
|
||||||
|| emergency_access.grantor_uuid != headers.user.uuid
|
|
||||||
{
|
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,23 +525,18 @@ async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbC
|
||||||
async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_enabled()?;
|
check_emergency_access_enabled()?;
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let mut emergency_access =
|
||||||
Some(emer) => emer,
|
match EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emer) => emer,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
if (emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32
|
if emergency_access.status != EmergencyAccessStatus::RecoveryInitiated as i32
|
||||||
&& emergency_access.status != EmergencyAccessStatus::RecoveryApproved as i32)
|
&& emergency_access.status != EmergencyAccessStatus::RecoveryApproved as i32
|
||||||
|| emergency_access.grantor_uuid != headers.user.uuid
|
|
||||||
{
|
{
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let grantor_user = match User::find_by_uuid(&headers.user.uuid, &mut conn).await {
|
|
||||||
Some(user) => user,
|
|
||||||
None => err!("Grantor user not found."),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() {
|
if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() {
|
||||||
let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await {
|
let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
|
@ -538,7 +547,7 @@ async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbCo
|
||||||
emergency_access.save(&mut conn).await?;
|
emergency_access.save(&mut conn).await?;
|
||||||
|
|
||||||
if CONFIG.mail_enabled() {
|
if CONFIG.mail_enabled() {
|
||||||
mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name).await?;
|
mail::send_emergency_access_recovery_rejected(&grantee_user.email, &headers.user.name).await?;
|
||||||
}
|
}
|
||||||
Ok(Json(emergency_access.to_json()))
|
Ok(Json(emergency_access.to_json()))
|
||||||
} else {
|
} else {
|
||||||
|
@ -554,10 +563,11 @@ async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbCo
|
||||||
async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_enabled()?;
|
check_emergency_access_enabled()?;
|
||||||
|
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let emergency_access =
|
||||||
Some(emer) => emer,
|
match EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &headers.user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emer) => emer,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
if !is_valid_request(&emergency_access, &headers.user.uuid, EmergencyAccessType::View) {
|
if !is_valid_request(&emergency_access, &headers.user.uuid, EmergencyAccessType::View) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
|
@ -592,10 +602,11 @@ async fn takeover_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
|
||||||
check_emergency_access_enabled()?;
|
check_emergency_access_enabled()?;
|
||||||
|
|
||||||
let requesting_user = headers.user;
|
let requesting_user = headers.user;
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let emergency_access =
|
||||||
Some(emer) => emer,
|
match EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &requesting_user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emer) => emer,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
|
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
|
@ -639,10 +650,11 @@ async fn password_emergency_access(
|
||||||
//let key = &data.Key;
|
//let key = &data.Key;
|
||||||
|
|
||||||
let requesting_user = headers.user;
|
let requesting_user = headers.user;
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let emergency_access =
|
||||||
Some(emer) => emer,
|
match EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &requesting_user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emer) => emer,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
|
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
|
@ -674,10 +686,11 @@ async fn password_emergency_access(
|
||||||
#[get("/emergency-access/<emer_id>/policies")]
|
#[get("/emergency-access/<emer_id>/policies")]
|
||||||
async fn policies_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn policies_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
let requesting_user = headers.user;
|
let requesting_user = headers.user;
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
let emergency_access =
|
||||||
Some(emer) => emer,
|
match EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &requesting_user.uuid, &mut conn).await {
|
||||||
None => err!("Emergency access not valid."),
|
Some(emer) => emer,
|
||||||
};
|
None => err!("Emergency access not valid."),
|
||||||
|
};
|
||||||
|
|
||||||
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
|
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
|
|
|
@ -238,15 +238,6 @@ impl EmergencyAccess {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> {
|
|
||||||
db_run! { conn: {
|
|
||||||
emergency_access::table
|
|
||||||
.filter(emergency_access::uuid.eq(uuid))
|
|
||||||
.first::<EmergencyAccessDb>(conn)
|
|
||||||
.ok().from_db()
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn find_by_grantor_uuid_and_grantee_uuid_or_email(
|
pub async fn find_by_grantor_uuid_and_grantee_uuid_or_email(
|
||||||
grantor_uuid: &str,
|
grantor_uuid: &str,
|
||||||
grantee_uuid: &str,
|
grantee_uuid: &str,
|
||||||
|
@ -281,6 +272,26 @@ impl EmergencyAccess {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_uuid_and_grantee_uuid(uuid: &str, grantee_uuid: &str, conn: &mut DbConn) -> Option<Self> {
|
||||||
|
db_run! { conn: {
|
||||||
|
emergency_access::table
|
||||||
|
.filter(emergency_access::uuid.eq(uuid))
|
||||||
|
.filter(emergency_access::grantee_uuid.eq(grantee_uuid))
|
||||||
|
.first::<EmergencyAccessDb>(conn)
|
||||||
|
.ok().from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_uuid_and_grantee_email(uuid: &str, grantee_email: &str, conn: &mut DbConn) -> Option<Self> {
|
||||||
|
db_run! { conn: {
|
||||||
|
emergency_access::table
|
||||||
|
.filter(emergency_access::uuid.eq(uuid))
|
||||||
|
.filter(emergency_access::email.eq(grantee_email))
|
||||||
|
.first::<EmergencyAccessDb>(conn)
|
||||||
|
.ok().from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
|
pub async fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
emergency_access::table
|
emergency_access::table
|
||||||
|
|
Laden …
In neuem Issue referenzieren