1
0

test_mysql.py 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, print_function, unicode_literals
  3. import logging
  4. import pytest
  5. import salt.utils.path
  6. from salt.ext import six
  7. from salt.ext.six.moves import range
  8. from salt.modules import mysql as mysqlmod
  9. from tests.support.case import ModuleCase
  10. from tests.support.helpers import destructiveTest
  11. from tests.support.mixins import SaltReturnAssertsMixin
  12. from tests.support.unit import skipIf
  13. log = logging.getLogger(__name__)
  14. NO_MYSQL = False
  15. try:
  16. import MySQLdb # pylint: disable=import-error,unused-import
  17. except Exception: # pylint: disable=broad-except
  18. NO_MYSQL = True
  19. if not salt.utils.path.which("mysqladmin"):
  20. NO_MYSQL = True
  21. @skipIf(
  22. NO_MYSQL,
  23. "Please install MySQL bindings and a MySQL Server before running"
  24. "MySQL integration tests.",
  25. )
  26. @pytest.mark.windows_whitelisted
  27. class MysqlModuleDbTest(ModuleCase, SaltReturnAssertsMixin):
  28. """
  29. Module testing database creation on a real MySQL Server.
  30. """
  31. user = "root"
  32. password = "poney"
  33. @destructiveTest
  34. def setUp(self):
  35. """
  36. Test presence of MySQL server, enforce a root password
  37. """
  38. super(MysqlModuleDbTest, self).setUp()
  39. NO_MYSQL_SERVER = True
  40. # now ensure we know the mysql root password
  41. # one of theses two at least should work
  42. ret1 = self.run_state(
  43. "cmd.run",
  44. name='mysqladmin --host="localhost" -u '
  45. + self.user
  46. + ' flush-privileges password "'
  47. + self.password
  48. + '"',
  49. )
  50. ret2 = self.run_state(
  51. "cmd.run",
  52. name='mysqladmin --host="localhost" -u '
  53. + self.user
  54. + ' --password="'
  55. + self.password
  56. + '" flush-privileges password "'
  57. + self.password
  58. + '"',
  59. )
  60. key, value = ret2.popitem()
  61. if value["result"]:
  62. NO_MYSQL_SERVER = False
  63. else:
  64. self.skipTest("No MySQL Server running, or no root access on it.")
  65. def _db_creation_loop(self, db_name, returning_name, test_conn=False, **kwargs):
  66. """
  67. Used in db testCase, create, check exists, check in list and removes.
  68. """
  69. ret = self.run_function("mysql.db_create", name=db_name, **kwargs)
  70. self.assertEqual(
  71. True, ret, "Problem while creating db for db name: '{0}'".format(db_name)
  72. )
  73. # test db exists
  74. ret = self.run_function("mysql.db_exists", name=db_name, **kwargs)
  75. self.assertEqual(
  76. True,
  77. ret,
  78. "Problem while testing db exists for db name: '{0}'".format(db_name),
  79. )
  80. # List db names to ensure db is created with the right utf8 string
  81. ret = self.run_function("mysql.db_list", **kwargs)
  82. if not isinstance(ret, list):
  83. raise AssertionError(
  84. (
  85. "Unexpected query result while retrieving databases list"
  86. " '{0}' for '{1}' test"
  87. ).format(ret, db_name)
  88. )
  89. self.assertIn(
  90. returning_name,
  91. ret,
  92. (
  93. "Problem while testing presence of db name in db lists"
  94. " for db name: '{0}' in list '{1}'"
  95. ).format(db_name, ret),
  96. )
  97. if test_conn:
  98. # test connections on database with root user
  99. ret = self.run_function(
  100. "mysql.query", database=db_name, query="SELECT 1", **kwargs
  101. )
  102. if not isinstance(ret, dict) or "results" not in ret:
  103. raise AssertionError(
  104. (
  105. "Unexpected result while testing connection"
  106. " on database : {0}"
  107. ).format(repr(db_name))
  108. )
  109. self.assertEqual([["1"]], ret["results"])
  110. # Now remove database
  111. ret = self.run_function("mysql.db_remove", name=db_name, **kwargs)
  112. self.assertEqual(
  113. True, ret, "Problem while removing db for db name: '{0}'".format(db_name)
  114. )
  115. @destructiveTest
  116. def test_database_creation_level1(self):
  117. """
  118. Create database, test presence, then drop db. All theses with complex names.
  119. """
  120. # name with space
  121. db_name = "foo 1"
  122. self._db_creation_loop(
  123. db_name=db_name,
  124. returning_name=db_name,
  125. test_conn=True,
  126. connection_user=self.user,
  127. connection_pass=self.password,
  128. )
  129. # ```````
  130. # create
  131. # also with character_set and collate only
  132. ret = self.run_function(
  133. "mysql.db_create",
  134. name="foo`2",
  135. character_set="utf8",
  136. collate="utf8_general_ci",
  137. connection_user=self.user,
  138. connection_pass=self.password,
  139. )
  140. self.assertEqual(True, ret)
  141. # test db exists
  142. ret = self.run_function(
  143. "mysql.db_exists",
  144. name="foo`2",
  145. connection_user=self.user,
  146. connection_pass=self.password,
  147. )
  148. self.assertEqual(True, ret)
  149. # redoing the same should fail
  150. # even with other character sets or collations
  151. ret = self.run_function(
  152. "mysql.db_create",
  153. name="foo`2",
  154. character_set="utf8",
  155. collate="utf8_general_ci",
  156. connection_user=self.user,
  157. connection_pass=self.password,
  158. )
  159. self.assertEqual(False, ret)
  160. # redoing the same should fail
  161. ret = self.run_function(
  162. "mysql.db_create",
  163. name="foo`2",
  164. character_set="utf8",
  165. collate="utf8_general_ci",
  166. connection_user=self.user,
  167. connection_pass=self.password,
  168. )
  169. self.assertEqual(False, ret)
  170. # Now remove database
  171. ret = self.run_function(
  172. "mysql.db_remove",
  173. name="foo`2",
  174. connection_user=self.user,
  175. connection_pass=self.password,
  176. )
  177. self.assertEqual(True, ret)
  178. # '''''''
  179. # create
  180. # also with character_set only
  181. db_name = "foo'3"
  182. self._db_creation_loop(
  183. db_name=db_name,
  184. returning_name=db_name,
  185. test_conn=True,
  186. character_set="utf8",
  187. connection_user=self.user,
  188. connection_pass=self.password,
  189. )
  190. # """"""""
  191. # also with collate only
  192. db_name = 'foo"4'
  193. self._db_creation_loop(
  194. db_name=db_name,
  195. returning_name=db_name,
  196. test_conn=True,
  197. collate="utf8_general_ci",
  198. connection_user=self.user,
  199. connection_pass=self.password,
  200. )
  201. # fuzzy
  202. db_name = '<foo` --"5>'
  203. self._db_creation_loop(
  204. db_name=db_name,
  205. returning_name=db_name,
  206. test_conn=True,
  207. connection_user=self.user,
  208. connection_pass=self.password,
  209. )
  210. @destructiveTest
  211. def test_mysql_dbname_character_percent(self):
  212. """
  213. Play with the '%' character problems
  214. This character should be escaped in the form '%%' on queries, but only
  215. when theses queries have arguments. It is also a special character
  216. in LIKE SQL queries. Finally it is used to indicate query arguments.
  217. """
  218. db_name1 = "foo%1_"
  219. db_name2 = "foo%12"
  220. ret = self.run_function(
  221. "mysql.db_create",
  222. name=db_name1,
  223. character_set="utf8",
  224. collate="utf8_general_ci",
  225. connection_user=self.user,
  226. connection_pass=self.password,
  227. )
  228. self.assertEqual(True, ret)
  229. ret = self.run_function(
  230. "mysql.db_create",
  231. name=db_name2,
  232. connection_user=self.user,
  233. connection_pass=self.password,
  234. )
  235. self.assertEqual(True, ret)
  236. ret = self.run_function(
  237. "mysql.db_remove",
  238. name=db_name1,
  239. connection_user=self.user,
  240. connection_pass=self.password,
  241. )
  242. self.assertEqual(True, ret)
  243. ret = self.run_function(
  244. "mysql.db_exists",
  245. name=db_name1,
  246. connection_user=self.user,
  247. connection_pass=self.password,
  248. )
  249. self.assertEqual(False, ret)
  250. ret = self.run_function(
  251. "mysql.db_exists",
  252. name=db_name2,
  253. connection_user=self.user,
  254. connection_pass=self.password,
  255. )
  256. self.assertEqual(True, ret)
  257. ret = self.run_function(
  258. "mysql.db_remove",
  259. name=db_name2,
  260. connection_user=self.user,
  261. connection_pass=self.password,
  262. )
  263. self.assertEqual(True, ret)
  264. @destructiveTest
  265. def test_database_creation_utf8(self):
  266. """
  267. Test support of utf8 in database names
  268. """
  269. # Simple accents : using utf8 string
  270. db_name_unicode = "notam\xe9rican"
  271. # same as 'notamérican' because of file encoding
  272. # but ensure it on this test
  273. db_name_utf8 = "notam\xc3\xa9rican"
  274. # FIXME: MySQLdb problems on conn strings containing
  275. # utf-8 on user name of db name prevent conn test
  276. self._db_creation_loop(
  277. db_name=db_name_utf8,
  278. returning_name=db_name_utf8,
  279. test_conn=False,
  280. connection_user=self.user,
  281. connection_pass=self.password,
  282. connection_charset="utf8",
  283. saltenv={"LC_ALL": "en_US.utf8"},
  284. )
  285. # test unicode entry will also return utf8 name
  286. self._db_creation_loop(
  287. db_name=db_name_unicode,
  288. returning_name=db_name_utf8,
  289. test_conn=False,
  290. connection_user=self.user,
  291. connection_pass=self.password,
  292. connection_charset="utf8",
  293. saltenv={"LC_ALL": "en_US.utf8"},
  294. )
  295. # Using more complex unicode characters:
  296. db_name_unicode = "\u6a19\u6e96\u8a9e"
  297. # same as '標準語' because of file encoding
  298. # but ensure it on this test
  299. db_name_utf8 = "\xe6\xa8\x99\xe6\xba\x96\xe8\xaa\x9e"
  300. self._db_creation_loop(
  301. db_name=db_name_utf8,
  302. returning_name=db_name_utf8,
  303. test_conn=False,
  304. connection_user=self.user,
  305. connection_pass=self.password,
  306. connection_charset="utf8",
  307. saltenv={"LC_ALL": "en_US.utf8"},
  308. )
  309. # test unicode entry will also return utf8 name
  310. self._db_creation_loop(
  311. db_name=db_name_unicode,
  312. returning_name=db_name_utf8,
  313. test_conn=False,
  314. connection_user=self.user,
  315. connection_pass=self.password,
  316. connection_charset="utf8",
  317. saltenv={"LC_ALL": "en_US.utf8"},
  318. )
  319. @destructiveTest
  320. def test_database_maintenance(self):
  321. """
  322. Test maintenance operations on a created database
  323. """
  324. dbname = "foo%'-- `\"'"
  325. # create database
  326. # but first silently try to remove it
  327. # in case of previous tests failures
  328. ret = self.run_function(
  329. "mysql.db_remove",
  330. name=dbname,
  331. connection_user=self.user,
  332. connection_pass=self.password,
  333. )
  334. ret = self.run_function(
  335. "mysql.db_create",
  336. name=dbname,
  337. character_set="utf8",
  338. collate="utf8_general_ci",
  339. connection_user=self.user,
  340. connection_pass=self.password,
  341. )
  342. self.assertEqual(True, ret)
  343. # test db exists
  344. ret = self.run_function(
  345. "mysql.db_exists",
  346. name=dbname,
  347. connection_user=self.user,
  348. connection_pass=self.password,
  349. )
  350. self.assertEqual(True, ret)
  351. # Create 3 tables
  352. tablenames = {
  353. 'A%table "`1': "MYISAM",
  354. "B%table '`2": "InnoDB",
  355. "Ctable --`3": "MEMORY",
  356. }
  357. for tablename, engine in sorted(six.iteritems(tablenames)):
  358. # prepare queries
  359. create_query = (
  360. "CREATE TABLE {tblname} ("
  361. " id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,"
  362. " data VARCHAR(100)) ENGINE={engine};".format(
  363. tblname=mysqlmod.quote_identifier(tablename), engine=engine,
  364. )
  365. )
  366. insert_query = "INSERT INTO {tblname} (data)" " VALUES ".format(
  367. tblname=mysqlmod.quote_identifier(tablename)
  368. )
  369. delete_query = "DELETE from {tblname}" " order by rand() limit 50;".format(
  370. tblname=mysqlmod.quote_identifier(tablename)
  371. )
  372. for x in range(100):
  373. insert_query += "('foo" + six.text_type(x) + "'),"
  374. insert_query += "('bar');"
  375. # populate database
  376. log.info("Adding table '%s'", tablename)
  377. ret = self.run_function(
  378. "mysql.query",
  379. database=dbname,
  380. query=create_query,
  381. connection_user=self.user,
  382. connection_pass=self.password,
  383. )
  384. if not isinstance(ret, dict) or "rows affected" not in ret:
  385. raise AssertionError(
  386. (
  387. "Unexpected query result while populating test table"
  388. " '{0}' : '{1}'"
  389. ).format(
  390. tablename, ret,
  391. )
  392. )
  393. self.assertEqual(ret["rows affected"], 0)
  394. log.info("Populating table '%s'", tablename)
  395. ret = self.run_function(
  396. "mysql.query",
  397. database=dbname,
  398. query=insert_query,
  399. connection_user=self.user,
  400. connection_pass=self.password,
  401. )
  402. if not isinstance(ret, dict) or "rows affected" not in ret:
  403. raise AssertionError(
  404. (
  405. "Unexpected query result while populating test table"
  406. " '{0}' : '{1}'"
  407. ).format(
  408. tablename, ret,
  409. )
  410. )
  411. self.assertEqual(ret["rows affected"], 101)
  412. log.info("Removing some rows on table'%s'", tablename)
  413. ret = self.run_function(
  414. "mysql.query",
  415. database=dbname,
  416. query=delete_query,
  417. connection_user=self.user,
  418. connection_pass=self.password,
  419. )
  420. if not isinstance(ret, dict) or "rows affected" not in ret:
  421. raise AssertionError(
  422. (
  423. "Unexpected query result while removing rows on test table"
  424. " '{0}' : '{1}'"
  425. ).format(
  426. tablename, ret,
  427. )
  428. )
  429. self.assertEqual(ret["rows affected"], 50)
  430. # test check/repair/opimize on 1 table
  431. tablename = 'A%table "`1'
  432. ret = self.run_function(
  433. "mysql.db_check",
  434. name=dbname,
  435. table=tablename,
  436. connection_user=self.user,
  437. connection_pass=self.password,
  438. )
  439. # Note that returned result does not quote_identifier of table and db
  440. self.assertEqual(
  441. ret,
  442. [
  443. {
  444. "Table": dbname + "." + tablename,
  445. "Msg_text": "OK",
  446. "Msg_type": "status",
  447. "Op": "check",
  448. }
  449. ],
  450. )
  451. ret = self.run_function(
  452. "mysql.db_repair",
  453. name=dbname,
  454. table=tablename,
  455. connection_user=self.user,
  456. connection_pass=self.password,
  457. )
  458. # Note that returned result does not quote_identifier of table and db
  459. self.assertEqual(
  460. ret,
  461. [
  462. {
  463. "Table": dbname + "." + tablename,
  464. "Msg_text": "OK",
  465. "Msg_type": "status",
  466. "Op": "repair",
  467. }
  468. ],
  469. )
  470. ret = self.run_function(
  471. "mysql.db_optimize",
  472. name=dbname,
  473. table=tablename,
  474. connection_user=self.user,
  475. connection_pass=self.password,
  476. )
  477. # Note that returned result does not quote_identifier of table and db
  478. self.assertEqual(
  479. ret,
  480. [
  481. {
  482. "Table": dbname + "." + tablename,
  483. "Msg_text": "OK",
  484. "Msg_type": "status",
  485. "Op": "optimize",
  486. }
  487. ],
  488. )
  489. # test check/repair/opimize on all tables
  490. ret = self.run_function(
  491. "mysql.db_check",
  492. name=dbname,
  493. connection_user=self.user,
  494. connection_pass=self.password,
  495. )
  496. expected = []
  497. for tablename, engine in sorted(six.iteritems(tablenames)):
  498. if engine == "MEMORY":
  499. expected.append(
  500. [
  501. {
  502. "Table": dbname + "." + tablename,
  503. "Msg_text": (
  504. "The storage engine for the table doesn't"
  505. " support check"
  506. ),
  507. "Msg_type": "note",
  508. "Op": "check",
  509. }
  510. ]
  511. )
  512. else:
  513. expected.append(
  514. [
  515. {
  516. "Table": dbname + "." + tablename,
  517. "Msg_text": "OK",
  518. "Msg_type": "status",
  519. "Op": "check",
  520. }
  521. ]
  522. )
  523. self.assertEqual(ret, expected)
  524. ret = self.run_function(
  525. "mysql.db_repair",
  526. name=dbname,
  527. connection_user=self.user,
  528. connection_pass=self.password,
  529. )
  530. expected = []
  531. for tablename, engine in sorted(six.iteritems(tablenames)):
  532. if engine == "MYISAM":
  533. expected.append(
  534. [
  535. {
  536. "Table": dbname + "." + tablename,
  537. "Msg_text": "OK",
  538. "Msg_type": "status",
  539. "Op": "repair",
  540. }
  541. ]
  542. )
  543. else:
  544. expected.append(
  545. [
  546. {
  547. "Table": dbname + "." + tablename,
  548. "Msg_text": (
  549. "The storage engine for the table doesn't"
  550. " support repair"
  551. ),
  552. "Msg_type": "note",
  553. "Op": "repair",
  554. }
  555. ]
  556. )
  557. self.assertEqual(ret, expected)
  558. ret = self.run_function(
  559. "mysql.db_optimize",
  560. name=dbname,
  561. connection_user=self.user,
  562. connection_pass=self.password,
  563. )
  564. expected = []
  565. for tablename, engine in sorted(six.iteritems(tablenames)):
  566. if engine == "MYISAM":
  567. expected.append(
  568. [
  569. {
  570. "Table": dbname + "." + tablename,
  571. "Msg_text": "OK",
  572. "Msg_type": "status",
  573. "Op": "optimize",
  574. }
  575. ]
  576. )
  577. elif engine == "InnoDB":
  578. expected.append(
  579. [
  580. {
  581. "Table": dbname + "." + tablename,
  582. "Msg_text": (
  583. "Table does not support optimize, "
  584. "doing recreate + analyze instead"
  585. ),
  586. "Msg_type": "note",
  587. "Op": "optimize",
  588. },
  589. {
  590. "Table": dbname + "." + tablename,
  591. "Msg_text": "OK",
  592. "Msg_type": "status",
  593. "Op": "optimize",
  594. },
  595. ]
  596. )
  597. elif engine == "MEMORY":
  598. expected.append(
  599. [
  600. {
  601. "Table": dbname + "." + tablename,
  602. "Msg_text": (
  603. "The storage engine for the table doesn't"
  604. " support optimize"
  605. ),
  606. "Msg_type": "note",
  607. "Op": "optimize",
  608. }
  609. ]
  610. )
  611. self.assertEqual(ret, expected)
  612. # Teardown, remove database
  613. ret = self.run_function(
  614. "mysql.db_remove",
  615. name=dbname,
  616. connection_user=self.user,
  617. connection_pass=self.password,
  618. )
  619. self.assertEqual(True, ret)
  620. @skipIf(
  621. NO_MYSQL,
  622. "Please install MySQL bindings and a MySQL Server before running"
  623. "MySQL integration tests.",
  624. )
  625. @pytest.mark.windows_whitelisted
  626. class MysqlModuleUserTest(ModuleCase, SaltReturnAssertsMixin):
  627. """
  628. User Creation and connection tests
  629. """
  630. user = "root"
  631. password = "poney"
  632. @destructiveTest
  633. def setUp(self):
  634. """
  635. Test presence of MySQL server, enforce a root password
  636. """
  637. super(MysqlModuleUserTest, self).setUp()
  638. NO_MYSQL_SERVER = True
  639. # now ensure we know the mysql root password
  640. # one of theses two at least should work
  641. ret1 = self.run_state(
  642. "cmd.run",
  643. name='mysqladmin --host="localhost" -u '
  644. + self.user
  645. + ' flush-privileges password "'
  646. + self.password
  647. + '"',
  648. )
  649. ret2 = self.run_state(
  650. "cmd.run",
  651. name='mysqladmin --host="localhost" -u '
  652. + self.user
  653. + ' --password="'
  654. + self.password
  655. + '" flush-privileges password "'
  656. + self.password
  657. + '"',
  658. )
  659. key, value = ret2.popitem()
  660. if value["result"]:
  661. NO_MYSQL_SERVER = False
  662. else:
  663. self.skipTest("No MySQL Server running, or no root access on it.")
  664. def _userCreationLoop(
  665. self,
  666. uname,
  667. host,
  668. password=None,
  669. new_password=None,
  670. new_password_hash=None,
  671. **kwargs
  672. ):
  673. """
  674. Perform some tests around creation of the given user
  675. """
  676. # First silently remove it, in case of
  677. ret = self.run_function("mysql.user_remove", user=uname, host=host, **kwargs)
  678. # creation
  679. ret = self.run_function(
  680. "mysql.user_create", user=uname, host=host, password=password, **kwargs
  681. )
  682. self.assertEqual(
  683. True,
  684. ret,
  685. ("Calling user_create on" " user '{0}' did not return True: {1}").format(
  686. uname, repr(ret)
  687. ),
  688. )
  689. # double creation failure
  690. ret = self.run_function(
  691. "mysql.user_create", user=uname, host=host, password=password, **kwargs
  692. )
  693. self.assertEqual(
  694. False,
  695. ret,
  696. (
  697. "Calling user_create a second time on"
  698. " user '{0}' did not return False: {1}"
  699. ).format(uname, repr(ret)),
  700. )
  701. # Alter password
  702. if new_password is not None or new_password_hash is not None:
  703. ret = self.run_function(
  704. "mysql.user_chpass",
  705. user=uname,
  706. host=host,
  707. password=new_password,
  708. password_hash=new_password_hash,
  709. connection_user=self.user,
  710. connection_pass=self.password,
  711. connection_charset="utf8",
  712. saltenv={"LC_ALL": "en_US.utf8"},
  713. )
  714. self.assertEqual(
  715. True,
  716. ret,
  717. (
  718. "Calling user_chpass on" " user '{0}' did not return True: {1}"
  719. ).format(uname, repr(ret)),
  720. )
  721. def _chck_userinfo(self, user, host, check_user, check_hash):
  722. """
  723. Internal routine to check user_info returned results
  724. """
  725. ret = self.run_function(
  726. "mysql.user_info",
  727. user=user,
  728. host=host,
  729. connection_user=self.user,
  730. connection_pass=self.password,
  731. connection_charset="utf8",
  732. saltenv={"LC_ALL": "en_US.utf8"},
  733. )
  734. if not isinstance(ret, dict):
  735. raise AssertionError(
  736. "Unexpected result while retrieving user_info for " "'{0}'".format(user)
  737. )
  738. self.assertEqual(ret["Host"], host)
  739. self.assertEqual(ret["Password"], check_hash)
  740. self.assertEqual(ret["User"], check_user)
  741. def _chk_remove_user(self, user, host, **kwargs):
  742. """
  743. Internal routine to check user_remove
  744. """
  745. ret = self.run_function("mysql.user_remove", user=user, host=host, **kwargs)
  746. self.assertEqual(
  747. True,
  748. ret,
  749. (
  750. "Assertion failed while removing user" " '{0}' on host '{1}': {2}"
  751. ).format(user, host, repr(ret)),
  752. )
  753. @destructiveTest
  754. def test_user_management(self):
  755. """
  756. Test various users creation settings
  757. """
  758. # Create users with rights on this database
  759. # and rights on other databases
  760. user1 = "user '1"
  761. user1_pwd = "pwd`'\"1b"
  762. user1_pwd_hash = "*4DF33B3B12E43384677050A818327877FAB2F4BA"
  763. # this is : user "2'標
  764. user2 = "user \"2'\xe6\xa8\x99"
  765. user2_pwd = "user \"2'\xe6\xa8\x99b"
  766. user2_pwd_hash = "*3A38A7B94B024B983687BB9B44FB60B7AA38FE61"
  767. user3 = 'user "3;,?:@=&/'
  768. user3_pwd = 'user "3;,?:@=&/'
  769. user3_pwd_hash = "*AA3B1D4105A45D381C23A5C221C47EA349E1FD7D"
  770. # this is : user ":=;4標 in unicode instead of utf-8
  771. # if unicode char is counted as 1 char we hit the max user
  772. # size (16)
  773. user4 = 'user":;,?:@=&/4\u6a19'
  774. user4_utf8 = 'user":;,?:@=&/4\xe6\xa8\x99'
  775. user4_pwd = 'user "4;,?:@=&/'
  776. user4_pwd_hash = "*FC8EF8DBF27628E4E113359F8E7478D5CF3DD57C"
  777. user5 = 'user ``"5'
  778. user5_utf8 = 'user ``"5'
  779. # this is 標標標\
  780. user5_pwd = "\xe6\xa8\x99\xe6\xa8\x99\\"
  781. # this is password('標標\\')
  782. user5_pwd_hash = "*3752E65CDD8751AF8D889C62CFFC6C998B12C376"
  783. user6 = 'user %--"6'
  784. user6_utf8 = 'user %--"6'
  785. # this is : --'"% SIX標b
  786. user6_pwd_u = " --'\"% SIX\u6a19b"
  787. user6_pwd_utf8 = " --'\"% SIX\xe6\xa8\x99b"
  788. # this is password(' --\'"% SIX標b')
  789. user6_pwd_hash = "*90AE800593E2D407CD9E28CCAFBE42D17EEA5369"
  790. self._userCreationLoop(
  791. uname=user1,
  792. host="localhost",
  793. password="pwd`'\"1",
  794. new_password="pwd`'\"1b",
  795. connection_user=self.user,
  796. connection_pass=self.password,
  797. )
  798. # Now check for results
  799. ret = self.run_function(
  800. "mysql.user_exists",
  801. user=user1,
  802. host="localhost",
  803. password=user1_pwd,
  804. password_hash=None,
  805. connection_user=self.user,
  806. connection_pass=self.password,
  807. connection_charset="utf8",
  808. saltenv={"LC_ALL": "en_US.utf8"},
  809. )
  810. self.assertEqual(
  811. True,
  812. ret,
  813. ("Testing final user '{0}' on host '{1}'" " existence failed").format(
  814. user1, "localhost"
  815. ),
  816. )
  817. self._userCreationLoop(
  818. uname=user2,
  819. host="localhost",
  820. password=None,
  821. # this is his name hash : user "2'標
  822. password_hash="*EEF6F854748ACF841226BB1C2422BEC70AE7F1FF",
  823. # and this is the same with a 'b' added
  824. new_password_hash=user2_pwd_hash,
  825. connection_user=self.user,
  826. connection_pass=self.password,
  827. connection_charset="utf8",
  828. saltenv={"LC_ALL": "en_US.utf8"},
  829. )
  830. # user2 can connect from other places with other password
  831. self._userCreationLoop(
  832. uname=user2,
  833. host="10.0.0.1",
  834. allow_passwordless=True,
  835. connection_user=self.user,
  836. connection_pass=self.password,
  837. connection_charset="utf8",
  838. saltenv={"LC_ALL": "en_US.utf8"},
  839. )
  840. self._userCreationLoop(
  841. uname=user2,
  842. host="10.0.0.2",
  843. allow_passwordless=True,
  844. unix_socket=True,
  845. connection_user=self.user,
  846. connection_pass=self.password,
  847. connection_charset="utf8",
  848. saltenv={"LC_ALL": "en_US.utf8"},
  849. )
  850. # Now check for results
  851. ret = self.run_function(
  852. "mysql.user_exists",
  853. user=user2,
  854. host="localhost",
  855. password=None,
  856. password_hash=user2_pwd_hash,
  857. connection_user=self.user,
  858. connection_pass=self.password,
  859. connection_charset="utf8",
  860. saltenv={"LC_ALL": "en_US.utf8"},
  861. )
  862. self.assertEqual(
  863. True,
  864. ret,
  865. ("Testing final user '{0}' on host '{1}'" " failed").format(
  866. user2, "localhost"
  867. ),
  868. )
  869. ret = self.run_function(
  870. "mysql.user_exists",
  871. user=user2,
  872. host="10.0.0.1",
  873. allow_passwordless=True,
  874. connection_user=self.user,
  875. connection_pass=self.password,
  876. connection_charset="utf8",
  877. saltenv={"LC_ALL": "en_US.utf8"},
  878. )
  879. self.assertEqual(
  880. True,
  881. ret,
  882. (
  883. "Testing final user '{0}' on host '{1}'" " without password failed"
  884. ).format(user2, "10.0.0.1"),
  885. )
  886. ret = self.run_function(
  887. "mysql.user_exists",
  888. user=user2,
  889. host="10.0.0.2",
  890. allow_passwordless=True,
  891. unix_socket=True,
  892. connection_user=self.user,
  893. connection_pass=self.password,
  894. connection_charset="utf8",
  895. saltenv={"LC_ALL": "en_US.utf8"},
  896. )
  897. self.assertEqual(
  898. True,
  899. ret,
  900. (
  901. "Testing final user '{0}' on host '{1}'" " without password failed"
  902. ).format(user2, "10.0.0.2"),
  903. )
  904. # Empty password is not passwordless (or is it a bug?)
  905. self._userCreationLoop(
  906. uname=user3,
  907. host="localhost",
  908. password="",
  909. connection_user=self.user,
  910. connection_pass=self.password,
  911. )
  912. # user 3 on another host with a password
  913. self._userCreationLoop(
  914. uname=user3,
  915. host="%",
  916. password="foo",
  917. new_password=user3_pwd,
  918. connection_user=self.user,
  919. connection_pass=self.password,
  920. )
  921. # Now check for results
  922. ret = self.run_function(
  923. "mysql.user_exists",
  924. user=user3,
  925. host="localhost",
  926. password="",
  927. connection_user=self.user,
  928. connection_pass=self.password,
  929. )
  930. self.assertEqual(
  931. True,
  932. ret,
  933. (
  934. "Testing final user '{0}' on host '{1}'"
  935. " without empty password failed"
  936. ).format(user3, "localhost"),
  937. )
  938. ret = self.run_function(
  939. "mysql.user_exists",
  940. user=user3,
  941. host="%",
  942. password=user3_pwd,
  943. connection_user=self.user,
  944. connection_pass=self.password,
  945. )
  946. self.assertEqual(
  947. True,
  948. ret,
  949. ("Testing final user '{0}' on host '{1}'" " with password failed").format(
  950. user3, "%"
  951. ),
  952. )
  953. # check unicode name, and password > password_hash
  954. self._userCreationLoop(
  955. uname=user4,
  956. host="%",
  957. password=user4_pwd,
  958. # this is password('foo')
  959. password_hash="*F3A2A51A9B0F2BE2468926B4132313728C250DBF",
  960. connection_user=self.user,
  961. connection_pass=self.password,
  962. connection_charset="utf8",
  963. saltenv={"LC_ALL": "en_US.utf8"},
  964. )
  965. # Now check for results
  966. ret = self.run_function(
  967. "mysql.user_exists",
  968. user=user4_utf8,
  969. host="%",
  970. password=user4_pwd,
  971. connection_user=self.user,
  972. connection_pass=self.password,
  973. connection_charset="utf8",
  974. saltenv={"LC_ALL": "en_US.utf8"},
  975. )
  976. self.assertEqual(
  977. True,
  978. ret,
  979. (
  980. "Testing final user '{0}' on host '{1}'"
  981. " with password take from password and not password_hash"
  982. " failed"
  983. ).format(user4_utf8, "%"),
  984. )
  985. self._userCreationLoop(
  986. uname=user5,
  987. host="localhost",
  988. password="\xe6\xa8\x99\xe6\xa8\x99",
  989. new_password=user5_pwd,
  990. unix_socket=True,
  991. connection_user=self.user,
  992. connection_pass=self.password,
  993. connection_charset="utf8",
  994. saltenv={"LC_ALL": "en_US.utf8"},
  995. )
  996. ret = self.run_function(
  997. "mysql.user_exists",
  998. user=user5_utf8,
  999. host="localhost",
  1000. password=user5_pwd,
  1001. connection_user=self.user,
  1002. connection_pass=self.password,
  1003. connection_charset="utf8",
  1004. saltenv={"LC_ALL": "en_US.utf8"},
  1005. )
  1006. self.assertEqual(
  1007. True,
  1008. ret,
  1009. (
  1010. "Testing final user '{0}' on host '{1}'" " with utf8 password failed"
  1011. ).format(user5_utf8, "localhost"),
  1012. )
  1013. # for this one we give password in unicode and check it in utf-8
  1014. self._userCreationLoop(
  1015. uname=user6,
  1016. host="10.0.0.1",
  1017. password=" foobar",
  1018. new_password=user6_pwd_u,
  1019. connection_user=self.user,
  1020. connection_pass=self.password,
  1021. connection_charset="utf8",
  1022. saltenv={"LC_ALL": "en_US.utf8"},
  1023. )
  1024. # Now check for results
  1025. ret = self.run_function(
  1026. "mysql.user_exists",
  1027. user=user6_utf8,
  1028. host="10.0.0.1",
  1029. password=user6_pwd_utf8,
  1030. connection_user=self.user,
  1031. connection_pass=self.password,
  1032. connection_charset="utf8",
  1033. saltenv={"LC_ALL": "en_US.utf8"},
  1034. )
  1035. self.assertEqual(
  1036. True,
  1037. ret,
  1038. (
  1039. "Testing final user '{0}' on host '{1}'" " with unicode password failed"
  1040. ).format(user6_utf8, "10.0.0.1"),
  1041. )
  1042. # Final result should be:
  1043. # mysql> select Host, User, Password from user where user like 'user%';
  1044. # +--------------------+-----------+-------------------------------+
  1045. # | User | Host | Password |
  1046. # +--------------------+-----------+-------------------------------+
  1047. # | user "2'標 | 10.0.0.1 | |
  1048. # | user "2'標 | 10.0.0.2 | |
  1049. # | user "2'標 | localhost | *3A38A7B94B0(...)60B7AA38FE61 |
  1050. # | user "3;,?:@=&/ | % | *AA3B1D4105(...)47EA349E1FD7D |
  1051. # | user "3;,?:@=&/ | localhost | |
  1052. # | user %--"6 | 10.0.0.1 | *90AE800593(...)E42D17EEA5369 |
  1053. # | user '1 | localhost | *4DF33B3B1(...)327877FAB2F4BA |
  1054. # | user ``"5 | localhost | *3752E65CD(...)FC6C998B12C376 |
  1055. # | user":;,?:@=&/4標 | % | *FC8EF8DBF(...)7478D5CF3DD57C |
  1056. # +--------------------+-----------+-------------------------------+
  1057. self._chck_userinfo(
  1058. user=user2, host="10.0.0.1", check_user=user2, check_hash=""
  1059. )
  1060. self._chck_userinfo(
  1061. user=user2, host="10.0.0.2", check_user=user2, check_hash=""
  1062. )
  1063. self._chck_userinfo(
  1064. user=user2, host="localhost", check_user=user2, check_hash=user2_pwd_hash
  1065. )
  1066. self._chck_userinfo(
  1067. user=user3, host="%", check_user=user3, check_hash=user3_pwd_hash
  1068. )
  1069. self._chck_userinfo(
  1070. user=user3, host="localhost", check_user=user3, check_hash=""
  1071. )
  1072. self._chck_userinfo(
  1073. user=user4, host="%", check_user=user4_utf8, check_hash=user4_pwd_hash
  1074. )
  1075. self._chck_userinfo(
  1076. user=user6,
  1077. host="10.0.0.1",
  1078. check_user=user6_utf8,
  1079. check_hash=user6_pwd_hash,
  1080. )
  1081. self._chck_userinfo(
  1082. user=user1, host="localhost", check_user=user1, check_hash=user1_pwd_hash
  1083. )
  1084. self._chck_userinfo(
  1085. user=user5,
  1086. host="localhost",
  1087. check_user=user5_utf8,
  1088. check_hash=user5_pwd_hash,
  1089. )
  1090. # check user_list function
  1091. ret = self.run_function(
  1092. "mysql.user_list",
  1093. connection_user=self.user,
  1094. connection_pass=self.password,
  1095. connection_charset="utf8",
  1096. saltenv={"LC_ALL": "en_US.utf8"},
  1097. )
  1098. self.assertIn({"Host": "localhost", "User": user1}, ret)
  1099. self.assertIn({"Host": "localhost", "User": user2}, ret)
  1100. self.assertIn({"Host": "10.0.0.1", "User": user2}, ret)
  1101. self.assertIn({"Host": "10.0.0.2", "User": user2}, ret)
  1102. self.assertIn({"Host": "%", "User": user3}, ret)
  1103. self.assertIn({"Host": "localhost", "User": user3}, ret)
  1104. self.assertIn({"Host": "%", "User": user4_utf8}, ret)
  1105. self.assertIn({"Host": "localhost", "User": user5_utf8}, ret)
  1106. self.assertIn({"Host": "10.0.0.1", "User": user6_utf8}, ret)
  1107. # And finally, test connections on MySQL with theses users
  1108. ret = self.run_function(
  1109. "mysql.query",
  1110. database="information_schema",
  1111. query="SELECT 1",
  1112. connection_user=user1,
  1113. connection_pass="pwd`'\"1b",
  1114. connection_host="localhost",
  1115. )
  1116. if not isinstance(ret, dict) or "results" not in ret:
  1117. raise AssertionError(
  1118. (
  1119. "Unexpected result while testing connection" " with user '{0}': {1}"
  1120. ).format(user1, repr(ret))
  1121. )
  1122. self.assertEqual([["1"]], ret["results"])
  1123. # FIXME: still failing, but works by hand...
  1124. # mysql --user="user \"2'標" --password="user \"2'標b" information_schema
  1125. # Seems to be a python-mysql library problem with user names containing
  1126. # utf8 characters
  1127. # @see https://github.com/farcepest/MySQLdb1/issues/40
  1128. # import urllib
  1129. # ret = self.run_function(
  1130. # 'mysql.query',
  1131. # database='information_schema',
  1132. # query='SELECT 1',
  1133. # connection_user=urllib.quote_plus(user2),
  1134. # connection_pass=urllib.quote_plus(user2_pwd),
  1135. # connection_host='localhost',
  1136. # connection_charset='utf8',
  1137. # saltenv={"LC_ALL": "en_US.utf8"}
  1138. # )
  1139. # if not isinstance(ret, dict) or 'results' not in ret:
  1140. # raise AssertionError(
  1141. # ('Unexpected result while testing connection'
  1142. # ' with user \'{0}\': {1}').format(
  1143. # user2,
  1144. # repr(ret)
  1145. # )
  1146. # )
  1147. # self.assertEqual([['1']], ret['results'])
  1148. ret = self.run_function(
  1149. "mysql.query",
  1150. database="information_schema",
  1151. query="SELECT 1",
  1152. connection_user=user3,
  1153. connection_pass="",
  1154. connection_host="localhost",
  1155. )
  1156. if not isinstance(ret, dict) or "results" not in ret:
  1157. raise AssertionError(
  1158. (
  1159. "Unexpected result while testing connection" " with user '{0}': {1}"
  1160. ).format(user3, repr(ret))
  1161. )
  1162. self.assertEqual([["1"]], ret["results"])
  1163. # FIXME: Failing
  1164. # ret = self.run_function(
  1165. # 'mysql.query',
  1166. # database='information_schema',
  1167. # query='SELECT 1',
  1168. # connection_user=user4_utf8,
  1169. # connection_pass=user4_pwd,
  1170. # connection_host='localhost',
  1171. # connection_charset='utf8',
  1172. # saltenv={"LC_ALL": "en_US.utf8"}
  1173. # )
  1174. # if not isinstance(ret, dict) or 'results' not in ret:
  1175. # raise AssertionError(
  1176. # ('Unexpected result while testing connection'
  1177. # ' with user \'{0}\': {1}').format(
  1178. # user4_utf8,
  1179. # repr(ret)
  1180. # )
  1181. # )
  1182. # self.assertEqual([['1']], ret['results'])
  1183. ret = self.run_function(
  1184. "mysql.query",
  1185. database="information_schema",
  1186. query="SELECT 1",
  1187. connection_user=user5_utf8,
  1188. connection_pass=user5_pwd,
  1189. connection_host="localhost",
  1190. connection_charset="utf8",
  1191. saltenv={"LC_ALL": "en_US.utf8"},
  1192. )
  1193. if not isinstance(ret, dict) or "results" not in ret:
  1194. raise AssertionError(
  1195. (
  1196. "Unexpected result while testing connection" " with user '{0}': {1}"
  1197. ).format(user5_utf8, repr(ret))
  1198. )
  1199. self.assertEqual([["1"]], ret["results"])
  1200. # Teardown by deleting with user_remove
  1201. self._chk_remove_user(
  1202. user=user2,
  1203. host="10.0.0.1",
  1204. connection_user=self.user,
  1205. connection_pass=self.password,
  1206. connection_charset="utf8",
  1207. saltenv={"LC_ALL": "en_US.utf8"},
  1208. )
  1209. self._chk_remove_user(
  1210. user=user2,
  1211. host="10.0.0.2",
  1212. connection_user=self.user,
  1213. connection_pass=self.password,
  1214. connection_charset="utf8",
  1215. saltenv={"LC_ALL": "en_US.utf8"},
  1216. )
  1217. self._chk_remove_user(
  1218. user=user2,
  1219. host="localhost",
  1220. connection_user=self.user,
  1221. connection_pass=self.password,
  1222. connection_charset="utf8",
  1223. saltenv={"LC_ALL": "en_US.utf8"},
  1224. )
  1225. self._chk_remove_user(
  1226. user=user3,
  1227. host="%",
  1228. connection_user=self.user,
  1229. connection_pass=self.password,
  1230. )
  1231. self._chk_remove_user(
  1232. user=user3,
  1233. host="localhost",
  1234. connection_user=self.user,
  1235. connection_pass=self.password,
  1236. )
  1237. self._chk_remove_user(
  1238. user=user4,
  1239. host="%",
  1240. connection_user=self.user,
  1241. connection_pass=self.password,
  1242. connection_charset="utf8",
  1243. saltenv={"LC_ALL": "en_US.utf8"},
  1244. )
  1245. self._chk_remove_user(
  1246. user=user6,
  1247. host="10.0.0.1",
  1248. connection_user=self.user,
  1249. connection_pass=self.password,
  1250. )
  1251. self._chk_remove_user(
  1252. user=user1,
  1253. host="localhost",
  1254. connection_user=self.user,
  1255. connection_pass=self.password,
  1256. )
  1257. self._chk_remove_user(
  1258. user=user5,
  1259. host="localhost",
  1260. connection_user=self.user,
  1261. connection_pass=self.password,
  1262. )
  1263. # Final verification of the cleanup
  1264. ret = self.run_function(
  1265. "mysql.user_list",
  1266. connection_user=self.user,
  1267. connection_pass=self.password,
  1268. connection_charset="utf8",
  1269. saltenv={"LC_ALL": "en_US.utf8"},
  1270. )
  1271. self.assertNotIn({"Host": "localhost", "User": user1}, ret)
  1272. self.assertNotIn({"Host": "localhost", "User": user2}, ret)
  1273. self.assertNotIn({"Host": "10.0.0.1", "User": user2}, ret)
  1274. self.assertNotIn({"Host": "10.0.0.2", "User": user2}, ret)
  1275. self.assertNotIn({"Host": "%", "User": user3}, ret)
  1276. self.assertNotIn({"Host": "localhost", "User": user3}, ret)
  1277. self.assertNotIn({"Host": "%", "User": user4_utf8}, ret)
  1278. self.assertNotIn({"Host": "localhost", "User": user5_utf8}, ret)
  1279. self.assertNotIn({"Host": "10.0.0.1", "User": user6_utf8}, ret)
  1280. @skipIf(
  1281. NO_MYSQL,
  1282. "Please install MySQL bindings and a MySQL Server before running"
  1283. "MySQL integration tests.",
  1284. )
  1285. @pytest.mark.windows_whitelisted
  1286. class MysqlModuleUserGrantTest(ModuleCase, SaltReturnAssertsMixin):
  1287. """
  1288. User Creation and connection tests
  1289. """
  1290. user = "root"
  1291. password = "poney"
  1292. # yep, theses are valid MySQL db names
  1293. # very special chars are _ % and .
  1294. testdb1 = "tes.t'\"saltdb"
  1295. testdb2 = "t_st `(:=salt%b)"
  1296. testdb3 = "test `(:=salteeb)"
  1297. test_file_query_db = "test_query"
  1298. table1 = "foo"
  1299. table2 = "foo `'%_bar"
  1300. users = {
  1301. "user1": {"name": "foo", "pwd": "bar"},
  1302. "user2": {"name": 'user ";--,?:&/\\', "pwd": '";--(),?:@=&/\\'},
  1303. # this is : passwd 標標
  1304. "user3": {"name": "user( @ )=foobar", "pwd": "\xe6\xa8\x99\xe6\xa8\x99"},
  1305. # this is : user/password containing 標標
  1306. "user4": {"name": "user \xe6\xa8\x99", "pwd": "\xe6\xa8\x99\xe6\xa8\x99"},
  1307. }
  1308. @destructiveTest
  1309. def setUp(self):
  1310. """
  1311. Test presence of MySQL server, enforce a root password, create users
  1312. """
  1313. super(MysqlModuleUserGrantTest, self).setUp()
  1314. NO_MYSQL_SERVER = True
  1315. # now ensure we know the mysql root password
  1316. # one of theses two at least should work
  1317. ret1 = self.run_state(
  1318. "cmd.run",
  1319. name='mysqladmin --host="localhost" -u '
  1320. + self.user
  1321. + ' flush-privileges password "'
  1322. + self.password
  1323. + '"',
  1324. )
  1325. ret2 = self.run_state(
  1326. "cmd.run",
  1327. name='mysqladmin --host="localhost" -u '
  1328. + self.user
  1329. + ' --password="'
  1330. + self.password
  1331. + '" flush-privileges password "'
  1332. + self.password
  1333. + '"',
  1334. )
  1335. key, value = ret2.popitem()
  1336. if value["result"]:
  1337. NO_MYSQL_SERVER = False
  1338. else:
  1339. self.skipTest("No MySQL Server running, or no root access on it.")
  1340. # Create some users and a test db
  1341. for user, userdef in six.iteritems(self.users):
  1342. self._userCreation(uname=userdef["name"], password=userdef["pwd"])
  1343. self.run_function(
  1344. "mysql.db_create",
  1345. name=self.testdb1,
  1346. connection_user=self.user,
  1347. connection_pass=self.password,
  1348. )
  1349. self.run_function(
  1350. "mysql.db_create",
  1351. name=self.testdb2,
  1352. connection_user=self.user,
  1353. connection_pass=self.password,
  1354. )
  1355. create_query = (
  1356. "CREATE TABLE {tblname} ("
  1357. " id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,"
  1358. " data VARCHAR(100)) ENGINE={engine};".format(
  1359. tblname=mysqlmod.quote_identifier(self.table1), engine="MYISAM",
  1360. )
  1361. )
  1362. log.info("Adding table '%s'", self.table1)
  1363. self.run_function(
  1364. "mysql.query",
  1365. database=self.testdb2,
  1366. query=create_query,
  1367. connection_user=self.user,
  1368. connection_pass=self.password,
  1369. )
  1370. create_query = (
  1371. "CREATE TABLE {tblname} ("
  1372. " id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,"
  1373. " data VARCHAR(100)) ENGINE={engine};".format(
  1374. tblname=mysqlmod.quote_identifier(self.table2), engine="MYISAM",
  1375. )
  1376. )
  1377. log.info("Adding table '%s'", self.table2)
  1378. self.run_function(
  1379. "mysql.query",
  1380. database=self.testdb2,
  1381. query=create_query,
  1382. connection_user=self.user,
  1383. connection_pass=self.password,
  1384. )
  1385. @destructiveTest
  1386. def tearDown(self):
  1387. """
  1388. Removes created users and db
  1389. """
  1390. for user, userdef in six.iteritems(self.users):
  1391. self._userRemoval(uname=userdef["name"], password=userdef["pwd"])
  1392. self.run_function(
  1393. "mysql.db_remove",
  1394. name=self.testdb1,
  1395. connection_user=self.user,
  1396. connection_pass=self.password,
  1397. )
  1398. self.run_function(
  1399. "mysql.db_remove",
  1400. name=self.testdb2,
  1401. connection_user=self.user,
  1402. connection_pass=self.password,
  1403. )
  1404. self.run_function(
  1405. "mysql.db_remove",
  1406. name=self.test_file_query_db,
  1407. connection_user=self.user,
  1408. connection_pass=self.password,
  1409. )
  1410. def _userCreation(self, uname, password=None):
  1411. """
  1412. Create a test user
  1413. """
  1414. self.run_function(
  1415. "mysql.user_create",
  1416. user=uname,
  1417. host="localhost",
  1418. password=password,
  1419. connection_user=self.user,
  1420. connection_pass=self.password,
  1421. connection_charset="utf8",
  1422. saltenv={"LC_ALL": "en_US.utf8"},
  1423. )
  1424. def _userRemoval(self, uname, password=None):
  1425. """
  1426. Removes a test user
  1427. """
  1428. self.run_function(
  1429. "mysql.user_remove",
  1430. user=uname,
  1431. host="localhost",
  1432. connection_user=self.user,
  1433. connection_pass=self.password,
  1434. connection_charset="utf8",
  1435. saltenv={"LC_ALL": "en_US.utf8"},
  1436. )
  1437. def _addGrantRoutine(
  1438. self, grant, user, db, grant_option=False, escape=True, **kwargs
  1439. ):
  1440. """
  1441. Perform some tests around creation of the given grants
  1442. """
  1443. ret = self.run_function(
  1444. "mysql.grant_add",
  1445. grant=grant,
  1446. database=db,
  1447. user=user,
  1448. grant_option=grant_option,
  1449. escape=escape,
  1450. **kwargs
  1451. )
  1452. self.assertEqual(
  1453. True,
  1454. ret,
  1455. (
  1456. "Calling grant_add on"
  1457. " user '{0}' and grants '{1}' did not return True: {2}"
  1458. ).format(user, grant, repr(ret)),
  1459. )
  1460. ret = self.run_function(
  1461. "mysql.grant_exists",
  1462. grant=grant,
  1463. database=db,
  1464. user=user,
  1465. grant_option=grant_option,
  1466. escape=escape,
  1467. **kwargs
  1468. )
  1469. self.assertEqual(
  1470. True,
  1471. ret,
  1472. (
  1473. "Calling grant_exists on"
  1474. " user '{0}' and grants '{1}' did not return True: {2}"
  1475. ).format(user, grant, repr(ret)),
  1476. )
  1477. @destructiveTest
  1478. def testGrants(self):
  1479. """
  1480. Test user grant methods
  1481. """
  1482. self._addGrantRoutine(
  1483. grant="SELECT, INSERT,UPDATE, CREATE",
  1484. user=self.users["user1"]["name"],
  1485. db=self.testdb1 + ".*",
  1486. grant_option=True,
  1487. escape=True,
  1488. connection_user=self.user,
  1489. connection_pass=self.password,
  1490. )
  1491. self._addGrantRoutine(
  1492. grant="INSERT, SELECT",
  1493. user=self.users["user1"]["name"],
  1494. db=self.testdb2 + "." + self.table1,
  1495. grant_option=True,
  1496. escape=True,
  1497. connection_user=self.user,
  1498. connection_pass=self.password,
  1499. )
  1500. self._addGrantRoutine(
  1501. grant=" SELECT, UPDATE,DELETE, CREATE TEMPORARY TABLES",
  1502. user=self.users["user2"]["name"],
  1503. db=self.testdb1 + ".*",
  1504. grant_option=True,
  1505. escape=True,
  1506. connection_user=self.user,
  1507. connection_pass=self.password,
  1508. )
  1509. self._addGrantRoutine(
  1510. grant="select, ALTER,CREATE TEMPORARY TABLES, EXECUTE ",
  1511. user=self.users["user3"]["name"],
  1512. db=self.testdb1 + ".*",
  1513. grant_option=True,
  1514. escape=True,
  1515. connection_user=self.user,
  1516. connection_pass=self.password,
  1517. )
  1518. self._addGrantRoutine(
  1519. grant="SELECT, INSERT",
  1520. user=self.users["user4"]["name"],
  1521. db=self.testdb2 + "." + self.table2,
  1522. grant_option=False,
  1523. escape=True,
  1524. connection_user=self.user,
  1525. connection_pass=self.password,
  1526. connection_charset="utf8",
  1527. )
  1528. self._addGrantRoutine(
  1529. grant="CREATE",
  1530. user=self.users["user4"]["name"],
  1531. db=self.testdb2 + ".*",
  1532. grant_option=False,
  1533. escape=True,
  1534. connection_user=self.user,
  1535. connection_pass=self.password,
  1536. connection_charset="utf8",
  1537. )
  1538. self._addGrantRoutine(
  1539. grant="SELECT, INSERT",
  1540. user=self.users["user4"]["name"],
  1541. db=self.testdb2 + "." + self.table1,
  1542. grant_option=False,
  1543. escape=True,
  1544. connection_user=self.user,
  1545. connection_pass=self.password,
  1546. connection_charset="utf8",
  1547. )
  1548. # '' is valid for anonymous users
  1549. self._addGrantRoutine(
  1550. grant="DELETE",
  1551. user="",
  1552. db=self.testdb3 + ".*",
  1553. grant_option=False,
  1554. escape=True,
  1555. connection_user=self.user,
  1556. connection_pass=self.password,
  1557. )
  1558. # Check result for users
  1559. ret = self.run_function(
  1560. "mysql.user_grants",
  1561. user=self.users["user1"]["name"],
  1562. host="localhost",
  1563. connection_user=self.user,
  1564. connection_pass=self.password,
  1565. )
  1566. self.assertEqual(
  1567. ret,
  1568. [
  1569. "GRANT USAGE ON *.* TO 'foo'@'localhost'",
  1570. (
  1571. "GRANT SELECT, INSERT, UPDATE, CREATE ON "
  1572. "`tes.t'\"saltdb`.* TO 'foo'@'localhost' WITH GRANT OPTION"
  1573. ),
  1574. (
  1575. "GRANT SELECT, INSERT ON `t_st ``(:=salt%b)`.`foo`"
  1576. " TO 'foo'@'localhost' WITH GRANT OPTION"
  1577. ),
  1578. ],
  1579. )
  1580. ret = self.run_function(
  1581. "mysql.user_grants",
  1582. user=self.users["user2"]["name"],
  1583. host="localhost",
  1584. connection_user=self.user,
  1585. connection_pass=self.password,
  1586. )
  1587. self.assertEqual(
  1588. ret,
  1589. [
  1590. "GRANT USAGE ON *.* TO 'user \";--,?:&/\\'@'localhost'",
  1591. (
  1592. "GRANT SELECT, UPDATE, DELETE, CREATE TEMPORARY TABLES ON `tes.t'"
  1593. "\"saltdb`.* TO 'user \";--,?:&/\\'@'localhost'"
  1594. " WITH GRANT OPTION"
  1595. ),
  1596. ],
  1597. )
  1598. ret = self.run_function(
  1599. "mysql.user_grants",
  1600. user=self.users["user3"]["name"],
  1601. host="localhost",
  1602. connection_user=self.user,
  1603. connection_pass=self.password,
  1604. )
  1605. self.assertEqual(
  1606. ret,
  1607. [
  1608. "GRANT USAGE ON *.* TO 'user( @ )=foobar'@'localhost'",
  1609. (
  1610. "GRANT SELECT, ALTER, CREATE TEMPORARY TABLES, EXECUTE ON "
  1611. "`tes.t'\"saltdb`.* TO 'user( @ )=foobar'@'localhost' "
  1612. "WITH GRANT OPTION"
  1613. ),
  1614. ],
  1615. )
  1616. ret = self.run_function(
  1617. "mysql.user_grants",
  1618. user=self.users["user4"]["name"],
  1619. host="localhost",
  1620. connection_user=self.user,
  1621. connection_pass=self.password,
  1622. connection_charset="utf8",
  1623. )
  1624. self.assertEqual(
  1625. ret,
  1626. [
  1627. "GRANT USAGE ON *.* TO 'user \xe6\xa8\x99'@'localhost'",
  1628. (
  1629. r"GRANT CREATE ON `t\_st ``(:=salt\%b)`.* TO "
  1630. "'user \xe6\xa8\x99'@'localhost'"
  1631. ),
  1632. (
  1633. "GRANT SELECT, INSERT ON `t_st ``(:=salt%b)`.`foo ``'%_bar` TO "
  1634. "'user \xe6\xa8\x99'@'localhost'"
  1635. ),
  1636. (
  1637. "GRANT SELECT, INSERT ON `t_st ``(:=salt%b)`.`foo` TO "
  1638. "'user \xe6\xa8\x99'@'localhost'"
  1639. ),
  1640. ],
  1641. )
  1642. ret = self.run_function(
  1643. "mysql.user_grants",
  1644. user="",
  1645. host="localhost",
  1646. connection_user=self.user,
  1647. connection_pass=self.password,
  1648. )
  1649. self.assertEqual(
  1650. ret,
  1651. [
  1652. "GRANT USAGE ON *.* TO ''@'localhost'",
  1653. "GRANT DELETE ON `test ``(:=salteeb)`.* TO ''@'localhost'",
  1654. ],
  1655. )
  1656. @skipIf(
  1657. NO_MYSQL,
  1658. "Please install MySQL bindings and a MySQL Server before running"
  1659. "MySQL integration tests.",
  1660. )
  1661. @pytest.mark.windows_whitelisted
  1662. class MysqlModuleFileQueryTest(ModuleCase, SaltReturnAssertsMixin):
  1663. """
  1664. Test file query module
  1665. """
  1666. user = "root"
  1667. password = "poney"
  1668. testdb = "test_file_query"
  1669. @destructiveTest
  1670. def setUp(self):
  1671. """
  1672. Test presence of MySQL server, enforce a root password, create users
  1673. """
  1674. super(MysqlModuleFileQueryTest, self).setUp()
  1675. NO_MYSQL_SERVER = True
  1676. # now ensure we know the mysql root password
  1677. # one of theses two at least should work
  1678. ret1 = self.run_state(
  1679. "cmd.run",
  1680. name='mysqladmin --host="localhost" -u '
  1681. + self.user
  1682. + ' flush-privileges password "'
  1683. + self.password
  1684. + '"',
  1685. )
  1686. ret2 = self.run_state(
  1687. "cmd.run",
  1688. name='mysqladmin --host="localhost" -u '
  1689. + self.user
  1690. + ' --password="'
  1691. + self.password
  1692. + '" flush-privileges password "'
  1693. + self.password
  1694. + '"',
  1695. )
  1696. key, value = ret2.popitem()
  1697. if value["result"]:
  1698. NO_MYSQL_SERVER = False
  1699. else:
  1700. self.skipTest("No MySQL Server running, or no root access on it.")
  1701. # Create some users and a test db
  1702. self.run_function(
  1703. "mysql.db_create",
  1704. name=self.testdb,
  1705. connection_user=self.user,
  1706. connection_pass=self.password,
  1707. connection_db="mysql",
  1708. )
  1709. @destructiveTest
  1710. def tearDown(self):
  1711. """
  1712. Removes created users and db
  1713. """
  1714. self.run_function(
  1715. "mysql.db_remove",
  1716. name=self.testdb,
  1717. connection_user=self.user,
  1718. connection_pass=self.password,
  1719. connection_db="mysql",
  1720. )
  1721. @destructiveTest
  1722. def test_update_file_query(self):
  1723. """
  1724. Test query without any output
  1725. """
  1726. ret = self.run_function(
  1727. "mysql.file_query",
  1728. database=self.testdb,
  1729. file_name="salt://mysql/update_query.sql",
  1730. character_set="utf8",
  1731. collate="utf8_general_ci",
  1732. connection_user=self.user,
  1733. connection_pass=self.password,
  1734. )
  1735. self.assertTrue("query time" in ret)
  1736. ret.pop("query time")
  1737. self.assertEqual(ret, {"rows affected": 2})
  1738. @destructiveTest
  1739. def test_select_file_query(self):
  1740. """
  1741. Test query with table output
  1742. """
  1743. ret = self.run_function(
  1744. "mysql.file_query",
  1745. database=self.testdb,
  1746. file_name="salt://mysql/select_query.sql",
  1747. character_set="utf8",
  1748. collate="utf8_general_ci",
  1749. connection_user=self.user,
  1750. connection_pass=self.password,
  1751. )
  1752. expected = {
  1753. "rows affected": 5,
  1754. "rows returned": 4,
  1755. "results": [[["2"], ["3"], ["4"], ["5"]]],
  1756. "columns": [["a"]],
  1757. }
  1758. self.assertTrue("query time" in ret)
  1759. ret.pop("query time")
  1760. self.assertEqual(ret, expected)