| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 1 | # Copyright 2019 Whitestack, LLC |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 4 | # not use this file except in compliance with the License. You may obtain |
| 5 | # a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 12 | # License for the specific language governing permissions and limitations |
| 13 | # under the License. |
| 14 | # |
| 15 | # For those usages not covered by the Apache License, Version 2.0 please |
| 16 | # contact: esousa@whitestack.com or glavado@whitestack.com |
| 17 | ## |
| 18 | |
| 19 | """ |
| 20 | OSM role mgmt API |
| 21 | """ |
| 22 | |
| 23 | from osmclient.common import utils |
| 24 | from osmclient.common.exceptions import ClientException |
| 25 | from osmclient.common.exceptions import NotFound |
| 26 | import json |
| 27 | import yaml |
| garciadeblas | 6bc001c | 2019-11-21 12:02:05 +0100 | [diff] [blame] | 28 | import logging |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 29 | |
| 30 | |
| 31 | class Role(object): |
| 32 | def __init__(self, http=None, client=None): |
| 33 | self._http = http |
| 34 | self._client = client |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 35 | self._logger = logging.getLogger("osmclient") |
| 36 | self._apiName = "/admin" |
| 37 | self._apiVersion = "/v1" |
| 38 | self._apiResource = "/roles" |
| 39 | self._apiBase = "{}{}{}".format( |
| 40 | self._apiName, self._apiVersion, self._apiResource |
| 41 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 42 | |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 43 | def create(self, name, permissions): |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 44 | """ |
| 45 | Creates a new OSM role. |
| 46 | |
| 47 | :param name: name of the role. |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 48 | :param permissions: permissions of the role in YAML. |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 49 | :raises ClientException: when receives an unexpected from the server. |
| 50 | :raises ClientException: when fails creating a role. |
| 51 | """ |
| garciadeblas | 6bc001c | 2019-11-21 12:02:05 +0100 | [diff] [blame] | 52 | self._logger.debug("") |
| pinoa | 93012ad | 2019-11-05 14:28:15 +0100 | [diff] [blame] | 53 | self._client.get_token() |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 54 | role = {"name": name} |
| 55 | |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 56 | if permissions: |
| garciadeblas | dd006fa | 2019-11-07 09:30:30 +0100 | [diff] [blame] | 57 | role_permissions = yaml.safe_load(permissions) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 58 | |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 59 | if not isinstance(role_permissions, dict): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 60 | raise ClientException( |
| 61 | "Role permissions should be provided in a key-value fashion" |
| 62 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 63 | |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 64 | for key, value in role_permissions.items(): |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 65 | if not isinstance(value, bool): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 66 | raise ClientException( |
| 67 | "Value of '{}' in a role permissions should be boolean".format( |
| 68 | key |
| 69 | ) |
| 70 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 71 | |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 72 | role["permissions"] = role_permissions |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 73 | |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 74 | http_code, resp = self._http.post_cmd( |
| 75 | endpoint=self._apiBase, postfields_dict=role, skip_query_admin=True |
| 76 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 77 | # print('HTTP CODE: {}'.format(http_code)) |
| 78 | # print('RESP: {}'.format(resp)) |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 79 | # if http_code in (200, 201, 202, 204): |
| pinoa | 70d6f18 | 2019-12-12 12:10:27 +0100 | [diff] [blame] | 80 | if resp: |
| 81 | resp = json.loads(resp) |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 82 | if not resp or "id" not in resp: |
| 83 | raise ClientException("Unexpected response from server - {}".format(resp)) |
| 84 | print(resp["id"]) |
| 85 | # else: |
| pinoa | 70d6f18 | 2019-12-12 12:10:27 +0100 | [diff] [blame] | 86 | # msg = "" |
| 87 | # if resp: |
| 88 | # try: |
| 89 | # msg = json.loads(resp) |
| 90 | # except ValueError: |
| 91 | # msg = resp |
| 92 | # raise ClientException("Failed to create role {} - {}".format(name, msg)) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 93 | |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 94 | def update(self, name, new_name, permissions, add=None, remove=None): |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 95 | """ |
| 96 | Updates an OSM role identified by name. |
| 97 | |
| 98 | NOTE: definition and add/remove are mutually exclusive. |
| 99 | |
| 100 | :param name: name of the role |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 101 | :param set_name: if provided, change the name. |
| 102 | :param permissions: if provided, overwrites the existing role specification. NOT IMPLEMENTED |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 103 | :param add: if provided, adds new rules to the definition. |
| 104 | :param remove: if provided, removes rules from the definition. |
| 105 | :raises ClientException: when receives an unexpected response from the server. |
| 106 | :raises ClientException: when fails updating a role. |
| 107 | """ |
| garciadeblas | 6bc001c | 2019-11-21 12:02:05 +0100 | [diff] [blame] | 108 | self._logger.debug("") |
| pinoa | 93012ad | 2019-11-05 14:28:15 +0100 | [diff] [blame] | 109 | self._client.get_token() |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 110 | if new_name is None and permissions is None and add is None and remove is None: |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 111 | raise ClientException("At least one option should be provided") |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 112 | elif permissions and (add or remove): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 113 | raise ClientException("permissions and add/remove are mutually exclusive") |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 114 | |
| 115 | role_obj = self.get(name) |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 116 | new_role_obj = {"permissions": {}} |
| 117 | if new_name: |
| 118 | new_role_obj["name"] = new_name |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 119 | |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 120 | if permissions: |
| garciadeblas | dd006fa | 2019-11-07 09:30:30 +0100 | [diff] [blame] | 121 | role_definition = yaml.safe_load(permissions) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 122 | |
| 123 | if not isinstance(role_definition, dict): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 124 | raise ClientException( |
| 125 | "Role permissions should be provided in a key-value fashion" |
| 126 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 127 | |
| 128 | for key, value in role_definition.items(): |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 129 | if not isinstance(value, bool) and value is not None: |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 130 | raise ClientException( |
| 131 | "Value in a role permissions should be boolean or None to remove" |
| 132 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 133 | |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 134 | new_role_obj["permissions"] = role_definition |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 135 | else: |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 136 | if remove: |
| garciadeblas | dd006fa | 2019-11-07 09:30:30 +0100 | [diff] [blame] | 137 | keys_from_remove = yaml.safe_load(remove) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 138 | |
| 139 | if not isinstance(keys_from_remove, list): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 140 | raise ClientException("Keys should be provided in a list fashion") |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 141 | |
| 142 | for key in keys_from_remove: |
| 143 | if not isinstance(key, str): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 144 | raise ClientException("Individual keys should be strings") |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 145 | new_role_obj["permissions"][key] = None |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 146 | |
| 147 | if add: |
| garciadeblas | dd006fa | 2019-11-07 09:30:30 +0100 | [diff] [blame] | 148 | add_roles = yaml.safe_load(add) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 149 | |
| 150 | if not isinstance(add_roles, dict): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 151 | raise ClientException( |
| 152 | "Add should be provided in a key-value fashion" |
| 153 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 154 | |
| 155 | for key, value in add_roles.items(): |
| 156 | if not isinstance(value, bool): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 157 | raise ClientException( |
| 158 | "Value '{}' in a role permissions should be boolean".format( |
| 159 | key |
| 160 | ) |
| 161 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 162 | |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 163 | new_role_obj["permissions"][key] = value |
| 164 | if not new_role_obj["permissions"]: |
| 165 | del new_role_obj["permissions"] |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 166 | |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 167 | http_code, resp = self._http.patch_cmd( |
| 168 | endpoint="{}/{}".format(self._apiBase, role_obj["_id"]), |
| 169 | postfields_dict=new_role_obj, |
| 170 | skip_query_admin=True, |
| 171 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 172 | # print('HTTP CODE: {}'.format(http_code)) |
| 173 | # print('RESP: {}'.format(resp)) |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 174 | if http_code in (200, 201, 202): |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 175 | if resp: |
| 176 | resp = json.loads(resp) |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 177 | if not resp or "id" not in resp: |
| 178 | raise ClientException( |
| 179 | "Unexpected response from server - {}".format(resp) |
| 180 | ) |
| 181 | print(resp["id"]) |
| tierno | b2829e9 | 2019-06-13 22:38:05 +0000 | [diff] [blame] | 182 | elif http_code == 204: |
| 183 | print("Updated") |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 184 | # else: |
| pinoa | 70d6f18 | 2019-12-12 12:10:27 +0100 | [diff] [blame] | 185 | # msg = "" |
| 186 | # if resp: |
| 187 | # try: |
| 188 | # msg = json.loads(resp) |
| 189 | # except ValueError: |
| 190 | # msg = resp |
| 191 | # raise ClientException("Failed to update role {} - {}".format(name, msg)) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 192 | |
| 193 | def delete(self, name, force=False): |
| 194 | """ |
| 195 | Deletes an OSM role identified by name. |
| 196 | |
| 197 | :param name: |
| 198 | :param force: |
| 199 | :raises ClientException: when fails to delete a role. |
| 200 | """ |
| garciadeblas | 6bc001c | 2019-11-21 12:02:05 +0100 | [diff] [blame] | 201 | self._logger.debug("") |
| pinoa | 93012ad | 2019-11-05 14:28:15 +0100 | [diff] [blame] | 202 | self._client.get_token() |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 203 | role = self.get(name) |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 204 | querystring = "" |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 205 | if force: |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 206 | querystring = "?FORCE=True" |
| 207 | http_code, resp = self._http.delete_cmd( |
| 208 | "{}/{}{}".format(self._apiBase, role["_id"], querystring), |
| 209 | skip_query_admin=True, |
| 210 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 211 | # print('HTTP CODE: {}'.format(http_code)) |
| 212 | # print('RESP: {}'.format(resp)) |
| 213 | if http_code == 202: |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 214 | print("Deletion in progress") |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 215 | elif http_code == 204: |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 216 | print("Deleted") |
| 217 | elif resp and "result" in resp: |
| 218 | print("Deleted") |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 219 | else: |
| tierno | bd39b09 | 2020-01-21 09:27:09 +0000 | [diff] [blame] | 220 | msg = resp or "" |
| 221 | # if resp: |
| 222 | # try: |
| 223 | # msg = json.loads(resp) |
| 224 | # except ValueError: |
| 225 | # msg = resp |
| 226 | raise ClientException("Failed to delete role {} - {}".format(name, msg)) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 227 | |
| 228 | def list(self, filter=None): |
| 229 | """ |
| 230 | Returns the list of OSM role. |
| 231 | |
| 232 | :param filter: |
| 233 | :returns: |
| 234 | """ |
| garciadeblas | 6bc001c | 2019-11-21 12:02:05 +0100 | [diff] [blame] | 235 | self._logger.debug("") |
| pinoa | 93012ad | 2019-11-05 14:28:15 +0100 | [diff] [blame] | 236 | self._client.get_token() |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 237 | filter_string = "" |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 238 | if filter: |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 239 | filter_string = "?{}".format(filter) |
| 240 | _, resp = self._http.get2_cmd( |
| 241 | "{}{}".format(self._apiBase, filter_string), skip_query_admin=True |
| 242 | ) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 243 | # print('RESP: {}'.format(resp)) |
| 244 | if resp: |
| pinoa | 70d6f18 | 2019-12-12 12:10:27 +0100 | [diff] [blame] | 245 | return json.loads(resp) |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 246 | return list() |
| 247 | |
| 248 | def get(self, name): |
| 249 | """ |
| 250 | Returns a specific OSM role based on name or id. |
| 251 | |
| 252 | :param name: |
| 253 | :raises NotFound: when the role is not found. |
| 254 | :returns: the specified role. |
| 255 | """ |
| garciadeblas | 6bc001c | 2019-11-21 12:02:05 +0100 | [diff] [blame] | 256 | self._logger.debug("") |
| pinoa | 93012ad | 2019-11-05 14:28:15 +0100 | [diff] [blame] | 257 | self._client.get_token() |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 258 | if utils.validate_uuid4(name): |
| 259 | for role in self.list(): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 260 | if name == role["_id"]: |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 261 | return role |
| 262 | else: |
| 263 | for role in self.list(): |
| beierlm | 95686bb | 2021-03-23 16:26:45 -0400 | [diff] [blame^] | 264 | if name == role["name"]: |
| Eduardo Sousa | a63fb3c | 2019-03-31 23:49:14 +0100 | [diff] [blame] | 265 | return role |
| 266 | raise NotFound("Role {} not found".format(name)) |