1""" 2Management of MySQL users 3========================= 4 5:depends: - MySQLdb Python module 6:configuration: See :py:mod:`salt.modules.mysql` for setup instructions. 7 8.. code-block:: yaml 9 10 frank: 11 mysql_user.present: 12 - host: localhost 13 - password: bobcat 14 15 16.. versionadded:: 0.16.2 17 Authentication overrides have been added. 18 19The MySQL authentication information specified in the minion config file can be 20overridden in states using the following arguments: ``connection_host``, 21``connection_port``, ``connection_user``, ``connection_pass``, 22``connection_db``, ``connection_unix_socket``, ``connection_default_file`` and 23``connection_charset``. 24 25.. code-block:: yaml 26 27 frank: 28 mysql_user.present: 29 - host: localhost 30 - password: "bob@cat" 31 - connection_user: someuser 32 - connection_pass: somepass 33 - connection_charset: utf8 34 - saltenv: 35 - LC_ALL: "en_US.utf8" 36 37 38This state is not able to grant permissions for the user. See 39:py:mod:`salt.states.mysql_grants` for further instructions. 40 41""" 42 43import sys 44 45import salt.utils.data 46 47 48def __virtual__(): 49 """ 50 Only load if the mysql module is in __salt__ 51 """ 52 if "mysql.user_create" in __salt__: 53 return True 54 return (False, "mysql module could not be loaded") 55 56 57def _get_mysql_error(): 58 """ 59 Look in module context for a MySQL error. Eventually we should make a less 60 ugly way of doing this. 61 """ 62 return sys.modules[__salt__["test.ping"].__module__].__context__.pop( 63 "mysql.error", None 64 ) 65 66 67def present( 68 name, 69 host="localhost", 70 password=None, 71 password_hash=None, 72 allow_passwordless=False, 73 unix_socket=False, 74 password_column=None, 75 auth_plugin="mysql_native_password", 76 **connection_args 77): 78 """ 79 Ensure that the named user is present with the specified properties. A 80 passwordless user can be configured by omitting ``password`` and 81 ``password_hash``, and setting ``allow_passwordless`` to ``True``. 82 83 name 84 The name of the user to manage 85 86 host 87 Host for which this user/password combo applies 88 89 password 90 The password to use for this user. Will take precedence over the 91 ``password_hash`` option if both are specified. 92 93 password_hash 94 The password in hashed form. Be sure to quote the password because YAML 95 doesn't like the ``*``. A password hash can be obtained from the mysql 96 command-line client like so:: 97 98 mysql> SELECT PASSWORD('mypass'); 99 +-------------------------------------------+ 100 | PASSWORD('mypass') | 101 +-------------------------------------------+ 102 | *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 | 103 +-------------------------------------------+ 104 1 row in set (0.00 sec) 105 106 allow_passwordless 107 If ``True``, then ``password`` and ``password_hash`` can be omitted to 108 permit a passwordless login. 109 110 .. versionadded:: 0.16.2 111 112 unix_socket 113 If ``True`` and allow_passwordless is ``True``, the unix_socket auth 114 plugin will be used. 115 """ 116 ret = { 117 "name": name, 118 "changes": {}, 119 "result": True, 120 "comment": "User {}@{} is already present".format(name, host), 121 } 122 123 passwordless = not any((password, password_hash)) 124 125 # check if user exists with the same password (or passwordless login) 126 if passwordless: 127 if not salt.utils.data.is_true(allow_passwordless) and not unix_socket: 128 ret["comment"] = ( 129 "Either password or password_hash must be " 130 "specified, unless allow_passwordless is True" 131 ) 132 ret["result"] = False 133 return ret 134 else: 135 if __salt__["mysql.user_exists"]( 136 name, 137 host, 138 passwordless=True, 139 unix_socket=unix_socket, 140 password_column=password_column, 141 **connection_args 142 ): 143 if allow_passwordless: 144 ret["comment"] += " with passwordless login" 145 return ret 146 else: 147 err = _get_mysql_error() 148 if err is not None: 149 ret["comment"] = err 150 ret["result"] = False 151 return ret 152 else: 153 if __salt__["mysql.user_exists"]( 154 name, 155 host, 156 password, 157 password_hash, 158 unix_socket=unix_socket, 159 password_column=password_column, 160 **connection_args 161 ): 162 if auth_plugin == "mysql_native_password": 163 ret["comment"] += " with the desired password" 164 if password_hash and not password: 165 ret["comment"] += " hash" 166 else: 167 ret["comment"] += ". Unable to verify password." 168 return ret 169 else: 170 err = _get_mysql_error() 171 if err is not None: 172 ret["comment"] = err 173 ret["result"] = False 174 return ret 175 176 # check if user exists with a different password 177 if __salt__["mysql.user_exists"]( 178 name, host, unix_socket=unix_socket, **connection_args 179 ): 180 181 # The user is present, change the password 182 if __opts__["test"]: 183 ret["comment"] = "Password for user {}@{} is set to be ".format(name, host) 184 ret["result"] = None 185 if passwordless: 186 ret["comment"] += "cleared" 187 if not salt.utils.data.is_true(allow_passwordless): 188 ret["comment"] += ", but allow_passwordless != True" 189 ret["result"] = False 190 else: 191 ret["comment"] += "changed" 192 return ret 193 194 if __salt__["mysql.user_chpass"]( 195 name, 196 host, 197 password, 198 password_hash, 199 allow_passwordless, 200 unix_socket, 201 **connection_args 202 ): 203 ret["comment"] = "Password for user {}@{} has been {}".format( 204 name, host, "cleared" if passwordless else "changed" 205 ) 206 ret["changes"][name] = "Updated" 207 else: 208 ret["comment"] = "Failed to {} password for user {}@{}".format( 209 "clear" if passwordless else "change", name, host 210 ) 211 err = _get_mysql_error() 212 if err is not None: 213 ret["comment"] += " ({})".format(err) 214 if passwordless and not salt.utils.data.is_true(allow_passwordless): 215 ret["comment"] += ( 216 ". Note: allow_passwordless must be True " 217 "to permit passwordless login." 218 ) 219 ret["result"] = False 220 else: 221 222 err = _get_mysql_error() 223 if err is not None: 224 ret["comment"] = err 225 ret["result"] = False 226 return ret 227 228 # The user is not present, make it! 229 if __opts__["test"]: 230 ret["comment"] = "User {}@{} is set to be added".format(name, host) 231 ret["result"] = None 232 if allow_passwordless: 233 ret["comment"] += " with passwordless login" 234 if not salt.utils.data.is_true(allow_passwordless): 235 ret["comment"] += ", but allow_passwordless != True" 236 ret["result"] = False 237 if unix_socket: 238 ret["comment"] += " using unix_socket" 239 return ret 240 241 if __salt__["mysql.user_create"]( 242 name, 243 host, 244 password, 245 password_hash, 246 allow_passwordless, 247 unix_socket=unix_socket, 248 password_column=password_column, 249 auth_plugin=auth_plugin, 250 **connection_args 251 ): 252 ret["comment"] = "The user {}@{} has been added".format(name, host) 253 if allow_passwordless: 254 ret["comment"] += " with passwordless login" 255 if unix_socket: 256 ret["comment"] += " using unix_socket" 257 ret["changes"][name] = "Present" 258 else: 259 ret["comment"] = "Failed to create user {}@{}".format(name, host) 260 err = _get_mysql_error() 261 if err is not None: 262 ret["comment"] += " ({})".format(err) 263 ret["result"] = False 264 265 return ret 266 267 268def absent(name, host="localhost", **connection_args): 269 """ 270 Ensure that the named user is absent 271 272 name 273 The name of the user to remove 274 """ 275 ret = {"name": name, "changes": {}, "result": True, "comment": ""} 276 277 # Check if user exists, and if so, remove it 278 if __salt__["mysql.user_exists"](name, host, **connection_args): 279 if __opts__["test"]: 280 ret["result"] = None 281 ret["comment"] = "User {}@{} is set to be removed".format(name, host) 282 return ret 283 if __salt__["mysql.user_remove"](name, host, **connection_args): 284 ret["comment"] = "User {}@{} has been removed".format(name, host) 285 ret["changes"][name] = "Absent" 286 return ret 287 else: 288 err = _get_mysql_error() 289 if err is not None: 290 ret["comment"] = err 291 ret["result"] = False 292 return ret 293 else: 294 err = _get_mysql_error() 295 if err is not None: 296 ret["comment"] = err 297 ret["result"] = False 298 return ret 299 300 # fallback 301 ret["comment"] = "User {}@{} is not present, so it cannot be removed".format( 302 name, host 303 ) 304 return ret 305