API CHANGE

Add new "usermod" action to the sysadm/users class. This is nearly identical to the "useradd" action, but performs changes to an existing user only (limited access users may modify their own settings, but not other users settings).

Additional OPTIONAL input: "newname" change the username to this instead.

REST Request (example):
-------------------------------
PUT /sysadm/users
{
   "action" : "usermod",
   "comment" : "somecomment",
   "name" : "test2"
}

WebSocket Request:
-------------------------------
{
   "name" : "users",
   "namespace" : "sysadm",
   "args" : {
      "name" : "test2",
      "comment" : "somecomment",
      "action" : "usermod"
   },
   "id" : "fooid"
}

Response:
-------------------------------
{
  "args": {
    "result": "success"
  },
  "id": "fooid",
  "name": "response",
  "namespace": "sysadm"
}
This commit is contained in:
Ken Moore
2016-07-25 14:45:43 -04:00
parent 86528334e0
commit 585beba03a
3 changed files with 261 additions and 0 deletions

View File

@@ -924,6 +924,16 @@ RestOutputStruct::ExitCode WebSocket::EvaluateSysadmUserRequest(bool allaccess,
}else{
out->insert("error","Cannot delete the current user");
}
}else if(action=="usermod"){
bool go = true;
if(!allaccess){
//ensure that the user being acted on is the current user - otherwise deny access
go = (in_args.toObject().value("name").toString() == user);
}
if(go){ ok = sysadm::UserManager::modifyUser(out, in_args.toObject() ); }
}
return (ok ? RestOutputStruct::OK : RestOutputStruct::BADREQUEST);
}

View File

@@ -0,0 +1,216 @@
//===========================================
// TrueOS source code
// Copyright (c) 2016, TrueOS Software/iXsystems
// Available under the 3-clause BSD license
// See the LICENSE file for full details
//===========================================
#include "sysadm-users.h"
#include "sysadm-general.h"
#include "sysadm-global.h"
#include "globals.h"
using namespace sysadm;
bool UserManager::listUsers(QJsonObject *out, bool showall, QString user){
QStringList args; args << "usershow";
if(showall){ args << "-a"; }
else{ args << user; }
bool ok = false;
QStringList users = sysadm::General::RunCommand(ok, "pw", args, "",QStringList() << "MM_CHARSET=UTF-8").split("\n");
if(ok){
//Get a list of all current PersonaCrypt users (if any)
bool PCinstalled = QFile::exists("/var/db/personacrypt");
//Go ahead and parse/list all the users
for(int i=0; i<users.length(); i++){
QStringList info = users[i].split(":");
if(info.length() == 10){
QJsonObject uinfo;
uinfo.insert("name", info[0]);
//uinfo.insert("name", info[1]); //Skip Password field (just a "*" in this viewer anyway)
uinfo.insert("uid", info[2]);
uinfo.insert("gid", info[3]);
uinfo.insert("class", info[4]);
uinfo.insert("change", info[5]);
uinfo.insert("expire", info[6]);
uinfo.insert("comment", info[7]);
uinfo.insert("home_dir", info[8]);
uinfo.insert("shell", info[9]);
if(PCinstalled && QFile::exists("/var/db/personacrypt/" + info[0] + ".key") ){ uinfo.insert("personacrypt_enabled","true"); }
out->insert(info[0], uinfo); //use the username as the unique object name
}else if(info.length() == 7){
QJsonObject uinfo;
uinfo.insert("name", info[0]);
//uinfo.insert("name", info[1]); //Skip Password field (just a "*" in this viewer anyway)
uinfo.insert("uid", info[2]);
uinfo.insert("gid", info[3]);
uinfo.insert("comment", info[4]);
uinfo.insert("home_dir", info[5]);
uinfo.insert("shell", info[6]);
if(PCinstalled && QFile::exists("/var/db/personacrypt/" + info[0] + ".key") ){ uinfo.insert("personacrypt_enabled","true"); }
out->insert(info[0], uinfo); //use the username as the unique object name
}
}
}else{
//Bad result from "pw" - inputs were just fine (just return nothing)
ok = true;
}
if(!ok){ out->insert("error",users.join("\n")); } //should never happen - "pw" is an OS built-in on FreeBSD
return ok;
}
bool UserManager::addUser(QJsonObject* out, QJsonObject obj){
bool ok = false;
//REQUIRED: "name" AND "password"
//OPTIONAL: "uid", "comment", "home_dir", "expire", "change", "shell", "group", "other_groups", "class"
//OPTIONAL: "personacrypt_init"=<devicename>, "personacrypt_password"
//OPTIONAL: "personacrypt_import"=<contents of key file - base64 encoded>
if(obj.contains("password") && obj.contains("name") ){
QString username = obj.value("name").toString();
QStringList args; args << "useradd";
args << "-n" << username;
if(obj.contains("uid")){ args << "-u" << obj.value("uid").toString(); }
if(obj.contains("comment")){ args << "-c" << obj.value("comment").toString(); }
if(obj.contains("home_dir")){ args << "-d" << obj.value("home_dir").toString(); }
if(obj.contains("expire")){ args << "-e" << obj.value("expire").toString(); }
if(obj.contains("change")){ args << "-p" << obj.value("change").toString(); }
if(obj.contains("shell")){ args << "-s" << obj.value("shell").toString(); }
if(obj.contains("group")){ args << "-g" << obj.value("group").toString(); }
if(obj.contains("other_groups")){
if(obj.value("other_groups").isString()){ args << "-G" << obj.value("other_groups").toString(); }
else if(obj.value("other_groups").isArray()){ args << "-G" << General::JsonArrayToStringList(obj.value("other_groups").toArray()).join(","); }
}
if(obj.contains("class")){ args << "-L" << obj.value("class").toString(); }
//See if PersonaCrypt should be used on this user as well
QString PCdev;
if(obj.contains("personacrypt_init")){
QString dev = obj.value("personacrypt_init").toString();
if(dev.startsWith("/dev/")){ dev = dev.remove(0,5); }
//Verify that the given device is valid
QStringList valid = getAvailablePersonaCryptDevices();
for(int i=0; i<valid.length(); i++){
if(valid[i].startsWith(dev+": ")){ PCdev = dev; } //this is a valid device
}
if(PCdev!=dev){ return false; } //invalid inputs - invalid device for PersonaCrypt
}
QTemporaryFile pwfile("/tmp/.XXXXXXXXXXXXXXX");
bool usercreated = false;
if(pwfile.open()){
pwfile.write( obj.value("password").toString().toUtf8().data() );
pwfile.close(); //closed but still exists - will go out of scope and get removed in a moment
args << "-h" << "0"; //read from std input
ok = (0== system("cat "+pwfile.fileName().toUtf8()+" | pw "+args.join(" ").toUtf8()) );
usercreated = ok;
}else{
out->insert("error","Could not open temporary file for password"); //should never happen
}
if(usercreated && !PCdev.isEmpty()){
// INIT PERSONACRYPT DEVICE NOW
//User created fine - go ahead and initialize the PersonaCrypt device for this user now
QString pass = obj.value("password").toString(); //assume the same password as the user unless specified otherwise
if(obj.contains("personacrypt_password")){ pass = obj.value("personacrypt_password").toString(); } //separate password for device
ok = InitializePersonaCryptDevice(username, pass, PCdev);
if(!ok){ out->insert("error","could not initialize personacrypt device"); }
}else if(usercreated && obj.contains("personacrypt_import")){
// IMPORT PERSONACRYPT KEY NOW
ok = false;
//Need to save key to a local file temporarily
QTemporaryFile keyfile("/tmp/.XXXXXXXXXXXXXXX");
if(keyfile.open()){
keyfile.write( QByteArray::fromBase64(obj.value("personacrypt_import").toString().toUtf8()).data() );
keyfile.close();
ok = ImportPersonaCryptKey(keyfile.fileName());
}
if(!ok){ out->insert("error","could not import personacrypt key"); }
}
if(usercreated && !ok){
//One of the personacrypt options failed - treat this as a full failure and also remove the newly-created user
removeUser(username);
}
} //end check for valid inputs
if(ok){ out->insert("result","success"); }
return ok;
}
bool UserManager::removeUser(QString username, bool deletehomedir){
bool ok = false;
QStringList args;
args << "userdel" << "-n" << username;
if(deletehomedir){ args << "-r"; }
ok = General::RunQuickCommand("pw", args);
return ok;
}
bool UserManager::modifyUser(QJsonObject* out, QJsonObject obj){
bool ok = false;
// REQUIRED: "name"
//OPTIONAL: "newname", "uid", "comment", "home_dir", "expire", "change", "shell", "group", "other_groups", "class"
//OPTIONAL: "personacrypt_init"=<devicename>, "personacrypt_password"
//OPTIONAL: "personacrypt_import"=<contents of key file - base64 encoded>
if(obj.contains("name") ){
QString username = obj.value("name").toString();
QStringList args; args << "usermod";
args << "-n" << username;
if(obj.contains("uid")){ args << "-u" << obj.value("uid").toString(); }
if(obj.contains("comment")){ args << "-c" << obj.value("comment").toString(); }
if(obj.contains("home_dir")){ args << "-d" << obj.value("home_dir").toString(); }
if(obj.contains("expire")){ args << "-e" << obj.value("expire").toString(); }
if(obj.contains("change")){ args << "-p" << obj.value("change").toString(); }
if(obj.contains("shell")){ args << "-s" << obj.value("shell").toString(); }
if(obj.contains("group")){ args << "-g" << obj.value("group").toString(); }
if(obj.contains("other_groups")){
if(obj.value("other_groups").isString()){ args << "-G" << obj.value("other_groups").toString(); }
else if(obj.value("other_groups").isArray()){ args << "-G" << General::JsonArrayToStringList(obj.value("other_groups").toArray()).join(","); }
}
if(obj.contains("class")){ args << "-L" << obj.value("class").toString(); }
if(obj.contains("newname")){ args << "-l" << obj.value("newname").toString(); }
if(obj.contains("password")){
//changing password too - need to handle differently
QTemporaryFile pwfile("/tmp/.XXXXXXXXXXXXXXX");
bool usercreated = false;
if(pwfile.open()){
pwfile.write( obj.value("password").toString().toUtf8().data() );
pwfile.close(); //closed but still exists - will go out of scope and get removed in a moment
args << "-h" << "0"; //read from std input
ok = (0== system("cat "+pwfile.fileName().toUtf8()+" | pw "+args.join(" ").toUtf8()) );
}else{
out->insert("error","Could not open temporary file for password"); //should never happen
}
}else{
//No password change - simple command
ok = General::RunQuickCommand("pw", args);
}
}
if(ok){ out->insert("result","success"); }
else{ out->insert("error","Could not modify user"); }
return ok;
}
// === PERSONACRYPT FUNCTIONS ===
//List all the devices currently available to be used for a PersonaCrypt User
QStringList UserManager::getAvailablePersonaCryptDevices(){
QStringList devs = General::RunCommand("personacrypt", QStringList() << "list" << "-r").split("\n");
for(int i=0; i<devs.length(); i++){
//Check validity of each line really quick (no gpart errors output and such)
if(devs[i].isEmpty() || devs[i].startsWith("gpart")){ devs.removeAt(i); i--; }
}
return devs;
}
//PersonaCrypt Modification functions
bool UserManager::InitializePersonaCryptDevice(QString username, QString pass, QString device){
QTemporaryFile pfile("/tmp/.XXXXXXXXXXXXXXX");
bool ok = false;
if( pfile.open() ){
pfile.write(pass.toUtf8().data());
pfile.close();
ok = General::RunQuickCommand("personacrypt", QStringList() << "init" << username << pfile.fileName() << device);
}
return ok;
}
bool UserManager::ImportPersonaCryptKey(QString keyfile){
if(!QFile::exists(keyfile)){ return false; } //quick check first
bool ok = General::RunQuickCommand("personacrypt", QStringList() << "import" << keyfile);
return ok;
}

View File

@@ -0,0 +1,35 @@
//===========================================
// TrueOS source code
// Copyright (c) 2016, TrueOS Software/iXsystems
// Available under the 3-clause BSD license
// See the LICENSE file for full details
//===========================================
#ifndef __TRUEOS_LIB_UTILS_USERMANAGER_H
#define __TRUEOS_LIB_UTILS_USERMANAGER_H
#include <QJsonObject>
#include "sysadm-global.h"
namespace sysadm{
class UserManager{
public:
//List all the users currently registered on the system
static bool listUsers(QJsonObject* out, bool showall, QString user = "");
//Add a new user to the system
static bool addUser(QJsonObject* out, QJsonObject inputs);
static bool removeUser(QString username, bool deletehomedir = true);
static bool modifyUser(QJsonObject* out, QJsonObject inputs);
//List all the devices currently available to be used for a PersonaCrypt User
static QStringList getAvailablePersonaCryptDevices();
//PersonaCrypt Modification functions
static bool InitializePersonaCryptDevice(QString username, QString pass, QString device);
static bool ImportPersonaCryptKey(QString keyfile);
};
} //end of sysadm namespace
#endif