2022-12-24 13:09:05 -05:00
|
|
|
// This file is part of Moonfire NVR, a security camera network video recorder.
|
|
|
|
// Copyright (C) 2022 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
|
|
|
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
|
|
|
|
|
|
|
|
//! User management: `/api/users/*`.
|
|
|
|
|
|
|
|
use base::{bail_t, format_err_t};
|
|
|
|
use http::{Method, Request, StatusCode};
|
|
|
|
|
2023-01-05 12:11:28 -06:00
|
|
|
use crate::json::{self, PutUsersResponse, UserSubset, UserSummary};
|
2022-12-24 13:09:05 -05:00
|
|
|
|
|
|
|
use super::{
|
2023-01-05 12:11:28 -06:00
|
|
|
bad_req, extract_json_body, plain_response, require_csrf_if_session, serve_json, Caller,
|
|
|
|
ResponseResult, Service,
|
2022-12-24 13:09:05 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
impl Service {
|
|
|
|
pub(super) async fn users(&self, req: Request<hyper::Body>, caller: Caller) -> ResponseResult {
|
2022-12-24 15:21:06 -05:00
|
|
|
match *req.method() {
|
|
|
|
Method::GET | Method::HEAD => self.get_users(req, caller).await,
|
|
|
|
Method::PUT => self.put_users(req, caller).await,
|
2022-12-24 15:34:27 -05:00
|
|
|
_ => Err(
|
|
|
|
plain_response(StatusCode::METHOD_NOT_ALLOWED, "GET, HEAD, or PUT expected").into(),
|
|
|
|
),
|
2022-12-24 15:21:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn get_users(&self, req: Request<hyper::Body>, caller: Caller) -> ResponseResult {
|
2022-12-24 13:09:05 -05:00
|
|
|
if !caller.permissions.admin_users {
|
|
|
|
bail_t!(Unauthenticated, "must have admin_users permission");
|
|
|
|
}
|
|
|
|
let users = self
|
|
|
|
.db
|
|
|
|
.lock()
|
|
|
|
.users_by_id()
|
|
|
|
.iter()
|
2023-01-05 12:11:28 -06:00
|
|
|
.map(|(&id, user)| UserSummary {
|
|
|
|
id,
|
|
|
|
username: user.username.clone(),
|
|
|
|
})
|
2022-12-24 13:09:05 -05:00
|
|
|
.collect();
|
2022-12-24 15:21:06 -05:00
|
|
|
serve_json(&req, &json::GetUsersResponse { users })
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn put_users(&self, mut req: Request<hyper::Body>, caller: Caller) -> ResponseResult {
|
|
|
|
if !caller.permissions.admin_users {
|
|
|
|
bail_t!(Unauthenticated, "must have admin_users permission");
|
|
|
|
}
|
|
|
|
let r = extract_json_body(&mut req).await?;
|
2023-01-05 12:11:28 -06:00
|
|
|
let mut r: json::PutUsers =
|
2022-12-25 22:50:05 -05:00
|
|
|
serde_json::from_slice(&r).map_err(|e| bad_req(e.to_string()))?;
|
2023-01-05 12:11:28 -06:00
|
|
|
require_csrf_if_session(&caller, r.csrf)?;
|
2022-12-24 15:21:06 -05:00
|
|
|
let username = r
|
2023-01-05 12:11:28 -06:00
|
|
|
.user
|
2022-12-24 15:21:06 -05:00
|
|
|
.username
|
2022-12-25 22:50:05 -05:00
|
|
|
.take()
|
2022-12-24 15:21:06 -05:00
|
|
|
.ok_or_else(|| format_err_t!(InvalidArgument, "username must be specified"))?;
|
|
|
|
let mut change = db::UserChange::add_user(username.to_owned());
|
2023-01-05 12:11:28 -06:00
|
|
|
if let Some(Some(pwd)) = r.user.password.take() {
|
2022-12-24 15:21:06 -05:00
|
|
|
change.set_password(pwd.to_owned());
|
|
|
|
}
|
2023-01-05 12:11:28 -06:00
|
|
|
if let Some(preferences) = r.user.preferences.take() {
|
2022-12-24 15:21:06 -05:00
|
|
|
change.config.preferences = preferences;
|
|
|
|
}
|
2023-01-05 12:11:28 -06:00
|
|
|
if let Some(permissions) = r.user.permissions.take() {
|
2022-12-24 15:21:06 -05:00
|
|
|
change.permissions = permissions.into();
|
|
|
|
}
|
2023-01-05 12:11:28 -06:00
|
|
|
if r.user != Default::default() {
|
2022-12-25 22:50:05 -05:00
|
|
|
bail_t!(Unimplemented, "unsupported user fields: {:#?}", r);
|
|
|
|
}
|
2022-12-24 15:21:06 -05:00
|
|
|
let mut l = self.db.lock();
|
|
|
|
let user = l.apply_user_change(change)?;
|
|
|
|
serve_json(&req, &PutUsersResponse { id: user.id })
|
2022-12-24 13:09:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) async fn user(
|
|
|
|
&self,
|
|
|
|
req: Request<hyper::Body>,
|
|
|
|
caller: Caller,
|
|
|
|
id: i32,
|
|
|
|
) -> ResponseResult {
|
|
|
|
match *req.method() {
|
2022-12-24 15:21:06 -05:00
|
|
|
Method::GET | Method::HEAD => self.get_user(req, caller, id).await,
|
2023-01-05 12:11:28 -06:00
|
|
|
Method::DELETE => self.delete_user(req, caller, id).await,
|
2022-12-24 13:09:05 -05:00
|
|
|
Method::POST => self.post_user(req, caller, id).await,
|
2022-12-24 15:34:27 -05:00
|
|
|
_ => Err(plain_response(
|
|
|
|
StatusCode::METHOD_NOT_ALLOWED,
|
|
|
|
"GET, HEAD, DELETE, or POST expected",
|
|
|
|
)
|
|
|
|
.into()),
|
2022-12-24 13:09:05 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-24 15:21:06 -05:00
|
|
|
async fn get_user(&self, req: Request<hyper::Body>, caller: Caller, id: i32) -> ResponseResult {
|
|
|
|
require_same_or_admin(&caller, id)?;
|
2022-12-24 13:09:05 -05:00
|
|
|
let db = self.db.lock();
|
|
|
|
let user = db
|
|
|
|
.users_by_id()
|
|
|
|
.get(&id)
|
|
|
|
.ok_or_else(|| format_err_t!(NotFound, "can't find requested user"))?;
|
|
|
|
let out = UserSubset {
|
2022-12-24 15:21:06 -05:00
|
|
|
username: Some(&user.username),
|
2022-12-24 13:09:05 -05:00
|
|
|
preferences: Some(user.config.preferences.clone()),
|
|
|
|
password: Some(if user.has_password() {
|
|
|
|
Some("(censored)")
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}),
|
2022-12-31 12:08:26 -05:00
|
|
|
permissions: Some(user.permissions.clone().into()),
|
2022-12-24 13:09:05 -05:00
|
|
|
};
|
|
|
|
serve_json(&req, &out)
|
|
|
|
}
|
|
|
|
|
2023-01-05 12:11:28 -06:00
|
|
|
async fn delete_user(
|
|
|
|
&self,
|
|
|
|
mut req: Request<hyper::Body>,
|
|
|
|
caller: Caller,
|
|
|
|
id: i32,
|
|
|
|
) -> ResponseResult {
|
2022-12-24 15:21:06 -05:00
|
|
|
if !caller.permissions.admin_users {
|
|
|
|
bail_t!(Unauthenticated, "must have admin_users permission");
|
|
|
|
}
|
2023-01-05 12:11:28 -06:00
|
|
|
let r = extract_json_body(&mut req).await?;
|
|
|
|
let r: json::DeleteUser = serde_json::from_slice(&r).map_err(|e| bad_req(e.to_string()))?;
|
|
|
|
require_csrf_if_session(&caller, r.csrf)?;
|
2022-12-24 15:21:06 -05:00
|
|
|
let mut l = self.db.lock();
|
|
|
|
l.delete_user(id)?;
|
|
|
|
Ok(plain_response(StatusCode::NO_CONTENT, &b""[..]))
|
|
|
|
}
|
|
|
|
|
2022-12-24 13:09:05 -05:00
|
|
|
async fn post_user(
|
|
|
|
&self,
|
|
|
|
mut req: Request<hyper::Body>,
|
|
|
|
caller: Caller,
|
|
|
|
id: i32,
|
|
|
|
) -> ResponseResult {
|
2022-12-24 15:21:06 -05:00
|
|
|
require_same_or_admin(&caller, id)?;
|
2022-12-24 13:09:05 -05:00
|
|
|
let r = extract_json_body(&mut req).await?;
|
|
|
|
let r: json::PostUser = serde_json::from_slice(&r).map_err(|e| bad_req(e.to_string()))?;
|
|
|
|
let mut db = self.db.lock();
|
|
|
|
let user = db
|
|
|
|
.get_user_by_id_mut(id)
|
|
|
|
.ok_or_else(|| format_err_t!(NotFound, "can't find requested user"))?;
|
2022-12-26 00:38:48 -05:00
|
|
|
if r.update.as_ref().and_then(|u| u.password).is_some()
|
|
|
|
&& r.precondition.as_ref().and_then(|p| p.password).is_none()
|
2022-12-24 13:09:05 -05:00
|
|
|
&& !caller.permissions.admin_users
|
|
|
|
{
|
|
|
|
bail_t!(
|
|
|
|
Unauthenticated,
|
|
|
|
"to change password, must supply previous password or have admin_users permission"
|
|
|
|
);
|
|
|
|
}
|
2023-01-05 12:11:28 -06:00
|
|
|
require_csrf_if_session(&caller, r.csrf)?;
|
2022-12-25 22:50:05 -05:00
|
|
|
if let Some(mut precondition) = r.precondition {
|
2022-12-31 12:08:26 -05:00
|
|
|
if matches!(precondition.username.take(), Some(n) if n != user.username) {
|
2022-12-25 22:50:05 -05:00
|
|
|
bail_t!(FailedPrecondition, "username mismatch");
|
|
|
|
}
|
|
|
|
if matches!(precondition.preferences.take(), Some(ref p) if p != &user.config.preferences)
|
|
|
|
{
|
2022-12-24 13:09:05 -05:00
|
|
|
bail_t!(FailedPrecondition, "preferences mismatch");
|
|
|
|
}
|
2022-12-25 22:50:05 -05:00
|
|
|
if let Some(p) = precondition.password.take() {
|
2022-12-24 13:09:05 -05:00
|
|
|
if !user.check_password(p)? {
|
|
|
|
bail_t!(FailedPrecondition, "password mismatch"); // or Unauthenticated?
|
|
|
|
}
|
|
|
|
}
|
2022-12-25 22:50:05 -05:00
|
|
|
if let Some(p) = precondition.permissions.take() {
|
2022-12-31 12:08:26 -05:00
|
|
|
if user.permissions != db::Permissions::from(p) {
|
2022-12-24 13:09:05 -05:00
|
|
|
bail_t!(FailedPrecondition, "permissions mismatch");
|
|
|
|
}
|
|
|
|
}
|
2022-12-25 22:50:05 -05:00
|
|
|
|
|
|
|
// Safety valve in case something is added to UserSubset and forgotten here.
|
|
|
|
if precondition != Default::default() {
|
|
|
|
bail_t!(
|
|
|
|
Unimplemented,
|
|
|
|
"preconditions not supported: {:#?}",
|
|
|
|
&precondition
|
|
|
|
);
|
|
|
|
}
|
2022-12-24 13:09:05 -05:00
|
|
|
}
|
2022-12-25 22:50:05 -05:00
|
|
|
if let Some(mut update) = r.update {
|
2022-12-24 13:09:05 -05:00
|
|
|
let mut change = user.change();
|
2022-12-26 00:38:48 -05:00
|
|
|
|
|
|
|
// First, set up updates which non-admins are allowed to perform on themselves.
|
2022-12-25 22:50:05 -05:00
|
|
|
if let Some(preferences) = update.preferences.take() {
|
2022-12-24 13:09:05 -05:00
|
|
|
change.config.preferences = preferences;
|
|
|
|
}
|
2022-12-25 22:50:05 -05:00
|
|
|
match update.password.take() {
|
2022-12-24 13:09:05 -05:00
|
|
|
None => {}
|
|
|
|
Some(None) => change.clear_password(),
|
|
|
|
Some(Some(p)) => change.set_password(p.to_owned()),
|
|
|
|
}
|
2022-12-26 00:38:48 -05:00
|
|
|
|
|
|
|
// Requires admin_users if there's anything else.
|
|
|
|
if update != Default::default() && !caller.permissions.admin_users {
|
|
|
|
bail_t!(Unauthenticated, "must have admin_users permission");
|
|
|
|
}
|
|
|
|
if let Some(n) = update.username.take() {
|
|
|
|
change.username = n.to_string();
|
|
|
|
}
|
2022-12-25 22:50:05 -05:00
|
|
|
if let Some(permissions) = update.permissions.take() {
|
2022-12-31 12:08:26 -05:00
|
|
|
change.permissions = permissions.into();
|
2022-12-25 22:50:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Safety valve in case something is added to UserSubset and forgotten here.
|
|
|
|
if update != Default::default() {
|
|
|
|
bail_t!(Unimplemented, "updates not supported: {:#?}", &update);
|
2022-12-24 13:09:05 -05:00
|
|
|
}
|
2022-12-26 00:38:48 -05:00
|
|
|
|
|
|
|
// Then apply all together.
|
2022-12-24 15:21:06 -05:00
|
|
|
db.apply_user_change(change)?;
|
2022-12-24 13:09:05 -05:00
|
|
|
}
|
|
|
|
Ok(plain_response(StatusCode::NO_CONTENT, &b""[..]))
|
|
|
|
}
|
|
|
|
}
|
2022-12-24 15:21:06 -05:00
|
|
|
|
|
|
|
fn require_same_or_admin(caller: &Caller, id: i32) -> Result<(), base::Error> {
|
|
|
|
if caller.user.as_ref().map(|u| u.id) != Some(id) && !caller.permissions.admin_users {
|
|
|
|
bail_t!(
|
|
|
|
Unauthenticated,
|
|
|
|
"must be authenticated as supplied user or have admin_users permission"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|