adding more system tests.
[osm/NBI.git] / osm_nbi / admin_topics.py
1 # -*- coding: utf-8 -*-
2
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain 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,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12 # implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 # import logging
17 from uuid import uuid4
18 from hashlib import sha256
19 from http import HTTPStatus
20 from validation import user_new_schema, user_edit_schema, project_new_schema, project_edit_schema
21 from validation import vim_account_new_schema, vim_account_edit_schema, sdn_new_schema, sdn_edit_schema
22 from base_topic import BaseTopic, EngineException
23
24 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
25
26
27 class UserTopic(BaseTopic):
28 topic = "users"
29 topic_msg = "users"
30 schema_new = user_new_schema
31 schema_edit = user_edit_schema
32
33 def __init__(self, db, fs, msg):
34 BaseTopic.__init__(self, db, fs, msg)
35
36 @staticmethod
37 def _get_project_filter(session, write=False, show_all=True):
38 """
39 Generates a filter dictionary for querying database users.
40 Current policy is admin can show all, non admin, only its own user.
41 :param session: contains "username", if user is "admin" and the working "project_id"
42 :param write: if operation is for reading (False) or writing (True)
43 :param show_all: if True it will show public or
44 :return:
45 """
46 if session["admin"]: # allows all
47 return {}
48 else:
49 return {"username": session["username"]}
50
51 def check_conflict_on_new(self, session, indata, force=False):
52 # check username not exists
53 if self.db.get_one(self.topic, {"username": indata.get("username")}, fail_on_empty=False, fail_on_more=False):
54 raise EngineException("username '{}' exists".format(indata["username"]), HTTPStatus.CONFLICT)
55 # check projects
56 if not force:
57 for p in indata["projects"]:
58 if p == "admin":
59 continue
60 if not self.db.get_one("projects", {"_id": p}, fail_on_empty=False, fail_on_more=False):
61 raise EngineException("project '{}' does not exists".format(p), HTTPStatus.CONFLICT)
62
63 def check_conflict_on_del(self, session, _id, force=False):
64 if _id == session["username"]:
65 raise EngineException("You cannot delete your own user", http_code=HTTPStatus.CONFLICT)
66
67 @staticmethod
68 def format_on_new(content, project_id=None, make_public=False):
69 BaseTopic.format_on_new(content, make_public=False)
70 content["_id"] = content["username"]
71 salt = uuid4().hex
72 content["_admin"]["salt"] = salt
73 if content.get("password"):
74 content["password"] = sha256(content["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest()
75
76 @staticmethod
77 def format_on_edit(final_content, edit_content):
78 BaseTopic.format_on_edit(final_content, edit_content)
79 if edit_content.get("password"):
80 salt = uuid4().hex
81 final_content["_admin"]["salt"] = salt
82 final_content["password"] = sha256(edit_content["password"].encode('utf-8') +
83 salt.encode('utf-8')).hexdigest()
84
85 def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
86 if not session["admin"]:
87 raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
88 return BaseTopic.edit(self, session, _id, indata=indata, kwargs=kwargs, force=force, content=content)
89
90 def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
91 if not session["admin"]:
92 raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
93 return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers, force=force,
94 make_public=make_public)
95
96
97 class ProjectTopic(BaseTopic):
98 topic = "projects"
99 topic_msg = "projects"
100 schema_new = project_new_schema
101 schema_edit = project_edit_schema
102
103 def __init__(self, db, fs, msg):
104 BaseTopic.__init__(self, db, fs, msg)
105
106 def check_conflict_on_new(self, session, indata, force=False):
107 if not indata.get("name"):
108 raise EngineException("missing 'name'")
109 # check name not exists
110 if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False):
111 raise EngineException("name '{}' exists".format(indata["name"]), HTTPStatus.CONFLICT)
112
113 @staticmethod
114 def format_on_new(content, project_id=None, make_public=False):
115 BaseTopic.format_on_new(content, None)
116 content["_id"] = content["name"]
117
118 def check_conflict_on_del(self, session, _id, force=False):
119 if _id == session["project_id"]:
120 raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
121 if force:
122 return
123 _filter = {"projects": _id}
124 if self.db.get_list("users", _filter):
125 raise EngineException("There is some USER that contains this project", http_code=HTTPStatus.CONFLICT)
126
127 def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
128 if not session["admin"]:
129 raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
130 return BaseTopic.edit(self, session, _id, indata=indata, kwargs=kwargs, force=force, content=content)
131
132 def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
133 if not session["admin"]:
134 raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
135 return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers, force=force,
136 make_public=make_public)
137
138
139 class VimAccountTopic(BaseTopic):
140 topic = "vim_accounts"
141 topic_msg = "vim_account"
142 schema_new = vim_account_new_schema
143 schema_edit = vim_account_edit_schema
144 vim_config_encrypted = ("admin_password", "nsx_password", "vcenter_password")
145
146 def __init__(self, db, fs, msg):
147 BaseTopic.__init__(self, db, fs, msg)
148
149 def check_conflict_on_new(self, session, indata, force=False):
150 self.check_unique_name(session, indata["name"], _id=None)
151
152 def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
153 if not force and edit_content.get("name"):
154 self.check_unique_name(session, edit_content["name"], _id=_id)
155
156 # encrypt passwords
157 schema_version = final_content.get("schema_version")
158 if schema_version:
159 if edit_content.get("vim_password"):
160 final_content["vim_password"] = self.db.encrypt(edit_content["vim_password"],
161 schema_version=schema_version, salt=_id)
162 if edit_content.get("config"):
163 for p in self.vim_config_encrypted:
164 if edit_content["config"].get(p):
165 final_content["config"][p] = self.db.encrypt(edit_content["config"][p],
166 schema_version=schema_version, salt=_id)
167
168 def format_on_new(self, content, project_id=None, make_public=False):
169 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
170 content["schema_version"] = schema_version = "1.1"
171
172 # encrypt passwords
173 if content.get("vim_password"):
174 content["vim_password"] = self.db.encrypt(content["vim_password"], schema_version=schema_version,
175 salt=content["_id"])
176 if content.get("config"):
177 for p in self.vim_config_encrypted:
178 if content["config"].get(p):
179 content["config"][p] = self.db.encrypt(content["config"][p], schema_version=schema_version,
180 salt=content["_id"])
181
182 content["_admin"]["operationalState"] = "PROCESSING"
183
184 def delete(self, session, _id, force=False, dry_run=False):
185 """
186 Delete item by its internal _id
187 :param session: contains the used login username, working project, and admin rights
188 :param _id: server internal id
189 :param force: indicates if deletion must be forced in case of conflict
190 :param dry_run: make checking but do not delete
191 :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
192 """
193 # TODO add admin to filter, validate rights
194 if dry_run or force: # delete completely
195 return BaseTopic.delete(self, session, _id, force, dry_run)
196 else: # if not, sent to kafka
197 v = BaseTopic.delete(self, session, _id, force, dry_run=True)
198 self.db.set_one("vim_accounts", {"_id": _id}, {"_admin.to_delete": True}) # TODO change status
199 self._send_msg("delete", {"_id": _id})
200 return v # TODO indicate an offline operation to return 202 ACCEPTED
201
202
203 class SdnTopic(BaseTopic):
204 topic = "sdns"
205 topic_msg = "sdn"
206 schema_new = sdn_new_schema
207 schema_edit = sdn_edit_schema
208
209 def __init__(self, db, fs, msg):
210 BaseTopic.__init__(self, db, fs, msg)
211
212 def check_conflict_on_new(self, session, indata, force=False):
213 self.check_unique_name(session, indata["name"], _id=None)
214
215 def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
216 if not force and edit_content.get("name"):
217 self.check_unique_name(session, edit_content["name"], _id=_id)
218
219 # encrypt passwords
220 schema_version = final_content.get("schema_version")
221 if schema_version and edit_content.get("password"):
222 final_content["password"] = self.db.encrypt(edit_content["password"], schema_version=schema_version,
223 salt=_id)
224
225 def format_on_new(self, content, project_id=None, make_public=False):
226 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
227 content["schema_version"] = schema_version = "1.1"
228 # encrypt passwords
229 if content.get("password"):
230 content["password"] = self.db.encrypt(content["password"], schema_version=schema_version,
231 salt=content["_id"])
232
233 content["_admin"]["operationalState"] = "PROCESSING"
234
235 def delete(self, session, _id, force=False, dry_run=False):
236 """
237 Delete item by its internal _id
238 :param session: contains the used login username, working project, and admin rights
239 :param _id: server internal id
240 :param force: indicates if deletion must be forced in case of conflict
241 :param dry_run: make checking but do not delete
242 :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
243 """
244 if dry_run or force: # delete completely
245 return BaseTopic.delete(self, session, _id, force, dry_run)
246 else: # if not sent to kafka
247 v = BaseTopic.delete(self, session, _id, force, dry_run=True)
248 self.db.set_one("sdns", {"_id": _id}, {"_admin.to_delete": True}) # TODO change status
249 self._send_msg("delete", {"_id": _id})
250 return v # TODO indicate an offline operation to return 202 ACCEPTED