PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/couchAdmin.php

https://github.com/rwaldron/PHP-on-Couch
PHP | 534 lines | 353 code | 28 blank | 153 comment | 44 complexity | 28134ae3770b9d1648fb3d614a50b9e5 MD5 | raw file
  1. <?PHP
  2. /**
  3. * Special class to handle administration tasks
  4. * - create administrators
  5. * - create users
  6. * - create roles
  7. * - assign roles to users
  8. *
  9. *
  10. *
  11. */
  12. class couchAdmin {
  13. /**
  14. * @var reference to our CouchDB client
  15. */
  16. private $client = null;
  17. /**
  18. * @var the name of the CouchDB server "users" database
  19. */
  20. private $userdb = "_users";
  21. /**
  22. *constructor
  23. *
  24. * @param couchClient $client the couchClient instance
  25. */
  26. public function __construct ( couchClient $client ) {
  27. $this->client = $client;
  28. }
  29. private function build_url ($parts) {
  30. $back = $parts["scheme"]."://";
  31. if ( !empty($parts["user"]) ) {
  32. $back.=$parts["user"];
  33. if ( !empty($parts["pass"]) ) {
  34. $back.=":".$parts["pass"];
  35. }
  36. $back.="@";
  37. }
  38. $back.=$parts["host"];
  39. if ( !empty($parts["port"]) ) {
  40. $back.=":".$parts["port"];
  41. }
  42. $back.="/";
  43. if ( !empty($parts["path"]) ) {
  44. $back.=$parts["path"];
  45. }
  46. return $back;
  47. }
  48. /**
  49. * Creates a new CouchDB server administrator
  50. *
  51. * @param string $login administrator login
  52. * @param string $password administrator password
  53. * @param array $roles add additionnal roles to the new admin
  54. * @return stdClass CouchDB server response
  55. */
  56. public function createAdmin ( $login, $password, $roles = array() ) {
  57. $login = urlencode($login);
  58. $data = (string)$password;
  59. if ( strlen($login) < 1 ) {
  60. throw new InvalidArgumentException("Login can't be empty");
  61. }
  62. if ( strlen($data) < 1 ) {
  63. throw new InvalidArgumentException("Password can't be empty");
  64. }
  65. $url = '/_config/admins/'.urlencode($login);
  66. try {
  67. $raw = $this->client->query(
  68. "PUT",
  69. $url,
  70. array(),
  71. json_encode($data)
  72. );
  73. } catch ( Exception $e ) {
  74. throw $e;
  75. }
  76. $resp = couch::parseRawResponse($raw);
  77. if ( $resp['status_code'] != 200 ) {
  78. throw new couchException($raw);
  79. }
  80. $dsn = $this->client->dsn_part();
  81. $dsn["user"] = $login;
  82. $dsn["pass"] = $password;
  83. $client = new couchClient( $this->build_url($dsn), $this->userdb, $this->client->options() );
  84. $user = new stdClass();
  85. $user->name=$login;
  86. $user->type = "user";
  87. $user->roles = $roles;
  88. $user->_id = "org.couchdb.user:".$login;
  89. return $client->storeDoc($user);
  90. }
  91. /**
  92. * Permanently removes a CouchDB Server administrator
  93. *
  94. *
  95. * @param string $login administrator login
  96. * @return stdClass CouchDB server response
  97. */
  98. // public function deleteAdmin ( $login ) {
  99. // $login = urlencode($login);
  100. // if ( strlen($login) < 1 ) {
  101. // throw new InvalidArgumentException("Login can't be empty");
  102. // }
  103. // $url = '/_config/admins/'.urlencode($login);
  104. // $raw = $this->client->query(
  105. // "DELETE",
  106. // $url
  107. // );
  108. // $resp = couch::parseRawResponse($raw);
  109. // if ( $resp['status_code'] != 200 ) {
  110. // throw new couchException($raw);
  111. // }
  112. // $client = new couchClient( $this->client->dsn() , "_users");
  113. // $doc = $client->getDoc("org.couchdb.user:".$name);
  114. // return $client->deleteDoc($doc);
  115. // }
  116. /**
  117. * create a user
  118. *
  119. * @param string $login user login
  120. * @param string $password user password
  121. * @param array $roles add additionnal roles to the new user
  122. * @return stdClass CouchDB user creation response (the same as a document storage response)
  123. */
  124. public function createUser ($login, $password, $roles = array() ) {
  125. $password = (string)$password;
  126. if ( strlen($login) < 1 ) {
  127. throw new InvalidArgumentException("Login can't be empty");
  128. }
  129. if ( strlen($password) < 1 ) {
  130. throw new InvalidArgumentException("Password can't be empty");
  131. }
  132. $user = new stdClass();
  133. $user->salt = sha1( microtime().mt_rand(1000000,9999999),false);
  134. $user->password_sha = sha1( $password . $user->salt, false);
  135. $user->name=$login;
  136. $user->type = "user";
  137. $user->roles = $roles;
  138. $user->_id = "org.couchdb.user:".$login;
  139. $client = new couchClient( $this->client->dsn() , $this->userdb, $this->client->options());
  140. return $client->storeDoc($user);
  141. }
  142. // public function deleteUser ( $login ) {
  143. // if ( strlen($login) < 1 ) {
  144. // throw new InvalidArgumentException("Login can't be empty");
  145. // }
  146. // $client = new couchClient( $this->client->dsn() , "_users");
  147. // $doc = $client->getDoc("org.couchdb.user:".$login);
  148. // // ugly hack bc REST DELETE query don't work on Couch 1.0.0
  149. // $doc->_deleted = true;
  150. // print_r($doc);
  151. // $url = '/_users/'.urlencode($doc->_id);
  152. // try {
  153. // $raw = $client->query(
  154. // "PUT",
  155. // $url,
  156. // array(),
  157. // json_encode($doc)
  158. // );
  159. // } catch ( Exception $e ) {
  160. // throw $e;
  161. // }
  162. // $resp = couch::parseRawResponse($raw);
  163. // if ( $resp['status_code'] != 200 ) {
  164. // throw new couchException($raw);
  165. // }
  166. // return $resp["body"];
  167. // }
  168. /**
  169. * returns the document of a user
  170. *
  171. * @param string $login login of the user to fetch
  172. * @return stdClass CouchDB document
  173. */
  174. public function getUser ($login) {
  175. if ( strlen($login) < 1 ) {
  176. throw new InvalidArgumentException("Login can't be empty");
  177. }
  178. $client = new couchClient( $this->client->dsn() , $this->userdb, $this->client->options());
  179. return $client->getDoc("org.couchdb.user:".$login);
  180. }
  181. /**
  182. * returns all users
  183. *
  184. * @param boolean $include_docs if set to true, users documents will also be included
  185. * @return array users array : each row is a stdObject with "id", "rev" and optionally "doc" properties
  186. */
  187. public function getAllUsers($include_docs = false) {
  188. $client = new couchClient( $this->client->dsn() , $this->userdb, $this->client->options());
  189. if ( $include_docs ) {
  190. $client->include_docs(true);
  191. }
  192. return $client->startkey("org.couchdb.user:")->endkey("org.couchdb.user;")->getAllDocs()->rows;
  193. }
  194. /**
  195. * Add a role to a user document
  196. *
  197. * @param string|stdClass $user the user login (as a string) or the user document ( fetched by getUser() method )
  198. * @param string $role the role to add in the list of roles the user belongs to
  199. * @return boolean true if the user $user now belongs to the role $role
  200. */
  201. public function addRoleToUser ($user,$role) {
  202. if ( is_string($user) ) {
  203. $user = $this->getUser($user);
  204. } elseif ( !property_exists($user,"_id") || !property_exists($user,"roles") ) {
  205. throw new InvalidArgumentException("user parameter should be the login or a user document");
  206. }
  207. if ( !in_array($role,$user->roles) ) {
  208. $user->roles[] = $role;
  209. $client = clone($this->client);
  210. $client->useDatabase($this->userdb);
  211. $client->storeDoc($user);
  212. }
  213. return true;
  214. }
  215. /**
  216. * Remove a role from a user document
  217. *
  218. * @param string|stdClass $user the user login (as a string) or the user document ( fetched by getUser() method )
  219. * @param string $role the role to remove from the list of roles the user belongs to
  220. * @return boolean true if the user $user don't belong to the role $role anymore
  221. */
  222. public function removeRoleFromUser ($user,$role) {
  223. if ( is_string($user) ) {
  224. $user = $this->getUser($user);
  225. } elseif ( !property_exists($user,"_id") || !property_exists($user,"roles") ) {
  226. throw new InvalidArgumentException("user parameter should be the login or a user document");
  227. }
  228. if ( in_array($role,$user->roles) ) {
  229. $user->roles = $this->rmFromArray($role, $user->roles);
  230. $client = clone($this->client);
  231. $client->useDatabase($this->userdb);
  232. $client->storeDoc($user);
  233. }
  234. return true;
  235. }
  236. /**
  237. * returns the security object of a database
  238. *
  239. * @link http://wiki.apache.org/couchdb/Security_Features_Overview
  240. * @return stdClass security object of the database
  241. */
  242. public function getSecurity ( ) {
  243. $dbname = $this->client->getDatabaseName();
  244. $raw = $this->client->query(
  245. "GET",
  246. "/".$dbname."/_security"
  247. );
  248. $resp = couch::parseRawResponse($raw);
  249. if ( $resp['status_code'] != 200 ) {
  250. throw new couchException($raw);
  251. }
  252. if ( ! property_exists($resp['body'], "admins") ) {
  253. $resp["body"]->admins = new stdClass();
  254. $resp["body"]->admins->names = array();
  255. $resp["body"]->admins->roles = array();
  256. $resp["body"]->readers = new stdClass();
  257. $resp["body"]->readers->names = array();
  258. $resp["body"]->readers->roles = array();
  259. }
  260. return $resp['body'];
  261. }
  262. /**
  263. * set the security object of a database
  264. *
  265. * @link http://wiki.apache.org/couchdb/Security_Features_Overview
  266. * @param stdClass $security the security object to apply to the database
  267. * @return stdClass CouchDB server response ( { "ok": true } )
  268. */
  269. public function setSecurity ( $security ) {
  270. if ( !is_object($security) ) {
  271. throw new InvalidArgumentException("Security should be an object");
  272. }
  273. $dbname = $this->client->getDatabaseName();
  274. $raw = $this->client->query(
  275. "PUT",
  276. "/".$dbname."/_security",
  277. array(),
  278. json_encode($security)
  279. );
  280. $resp = couch::parseRawResponse($raw);
  281. if ( $resp['status_code'] == 200 ) {
  282. return $resp['body'];
  283. }
  284. throw new couchException($raw);
  285. }
  286. /**
  287. * add a user to the list of readers for the current database
  288. *
  289. * @param string $login user login
  290. * @return boolean true if the user has successfuly been added
  291. */
  292. public function addDatabaseReaderUser($login) {
  293. if ( strlen($login) < 1 ) {
  294. throw new InvalidArgumentException("Login can't be empty");
  295. }
  296. $sec = $this->getSecurity();
  297. if ( in_array($login, $sec->readers->names) ) {
  298. return true;
  299. }
  300. array_push($sec->readers->names,$login);
  301. $back = $this->setSecurity ( $sec );
  302. if ( is_object($back) && property_exists($back,"ok") && $back->ok == true ) {
  303. return true;
  304. }
  305. return false;
  306. }
  307. /**
  308. * add a user to the list of admins for the current database
  309. *
  310. * @param string $login user login
  311. * @return boolean true if the user has successfuly been added
  312. */
  313. public function addDatabaseAdminUser($login) {
  314. if ( strlen($login) < 1 ) {
  315. throw new InvalidArgumentException("Login can't be empty");
  316. }
  317. $sec = $this->getSecurity();
  318. if ( in_array($login, $sec->admins->names) ) {
  319. return true;
  320. }
  321. array_push($sec->admins->names,$login);
  322. $back = $this->setSecurity ( $sec );
  323. if ( is_object($back) && property_exists($back,"ok") && $back->ok == true ) {
  324. return true;
  325. }
  326. return false;
  327. }
  328. /**
  329. * get the list of admins for the current database
  330. *
  331. * @return array database admins logins
  332. */
  333. public function getDatabaseAdminUsers() {
  334. $sec = $this->getSecurity();
  335. return $sec->admins->names;
  336. }
  337. /**
  338. * get the list of readers for the current database
  339. *
  340. * @return array database readers logins
  341. */
  342. public function getDatabaseReaderUsers() {
  343. $sec = $this->getSecurity();
  344. return $sec->readers->names;
  345. }
  346. /**
  347. * remove a user from the list of readers for the current database
  348. *
  349. * @param string $login user login
  350. * @return boolean true if the user has successfuly been removed
  351. */
  352. public function removeDatabaseReaderUser($login) {
  353. if ( strlen($login) < 1 ) {
  354. throw new InvalidArgumentException("Login can't be empty");
  355. }
  356. $sec = $this->getSecurity();
  357. if ( !in_array($login, $sec->readers->names) ) {
  358. return true;
  359. }
  360. $sec->readers->names = $this->rmFromArray($login, $sec->readers->names);
  361. $back = $this->setSecurity ( $sec );
  362. if ( is_object($back) && property_exists($back,"ok") && $back->ok == true ) {
  363. return true;
  364. }
  365. return false;
  366. }
  367. /**
  368. * remove a user from the list of admins for the current database
  369. *
  370. * @param string $login user login
  371. * @return boolean true if the user has successfuly been removed
  372. */
  373. public function removeDatabaseAdminUser($login) {
  374. if ( strlen($login) < 1 ) {
  375. throw new InvalidArgumentException("Login can't be empty");
  376. }
  377. $sec = $this->getSecurity();
  378. if ( !in_array($login, $sec->admins->names) ) {
  379. return true;
  380. }
  381. $sec->admins->names = $this->rmFromArray($login, $sec->admins->names);
  382. $back = $this->setSecurity ( $sec );
  383. if ( is_object($back) && property_exists($back,"ok") && $back->ok == true ) {
  384. return true;
  385. }
  386. return false;
  387. }
  388. /// roles
  389. /**
  390. * add a role to the list of readers for the current database
  391. *
  392. * @param string $role role name
  393. * @return boolean true if the role has successfuly been added
  394. */
  395. public function addDatabaseReaderRole($role) {
  396. if ( strlen($role) < 1 ) {
  397. throw new InvalidArgumentException("Role can't be empty");
  398. }
  399. $sec = $this->getSecurity();
  400. if ( in_array($role, $sec->readers->roles) ) {
  401. return true;
  402. }
  403. array_push($sec->readers->roles,$role);
  404. $back = $this->setSecurity ( $sec );
  405. if ( is_object($back) && property_exists($back,"ok") && $back->ok == true ) {
  406. return true;
  407. }
  408. return false;
  409. }
  410. /**
  411. * add a role to the list of admins for the current database
  412. *
  413. * @param string $role role name
  414. * @return boolean true if the role has successfuly been added
  415. */
  416. public function addDatabaseAdminRole($role) {
  417. if ( strlen($role) < 1 ) {
  418. throw new InvalidArgumentException("Role can't be empty");
  419. }
  420. $sec = $this->getSecurity();
  421. if ( in_array($role, $sec->admins->roles) ) {
  422. return true;
  423. }
  424. array_push($sec->admins->roles,$role);
  425. $back = $this->setSecurity ( $sec );
  426. if ( is_object($back) && property_exists($back,"ok") && $back->ok == true ) {
  427. return true;
  428. }
  429. return false;
  430. }
  431. /**
  432. * get the list of admin roles for the current database
  433. *
  434. * @return array database admins roles
  435. */
  436. public function getDatabaseAdminRoles() {
  437. $sec = $this->getSecurity();
  438. return $sec->admins->roles;
  439. }
  440. /**
  441. * get the list of reader roles for the current database
  442. *
  443. * @return array database readers roles
  444. */
  445. public function getDatabaseReaderRoles() {
  446. $sec = $this->getSecurity();
  447. return $sec->readers->roles;
  448. }
  449. /**
  450. * remove a role from the list of readers for the current database
  451. *
  452. * @param string $role role name
  453. * @return boolean true if the role has successfuly been removed
  454. */
  455. public function removeDatabaseReaderRole($role) {
  456. if ( strlen($role) < 1 ) {
  457. throw new InvalidArgumentException("Role can't be empty");
  458. }
  459. $sec = $this->getSecurity();
  460. if ( !in_array($role, $sec->readers->roles) ) {
  461. return true;
  462. }
  463. $sec->readers->roles = $this->rmFromArray($role, $sec->readers->roles);
  464. $back = $this->setSecurity ( $sec );
  465. if ( is_object($back) && property_exists($back,"ok") && $back->ok == true ) {
  466. return true;
  467. }
  468. return false;
  469. }
  470. /**
  471. * remove a role from the list of admins for the current database
  472. *
  473. * @param string $role role name
  474. * @return boolean true if the role has successfuly been removed
  475. */
  476. public function removeDatabaseAdminRole($role) {
  477. if ( strlen($role) < 1 ) {
  478. throw new InvalidArgumentException("Role can't be empty");
  479. }
  480. $sec = $this->getSecurity();
  481. if ( !in_array($role, $sec->admins->roles) ) {
  482. return true;
  483. }
  484. $sec->admins->roles = $this->rmFromArray($role, $sec->admins->roles);
  485. $back = $this->setSecurity ( $sec );
  486. if ( is_object($back) && property_exists($back,"ok") && $back->ok == true ) {
  487. return true;
  488. }
  489. return false;
  490. }
  491. /// /roles
  492. private function rmFromArray($needle, $haystack) {
  493. $back = array();
  494. foreach ( $haystack as $one ) {
  495. if ( $one != $needle ) { $back[] = $one; }
  496. }
  497. return $back;
  498. }
  499. }