test_mysql.py 60 KB

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