start of user management
authorkonrad <konrad@6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33>
Sun, 13 Jan 2008 18:01:08 +0000 (18:01 +0000)
committerkonrad <konrad@6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33>
Sun, 13 Jan 2008 18:01:08 +0000 (18:01 +0000)
git-svn-id: https://silmor.de/svn/softmagic/smoke/trunk@83 6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33

doc/prog_protocol.html
src/overview.cpp
src/overview.h
src/smoke.pro
src/webrequest.cpp
src/webrequest.h
www/inc/db_mysql.php
www/inc/session.php
www/machine.php

index 486d1c3..2f5205c 100644 (file)
@@ -4,7 +4,7 @@
 <h1>Magic Smoke Protocol Documentation</h1>
 <img src="logo.png" alt="logo" valign="left" align="top"/><br/>
 
-The Magic Smoke Protocol is used between the Magic Smoke Server and Client. It is based on HTTP and XML. Only POST requests are allowed.<p>
+The Magic Smoke Protocol is used between the Magic Smoke Server and Client. It is based on HTTP and XML. Only POST requests are allowed. All body data is presumed to be in UTF-8 encoding, headers are restricted to ASCII-127.<p>
 
 Each request must have a "x-magicsmoke-request=..." header. The actual request data is sent as POST-data. Except for the <tt>serverinfo</tt> and <tt>startsession</tt> requests all requests must also carry a "x-magicsmoke-session" header.
 
@@ -110,6 +110,12 @@ This is done with a <tt>sessionclose</tt> request. Neither request nor response
 
 This request always yields an "Ok" status response regardless of whether the session ID was still valid or not.
 
+<h3>Note on Usernames and Roles</h3>
+
+Both, user names and roles, are restricted to letters, digits and "_". Leading and trailing whitespace is ignored (trimmed).<p>
+
+Each role corresponds to a function that can be called and executed remotely through the machine interface. Roles beginning with "_" are reserved keywords that can alter the behaviour of other roles and must not be used to name functions.
+
 <h2>Basic Requests</h2>
 
 <h3>Getting ACL info</h3>
@@ -231,3 +237,46 @@ The <tt>setroomdata</tt> transaction is used to create/change one or more rooms,
 </pre>
 
 The response simply contains the status "Ok" or "Error".
+
+<h2>ACL related Transactions</h2>
+
+<h3>Getting User Info</h3>
+
+The <tt>getusers</tt> transaction returns the names and descriptions of all users in the system. The request is empty, the response looks like:<p>
+
+<pre>
+&lt;Users>
+  &lt;User name="loginname">Description of User&lt;/User>
+  ...
+&lt;/Users>
+</pre>
+
+<h3>Adding Users and Setting New Descriptions</h3>
+
+The <tt>setuserdescription</tt> call can be used to set new descriptions for existing users. The call may silently fail for any user that does not exist, hence the client should request user data from the server to confirm whether the call succeeded.<p>
+
+The <tt>adduser</tt> call can be used to create new user accounts. Initially the new user will have no ACL and no host settings.<p>
+
+Both calls use this structure for the request:<p>
+
+<pre>
+&lt;Users>
+  &lt;User name="loginname">Description of User&lt;/User>
+  ...
+&lt;/Users>
+</pre>
+
+The <tt>adduser</tt> call also uses it for the response, leaving out any user name that already existed or does not conform to the syntax requirements.
+
+<h3>Getting User ACLs</h3>
+
+The <tt>getuseracl</tt> transaction returns the ACL of a specific user. The request contains only the login name of the user. The response looks like:<p>
+
+<pre>
+&lt;ACL user="username">
+  &lt;Role name="rolename" set="1|0"/>
+  ...
+&lt;/ACL>
+</pre>
+
+All setable roles must be listed, so that the client can use these for displaying in a dialog.
index 2e06dd9..34c71c4 100644 (file)
@@ -16,6 +16,7 @@
 
 #include <QSettings>
 #include <QMessageBox>
+#include <QInputDialog>
 #include <QTabWidget>
 #include <QStatusBar>
 #include <QMenuBar>
@@ -117,6 +118,36 @@ MOverview::MOverview(MWebRequest*mw,QString pk)
        hl->addWidget(new QPushButton(tr("Save Order")));
        hl->addWidget(new QPushButton(tr("Clear")));
        
+       //user tab
+       tab->addTab(usertab=new QWidget,tr("Users"));
+       usertab->setLayout(hl=new QHBoxLayout);
+       hl->addWidget(usertable=new QTableView,10);
+       usertable->setModel(usermodel=new QStandardItemModel(this));
+       usertable->setSelectionMode(QAbstractItemView::SingleSelection);
+       usertable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+       hl->addSpacing(5);
+       hl->addLayout(vl=new QVBoxLayout,0);
+       vl->addWidget(p=new QPushButton(tr("New User...")),0);
+       connect(p,SIGNAL(clicked()),this,SLOT(newUser()));
+       p->setEnabled(req->hasRole("adduser"));
+       vl->addWidget(p=new QPushButton(tr("Delete User...")),0);
+       connect(p,SIGNAL(clicked()),this,SLOT(deleteUser()));
+       p->setEnabled(req->hasRole("deleteuser"));
+       vl->addSpacing(20);
+       vl->addWidget(p=new QPushButton(tr("Description...")),0);
+       connect(p,SIGNAL(clicked()),this,SLOT(editUserDescription()));
+       p->setEnabled(req->hasRole("setuserdescription"));
+       vl->addWidget(p=new QPushButton(tr("Hosts...")),0);
+       connect(p,SIGNAL(clicked()),this,SLOT(editUserHosts()));
+       p->setEnabled(req->hasRole("getuserhosts"));
+       vl->addWidget(p=new QPushButton(tr("Roles...")),0);
+       connect(p,SIGNAL(clicked()),this,SLOT(editUserRoles()));
+       p->setEnabled(req->hasRole("getuseracl"));
+       vl->addStretch(10);
+       
+       //host tab
+       tab->addTab(hosttab=new QWidget,tr("Hosts"));
+       
        //status bar
        statusBar()->setSizeGripEnabled(true);
        
@@ -125,6 +156,19 @@ MOverview::MOverview(MWebRequest*mw,QString pk)
                updateEvents();
        }else{
                eventtab->setEnabled(false);
+               tab->setTabEnabled(tab->indexOf(eventtab),false);
+       }
+       if(req->hasRole("getusers")){
+               updateUsers();
+       }else{
+               usertab->setEnabled(false);
+               tab->setTabEnabled(tab->indexOf(usertab),false);
+       }
+       if(req->hasRole("gethosts")){
+               updateHosts();
+       }else{
+               hosttab->setEnabled(false);
+               tab->setTabEnabled(tab->indexOf(hosttab),false);
        }
 }
 
@@ -185,3 +229,63 @@ void MOverview::editEvent()
        ed.exec();
        updateEvents();
 }
+
+void MOverview::updateUsers()
+{
+       QList<MUser>usl=req->getAllUsers();
+       usermodel->clear();
+       usermodel->insertColumns(0,2);
+       usermodel->insertRows(0,usl.size());
+       usermodel->setHorizontalHeaderLabels(QStringList()<<tr("Login Name")<<tr("Description"));
+       for(int i=0;i<usl.size();i++){
+               usermodel->setData(usermodel->index(i,0),usl[i].userId());
+               usermodel->setData(usermodel->index(i,1),usl[i].description());
+       }
+       usertable->resizeColumnToContents(0);
+       usertable->resizeColumnToContents(1);
+}
+
+void MOverview::newUser()
+{
+       //get name
+       QString name;
+       while(1){
+               bool ok;
+               name=QInputDialog::getText(this,tr("New User"),tr("Please enter new user name (only letters, digits, and underscore allowed):"),QLineEdit::Normal,name,&ok);
+               if(!ok)
+                       return;
+               if(QRegExp("[A-Za-z0-9_]+").exactMatch(name))
+                       break;
+               if(QMessageBox::warning(this,tr("Error"),tr("The user name must contain only letters, digits, and underscores and must be at least one character long!"),QMessageBox::Retry|QMessageBox::Abort)!=QMessageBox::Retry)
+                       return;
+       }
+       //send request
+       MUser(req,name).create();
+       //update display
+       updateUsers();
+}
+
+void MOverview::deleteUser(){}
+
+void MOverview::editUserDescription()
+{
+       //get selection
+       QModelIndex sel=usertable->currentIndex();
+       if(!sel.isValid())return;
+       //get uname & descr
+       QString name=usermodel->data(usermodel->index(sel.row(),0)).toString();
+       QString descr=usermodel->data(usermodel->index(sel.row(),1)).toString();
+       //edit descr
+       bool ok;
+       descr=QInputDialog::getText(this,tr("Edit Description"),tr("Descriptionof user %1:").arg(name),QLineEdit::Normal,descr,&ok);
+       if(ok)
+               MUser(req,name).setDescription(descr);
+       //update
+       updateUsers();
+}
+
+void MOverview::editUserRoles(){}
+void MOverview::editUserHosts(){}
+
+
+void MOverview::updateHosts(){}
index cd97875..dbd0f46 100644 (file)
@@ -35,12 +35,29 @@ class MOverview:public QMainWindow
        private slots:
                /**try to log in again*/
                void relogin();
+               
                /**create a new event*/
                void newEvent();
                /**edit existing event*/
                void editEvent();
                /**update list of events*/
                void updateEvents();
+               
+               /**update list of users*/
+               void updateUsers();
+               /**create new user*/
+               void newUser();
+               /**delete selected user*/
+               void deleteUser();
+               /**set users description*/
+               void editUserDescription();
+               /**set users roles*/
+               void editUserRoles();
+               /**set users hosts*/
+               void editUserHosts();
+               
+               /**update list of hosts*/
+               void updateHosts();
        private:
                //my session object
                QPointer<MWebRequest>req;
@@ -48,9 +65,9 @@ class MOverview:public QMainWindow
                QString profilekey;
                //widgets
                QTabWidget*tab;
-               QWidget*eventtab,*carttab;
-               QTableView*eventtable;
-               QStandardItemModel*eventmodel;
+               QWidget*eventtab,*carttab,*usertab,*hosttab;
+               QTableView*eventtable,*usertable,*hosttable;
+               QStandardItemModel*eventmodel,*usermodel,*hostmodel;
 };
 
 #endif
index a09b10f..c0d7e71 100644 (file)
@@ -31,7 +31,8 @@ SOURCES = \
        overview.cpp \
        eventedit.cpp \
        event.cpp \
-       room.cpp
+       room.cpp \
+       user.cpp
 HEADERS = \
        keygen.h \
        mainwindow.h \
@@ -40,7 +41,8 @@ HEADERS = \
        overview.h \
        eventedit.h \
        event.h \
-       room.h
+       room.h \
+       user.h
 
 RESOURCES += files.qrc
 
index dd4da22..d9be3f4 100644 (file)
@@ -60,8 +60,8 @@ bool MWebRequest::request(QString hreq,QByteArray data)
        hrh.setValue("X-MagicSmoke-Request",hreq);
        hrh.setValue("X-MagicSmoke-Session",sessionid);
        hrh.setContentLength(data.size());
-       hrh.setContentType("application/x-magicsmoke");
-       qDebug("Requesting data with:\n%s\n\n%s",hrh.toString().toAscii().data(),data.data());
+       hrh.setContentType("application/x-magicsmoke; charset=UTF-8");
+       qDebug("----------------------->\nRequesting data with:\n%s\n\nRequest Body:\n%s\n<---->",hrh.toString().toAscii().data(),data.data());
        waitid=req.request(hrh,data);
        qDebug("started req %i",waitid);
        fin=false;errstr="";
@@ -96,8 +96,8 @@ bool MWebRequest::request(QString hreq,QByteArray data)
        rspdata=req.readAll();
        rspstatus=rsph.value("x-magicsmoke-status").remove('"').trimmed().toLower();
        //
-       qDebug("HTTP Response Headers:\n%s",rsph.toString().toAscii().data());
-       qDebug("HTTP Response Body:\n%s\n<-->",rspdata.data());
+       qDebug("----->\nHTTP Response Headers:\n%s",rsph.toString().toAscii().data());
+       qDebug("HTTP Response Body:\n%s\n<------------------",rspdata.data());
        return true;
 }
 
@@ -344,6 +344,29 @@ QList<MRoom> MWebRequest::getAllRooms()
        return ret;
 }
 
+QList<MUser> MWebRequest::getAllUsers()
+{
+       errstr="";
+       if(!request("getusers",""))return QList<MUser>();
+       //parse return document
+       QDomDocument doc;
+       QString msg;int ln,cl;
+       if(!doc.setContent(rspdata,&msg,&ln,&cl)){
+               errstr=tr("Error parsing EventList XML data (line %1 column %2): %3").arg(ln).arg(cl).arg(msg);
+               return QList<MUser>();
+       }
+       QDomElement root=doc.documentElement();
+       QDomNodeList nl=root.elementsByTagName("User");
+       QList<MUser>ret;
+       for(int i=0;i<nl.size();i++){
+               QDomElement el=nl.at(i).toElement();
+               if(el.isNull())continue;
+               MUser us(this,el);
+               if(us.isValid())ret.append(us);
+       }
+       return ret;
+}
+
 QByteArray MWebRequest::responseBody()
 {
        return rspdata;
index a979270..2d0c405 100644 (file)
@@ -24,6 +24,7 @@
 
 #include "room.h"
 #include "event.h"
+#include "user.h"
 
 /**abstraction of requests to the web server, handles sessions and all data transfer*/
 class MWebRequest:public QObject
@@ -72,6 +73,9 @@ class MWebRequest:public QObject
                /**returns a list of all rooms (without description, which is loaded dynamically if requested)*/
                QList<MRoom>getAllRooms();
                
+               /**returns a list of all users**/
+               QList<MUser>getAllUsers();
+               
        public slots:
                /**set how long to wait for a web request*/
                void setTimeout(int);
index 8f6e2dd..7b4d17e 100644 (file)
@@ -11,6 +11,7 @@ class MysqlEngine extends DbEngine
        private $dbhdl=false;
        private $dbname="";
        private $engine="InnoDB";
+       private $charset="utf8";
        
        /**initialize driver*/
        public function __construct($server,$user,$pass)
@@ -40,11 +41,16 @@ class MysqlEngine extends DbEngine
        
        public function tryConnect()
        {
+               //connect
                $this->dbhdl=mysql_connect($this->server,$this->user,$this->pass);
                if($this->dbhdl===false)
                        die("Unable to connect to database system. Giving up.");
+               //select DB
                if(mysql_query("use $this->dbname",$this->dbhdl)===false)
                        die("Unable to select database \"$this->dbname\". Giving up.");
+               //TODO: select Unicode; TODO: fix it to be configurable
+               //if(mysql_query("SET NAMES 'utf8'"))
+               //      die("cannot set character set to utf-8");
        }
        
        public function haveTable($tnm)
index cfcdf89..ddac732 100644 (file)
@@ -2,7 +2,7 @@
 //
 // PHP Implementation: session
 //
-// Description: 
+// Description: implements session and ACL management
 //
 //
 // Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2007
@@ -217,4 +217,101 @@ class Session
        }
 };
 
+//return all users to client
+function getAllUsersXml()
+{
+       global $db;
+       header("X-MagicSmoke-Status: Ok");
+       $res=$db->select("users","uname,description","");
+       print("<Users>\n");
+       for($i=0;$i<count($res);$i++){
+               print("<User name=\"".htmlentities($res[$i]["uname"])."\">".htmlentities($res[$i]["description"])."</User>\n");
+       }
+       print("</Users>");
+}
+
+//return the roles of a specific user
+function getUserAclXml($user)
+{
+       //sanity check
+       $user=trim($user);
+       if(ereg("^[A-Za-z0-9_]+$",$user)===false){
+               header("X-MagicSmoke-Status: SyntaxError");
+               die("invalid user name");
+       }
+       //go on...
+       global $db,$ALLOWEDREQUESTS;
+       header("X-MagicSmoke-Status: Ok");
+       //create list of roles
+       $roles=$ALLOWEDREQUESTS;
+       $roles[]="_admin";
+       //get roles from DB
+       $res=$db->select("userrole","role","uname=".$user);
+       $acl=array();
+       foreach($res as $rl)$acl[]=$rl["role"];
+       print("<ACL user=\"$user\">\n");
+       foreach($roles as $rl){
+               print("<Role name=\"$rl\" set=\"");
+               if(array_search($rl,$acl)===false)print("0");
+               else print("1");
+               print("\">\n");
+       }
+       print("</ACL>\n");
+}
+
+//helper function: parse User-XML-structure
+function parseUserXml($txt)
+{
+       $xml=new DOMDocument;
+       if(!$xml->loadXML($txt)){
+               header("X-MagicSmoke-Status: SyntaxError");
+               die("unable to parse XML data");
+       }
+       $ret=array();
+       foreach($xml->getElementsByTagName("User") as $el){
+               $usr["name"]=$el->getAttribute("name");
+               $usr["descr"]="";
+               foreach($el->childNodes as $cn)
+                       if($cn->nodeType==XML_TEXT_NODE)
+                               $usr["descr"]=$cn->wholeText;
+               $ret[]=$usr;
+       }
+       return $ret;
+}
+
+//set new description for user
+function setUserDescrXml($txt)
+{
+       global $db;
+       $usr=parseUserXml($txt);
+       for($i=0;$i<count($usr);$i++){
+               $db->update("users",array("description"=>$usr[$i]["descr"]),"uname=".$db->escapeString($usr[$i]["name"]));
+       }
+       header("X-MagicSmoke-Status: Ok");
+}
+
+//add a new user
+function addUserXml($txt)
+{
+       global $db;
+       $usr=parseUserXml($txt);
+       header("X-MagicSmoke-Status: Ok");
+       print("<Users>\n");
+       for($i=0;$i<count($usr);$i++){
+               //syntax check
+               if(ereg("^[A-Za-z0-9_]+$",$usr[$i]["name"])===false)continue;
+               //existance check
+               $db->beginTransaction();
+               $res=$db->select("users","uname","uname='".$usr[$i]["name"]."'");
+               if(count($res)==0){
+                       //create new
+                       $db->insert("users",array("uname"=>$usr[$i]["name"],"description"=>$usr[$i]["descr"]));
+                       //print data
+                       print("<User name=\"".htmlentities($usr[$i]["name"])."\">".htmlentities($usr[$i]["descr"])."</User>\n");
+               }
+               $db->commitTransaction();
+       }
+       print("</Users>\n");
+}
+
 ?>
\ No newline at end of file
index d2b28b1..cf024a2 100644 (file)
@@ -14,8 +14,10 @@ $ALLOWEDREQUESTS=array(
        "serverinfo", //info request
        "startsession","sessionauth","closesession", //session requests
        //all requests below here need authentication
-       "getmyroles", //role management
+       "getmyroles", //role management: get my own ACLs
        //all requests below here need a role entry in the DB
+       "getusers","setuserdescription","getuseracl","setuseracl","getuserhosts","setuserhosts","adduser","deleteuser",//user management
+       "gethosts","sethost","addhost","deletehost", //host management
        "geteventlist", "geteventdata", "seteventdata", //event infos
        "getroomdata","setroomdata",//room infos
        "getcustomerlist" //customer info
@@ -32,6 +34,12 @@ if(isset($HTTP_RAW_POST_DATA)){
        $REQUESTDATA=$HTTP_RAW_POST_DATA;
 }
 
+/**provided to encode data into UTF-8 for transport*/
+function xmlentities($str)
+{
+       return htmlentities($str);
+}
+
 //initialize basics
 include("inc/loader.php");
 
@@ -137,6 +145,39 @@ if($SMOKEREQUEST=="setroomdata"){
        exit();
 }
 
+//get all users
+if($SMOKEREQUEST=="getusers"){
+       getAllUsersXml();
+       exit();
+}
+
+if($SMOKEREQUEST=="setuserdescription"){
+       setUserDescrXml($REQUESTDATA);
+       exit();
+}
+
+//get ACL info of specific users
+if($SMOKEREQUEST=="getuseracl"){
+       getUserAclXml($REQUESTDATA);
+       exit();
+}
+
+if($SMOKEREQUEST=="setuseracl"){}
+if($SMOKEREQUEST=="getuserhosts"){}
+if($SMOKEREQUEST=="setuserhosts"){}
+
+if($SMOKEREQUEST=="adduser"){
+       addUserXml($REQUESTDATA);
+       exit();
+}
+
+if($SMOKEREQUEST=="deleteuser"){}
+if($SMOKEREQUEST=="gethosts"){}
+if($SMOKEREQUEST=="sethost"){}
+if($SMOKEREQUEST=="addhost"){}
+if($SMOKEREQUEST=="deletehost"){}
+
+
 //EOF
 header("X-MagicSmoke-Status: Error");
 die("Internal Error: unknown command, hiccup in code structure.");