Files
wlan-cloud-ucentralsec/src/RESTAPI/RESTAPI_user_handler.cpp
2023-02-21 12:23:18 -08:00

350 lines
12 KiB
C++

//
// Created by stephane bourque on 2021-06-21.
//
#include "RESTAPI_user_handler.h"
#include "ACLProcessor.h"
#include "AuthService.h"
#include "MFAServer.h"
#include "RESTAPI/RESTAPI_db_helpers.h"
#include "SMSSender.h"
#include "SMTPMailerService.h"
#include "StorageService.h"
#include "TotpCache.h"
#include "framework/MicroServiceFuncs.h"
#include "framework/ow_constants.h"
namespace OpenWifi {
void RESTAPI_user_handler::DoGet() {
std::string Id = GetBinding("id", "");
if (Id.empty()) {
return BadRequest(RESTAPI::Errors::MissingUserID);
}
Poco::toLowerInPlace(Id);
std::string Arg;
SecurityObjects::UserInfo UInfo;
if (HasParameter("byEmail", Arg) && Arg == "true") {
if (!StorageService()->UserDB().GetUserByEmail(Id, UInfo)) {
return NotFound();
}
} else if (!StorageService()->UserDB().GetUserById(Id, UInfo)) {
return NotFound();
}
if (!ACLProcessor::Can(UserInfo_.userinfo, UInfo, ACLProcessor::READ)) {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
}
Poco::JSON::Object UserInfoObject;
Sanitize(UserInfo_, UInfo);
UInfo.to_json(UserInfoObject);
ReturnObject(UserInfoObject);
}
void RESTAPI_user_handler::DoDelete() {
std::string Id = GetBinding("id", "");
if (Id.empty()) {
return BadRequest(RESTAPI::Errors::MissingUserID);
}
SecurityObjects::UserInfo UInfo;
if (!StorageService()->UserDB().GetUserById(Id, UInfo)) {
return NotFound();
}
if (!ACLProcessor::Can(UserInfo_.userinfo, UInfo, ACLProcessor::DELETE)) {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
}
if (!StorageService()->UserDB().DeleteUser(UserInfo_.userinfo.email, Id)) {
return NotFound();
}
AuthService()->DeleteUserFromCache(Id);
StorageService()->AvatarDB().DeleteAvatar(UserInfo_.userinfo.email, Id);
StorageService()->PreferencesDB().DeletePreferences(UserInfo_.userinfo.email, Id);
StorageService()->UserTokenDB().RevokeAllTokens(Id);
StorageService()->ApiKeyDB().RemoveAllApiKeys(Id);
Logger_.information(
fmt::format("User '{}' deleted by '{}'.", Id, UserInfo_.userinfo.email));
OK();
}
void RESTAPI_user_handler::DoPost() {
std::string Id = GetBinding("id", "");
if (Id != "0") {
return BadRequest(RESTAPI::Errors::IdMustBe0);
}
SecurityObjects::UserInfo NewUser;
const auto &RawObject = ParsedBody_;
if (!NewUser.from_json(RawObject)) {
return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
}
if (NewUser.userRole == SecurityObjects::UNKNOWN) {
return BadRequest(RESTAPI::Errors::InvalidUserRole);
}
if (UserInfo_.userinfo.userRole == SecurityObjects::ROOT) {
NewUser.owner = GetParameter("entity", "");
} else {
NewUser.owner = UserInfo_.userinfo.owner;
}
if (!ACLProcessor::Can(UserInfo_.userinfo, NewUser, ACLProcessor::CREATE)) {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
}
Poco::toLowerInPlace(NewUser.email);
if (!Utils::ValidEMailAddress(NewUser.email)) {
return BadRequest(RESTAPI::Errors::InvalidEmailAddress);
}
SecurityObjects::UserInfo Existing;
if (StorageService()->SubDB().GetUserByEmail(NewUser.email, Existing)) {
return BadRequest(RESTAPI::Errors::UserAlreadyExists);
}
if (!NewUser.currentPassword.empty()) {
if (!AuthService()->ValidatePassword(NewUser.currentPassword)) {
return BadRequest(RESTAPI::Errors::InvalidPassword);
}
}
if (NewUser.name.empty())
NewUser.name = NewUser.email;
// You cannot enable MFA during user creation
NewUser.userTypeProprietaryInfo.mfa.enabled = false;
NewUser.userTypeProprietaryInfo.mfa.method = "";
NewUser.userTypeProprietaryInfo.mobiles.clear();
NewUser.userTypeProprietaryInfo.authenticatorSecret.clear();
NewUser.validated = true;
if (!StorageService()->UserDB().CreateUser(NewUser.email, NewUser)) {
Logger_.information(fmt::format("Could not add user '{}'.", NewUser.email));
return BadRequest(RESTAPI::Errors::RecordNotCreated);
}
if (GetBoolParameter("email_verification")) {
if (AuthService::VerifyEmail(NewUser))
Logger_.information(
fmt::format("Verification e-mail requested for {}", NewUser.email));
StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email, NewUser.id,
NewUser);
}
if (!StorageService()->UserDB().GetUserByEmail(NewUser.email, NewUser)) {
Logger_.information(fmt::format("User '{}' but not retrieved.", NewUser.email));
return NotFound();
}
Poco::JSON::Object UserInfoObject;
Sanitize(UserInfo_, NewUser);
NewUser.to_json(UserInfoObject);
ReturnObject(UserInfoObject);
Logger_.information(fmt::format("User '{}' has been added by '{}')", NewUser.email,
UserInfo_.userinfo.email));
}
void RESTAPI_user_handler::DoPut() {
std::string Id = GetBinding("id", "");
if (Id.empty()) {
return BadRequest(RESTAPI::Errors::MissingUserID);
}
SecurityObjects::UserInfo Existing;
if (!StorageService()->UserDB().GetUserById(Id, Existing)) {
return NotFound();
}
if (!ACLProcessor::Can(UserInfo_.userinfo, Existing, ACLProcessor::MODIFY)) {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
}
if (GetBoolParameter("resetMFA")) {
if ((UserInfo_.userinfo.userRole == SecurityObjects::ROOT) ||
(UserInfo_.userinfo.userRole == SecurityObjects::ADMIN &&
Existing.userRole != SecurityObjects::ROOT) ||
(UserInfo_.userinfo.id == Id)) {
Existing.userTypeProprietaryInfo.mfa.enabled = false;
Existing.userTypeProprietaryInfo.mfa.method.clear();
Existing.userTypeProprietaryInfo.mobiles.clear();
Existing.modified = OpenWifi::Now();
Existing.notes.push_back(
SecurityObjects::NoteInfo{.created = OpenWifi::Now(),
.createdBy = UserInfo_.userinfo.email,
.note = "MFA Reset by " + UserInfo_.userinfo.email});
StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email, Id, Existing);
SecurityObjects::UserInfo NewUserInfo;
StorageService()->UserDB().GetUserByEmail(UserInfo_.userinfo.email, NewUserInfo);
Poco::JSON::Object ModifiedObject;
Sanitize(UserInfo_, NewUserInfo);
NewUserInfo.to_json(ModifiedObject);
return ReturnObject(ModifiedObject);
} else {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
}
}
if (GetBoolParameter("forgotPassword")) {
Existing.changePassword = true;
Logger_.information(fmt::format("FORGOTTEN-PASSWORD({}): Request for {}",
Request->clientAddress().toString(), Existing.email));
SecurityObjects::ActionLink NewLink;
NewLink.action = OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD;
NewLink.id = MicroServiceCreateUUID();
NewLink.userId = Existing.id;
NewLink.created = OpenWifi::Now();
NewLink.expires = NewLink.created + (24 * 60 * 60);
NewLink.userAction = true;
StorageService()->ActionLinksDB().CreateAction(NewLink);
return OK();
}
SecurityObjects::UserInfo NewUser;
const auto &RawObject = ParsedBody_;
if (!NewUser.from_json(RawObject)) {
return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
}
// some basic validations
if (RawObject->has("userRole") &&
SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString()) ==
SecurityObjects::UNKNOWN) {
return BadRequest(RESTAPI::Errors::InvalidUserRole);
}
if (RawObject->has("owner")) {
if (UserInfo_.userinfo.userRole == SecurityObjects::ROOT && Existing.owner.empty()) {
AssignIfPresent(RawObject, "owner", Existing.owner);
}
}
// The only valid things to change are: changePassword, name,
AssignIfPresent(RawObject, "name", Existing.name);
AssignIfPresent(RawObject, "description", Existing.description);
AssignIfPresent(RawObject, "location", Existing.location);
AssignIfPresent(RawObject, "locale", Existing.locale);
AssignIfPresent(RawObject, "changePassword", Existing.changePassword);
AssignIfPresent(RawObject, "suspended", Existing.suspended);
AssignIfPresent(RawObject, "blackListed", Existing.blackListed);
if (RawObject->has("userRole")) {
auto NewRole =
SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString());
if (NewRole != Existing.userRole) {
if (UserInfo_.userinfo.userRole != SecurityObjects::ROOT &&
NewRole == SecurityObjects::ROOT) {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
}
if (Id == UserInfo_.userinfo.id) {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
}
Existing.userRole = NewRole;
}
}
if (RawObject->has("notes")) {
SecurityObjects::NoteInfoVec NIV;
NIV = RESTAPI_utils::to_object_array<SecurityObjects::NoteInfo>(
RawObject->get("notes").toString());
for (auto const &i : NIV) {
SecurityObjects::NoteInfo ii{.created = (uint64_t)OpenWifi::Now(),
.createdBy = UserInfo_.userinfo.email,
.note = i.note};
Existing.notes.push_back(ii);
}
}
if (RawObject->has("currentPassword")) {
if (!AuthService()->ValidatePassword(RawObject->get("currentPassword").toString())) {
return BadRequest(RESTAPI::Errors::InvalidPassword);
}
if (!AuthService()->SetPassword(RawObject->get("currentPassword").toString(),
Existing)) {
return BadRequest(RESTAPI::Errors::PasswordRejected);
}
}
if (GetBoolParameter("email_verification")) {
if (AuthService::VerifyEmail(Existing))
Logger_.information(
fmt::format("Verification e-mail requested for {}", Existing.email));
}
if (RawObject->has("userTypeProprietaryInfo")) {
if (NewUser.userTypeProprietaryInfo.mfa.enabled) {
if (!MFAMETHODS::Validate(NewUser.userTypeProprietaryInfo.mfa.method)) {
return BadRequest(RESTAPI::Errors::BadMFAMethod);
}
if (NewUser.userTypeProprietaryInfo.mfa.enabled &&
NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS &&
!SMSSender()->Enabled()) {
return BadRequest(RESTAPI::Errors::SMSMFANotEnabled);
}
if (NewUser.userTypeProprietaryInfo.mfa.enabled &&
NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL &&
!SMTPMailerService()->Enabled()) {
return BadRequest(RESTAPI::Errors::EMailMFANotEnabled);
}
Existing.userTypeProprietaryInfo.mfa.method =
NewUser.userTypeProprietaryInfo.mfa.method;
Existing.userTypeProprietaryInfo.mfa.enabled = true;
if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS) {
if (NewUser.userTypeProprietaryInfo.mobiles.empty()) {
return BadRequest(RESTAPI::Errors::NeedMobileNumber);
}
if (!SMSSender()->IsNumberValid(
NewUser.userTypeProprietaryInfo.mobiles[0].number,
UserInfo_.userinfo.email)) {
return BadRequest(RESTAPI::Errors::NeedMobileNumber);
}
Existing.userTypeProprietaryInfo.mobiles =
NewUser.userTypeProprietaryInfo.mobiles;
Existing.userTypeProprietaryInfo.mobiles[0].verified = true;
Existing.userTypeProprietaryInfo.authenticatorSecret.clear();
} else if (NewUser.userTypeProprietaryInfo.mfa.method ==
MFAMETHODS::AUTHENTICATOR) {
std::string Secret;
Existing.userTypeProprietaryInfo.mobiles.clear();
if (Existing.userTypeProprietaryInfo.authenticatorSecret.empty() &&
TotpCache()->CompleteValidation(UserInfo_.userinfo, false, Secret)) {
Existing.userTypeProprietaryInfo.authenticatorSecret = Secret;
} else if (!Existing.userTypeProprietaryInfo.authenticatorSecret.empty()) {
// we allow someone to use their old secret
} else {
return BadRequest(RESTAPI::Errors::AuthenticatorVerificationIncomplete);
}
} else if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL) {
Existing.userTypeProprietaryInfo.mobiles.clear();
Existing.userTypeProprietaryInfo.authenticatorSecret.clear();
}
} else {
Existing.userTypeProprietaryInfo.authenticatorSecret.clear();
Existing.userTypeProprietaryInfo.mobiles.clear();
Existing.userTypeProprietaryInfo.mfa.enabled = false;
}
}
Existing.modified = OpenWifi::Now();
if (StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email, Id, Existing)) {
SecurityObjects::UserInfo NewUserInfo;
StorageService()->UserDB().GetUserByEmail(UserInfo_.userinfo.email, NewUserInfo);
Poco::JSON::Object ModifiedObject;
Sanitize(UserInfo_, NewUserInfo);
NewUserInfo.to_json(ModifiedObject);
return ReturnObject(ModifiedObject);
}
BadRequest(RESTAPI::Errors::RecordNotUpdated);
}
} // namespace OpenWifi