make auth tokens persistent
[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 validation import wim_account_new_schema, wim_account_edit_schema
23 from base_topic import BaseTopic, EngineException
24
25 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
26
27
28 class UserTopic(BaseTopic):
29 topic = "users"
30 topic_msg = "users"
31 schema_new = user_new_schema
32 schema_edit = user_edit_schema
33
34 def __init__(self, db, fs, msg):
35 BaseTopic.__init__(self, db, fs, msg)
36
37 @staticmethod
38 def _get_project_filter(session, write=False, show_all=True):
39 """
40 Generates a filter dictionary for querying database users.
41 Current policy is admin can show all, non admin, only its own user.
42 :param session: contains "username", if user is "admin" and the working "project_id"
43 :param write: if operation is for reading (False) or writing (True)
44 :param show_all: if True it will show public or
45 :return:
46 """
47 if session["admin"]: # allows all
48 return {}
49 else:
50 return {"username": session["username"]}
51
52 def check_conflict_on_new(self, session, indata, force=False):
53 # check username not exists
54 if self.db.get_one(self.topic, {"username": indata.get("username")}, fail_on_empty=False, fail_on_more=False):
55 raise EngineException("username '{}' exists".format(indata["username"]), HTTPStatus.CONFLICT)
56 # check projects
57 if not force:
58 for p in indata["projects"]:
59 if p == "admin":
60 continue
61 if not self.db.get_one("projects", {"_id": p}, fail_on_empty=False, fail_on_more=False):
62 raise EngineException("project '{}' does not exists".format(p), HTTPStatus.CONFLICT)
63
64 def check_conflict_on_del(self, session, _id, force=False):
65 if _id == session["username"]:
66 raise EngineException("You cannot delete your own user", http_code=HTTPStatus.CONFLICT)
67
68 @staticmethod
69 def format_on_new(content, project_id=None, make_public=False):
70 BaseTopic.format_on_new(content, make_public=False)
71 content["_id"] = content["username"]
72 salt = uuid4().hex
73 content["_admin"]["salt"] = salt
74 if content.get("password"):
75 content["password"] = sha256(content["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest()
76
77 @staticmethod
78 def format_on_edit(final_content, edit_content):
79 BaseTopic.format_on_edit(final_content, edit_content)
80 if edit_content.get("password"):
81 salt = uuid4().hex
82 final_content["_admin"]["salt"] = salt
83 final_content["password"] = sha256(edit_content["password"].encode('utf-8') +
84 salt.encode('utf-8')).hexdigest()
85
86 def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
87 if not session["admin"]:
88 raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
89 return BaseTopic.edit(self, session, _id, indata=indata, kwargs=kwargs, force=force, content=content)
90
91 def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
92 if not session["admin"]:
93 raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
94 return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers, force=force,
95 make_public=make_public)
96
97
98 class ProjectTopic(BaseTopic):
99 topic = "projects"
100 topic_msg = "projects"
101 schema_new = project_new_schema
102 schema_edit = project_edit_schema
103
104 def __init__(self, db, fs, msg):
105 BaseTopic.__init__(self, db, fs, msg)
106
107 def check_conflict_on_new(self, session, indata, force=False):
108 if not indata.get("name"):
109 raise EngineException("missing 'name'")
110 # check name not exists
111 if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False):
112 raise EngineException("name '{}' exists".format(indata["name"]), HTTPStatus.CONFLICT)
113
114 @staticmethod
115 def format_on_new(content, project_id=None, make_public=False):
116 BaseTopic.format_on_new(content, None)
117 content["_id"] = content["name"]
118
119 def check_conflict_on_del(self, session, _id, force=False):
120 if _id == session["project_id"]:
121 raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
122 if force:
123 return
124 _filter = {"projects": _id}
125 if self.db.get_list("users", _filter):
126 raise EngineException("There is some USER that contains this project", http_code=HTTPStatus.CONFLICT)
127
128 def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
129 if not session["admin"]:
130 raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
131 return BaseTopic.edit(self, session, _id, indata=indata, kwargs=kwargs, force=force, content=content)
132
133 def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
134 if not session["admin"]:
135 raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
136 return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers, force=force,
137 make_public=make_public)
138
139
140 class VimAccountTopic(BaseTopic):
141 topic = "vim_accounts"
142 topic_msg = "vim_account"
143 schema_new = vim_account_new_schema
144 schema_edit = vim_account_edit_schema
145 vim_config_encrypted = ("admin_password", "nsx_password", "vcenter_password")
146
147 def __init__(self, db, fs, msg):
148 BaseTopic.__init__(self, db, fs, msg)
149
150 def check_conflict_on_new(self, session, indata, force=False):
151 self.check_unique_name(session, indata["name"], _id=None)
152
153 def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
154 if not force and edit_content.get("name"):
155 self.check_unique_name(session, edit_content["name"], _id=_id)
156
157 # encrypt passwords
158 schema_version = final_content.get("schema_version")
159 if schema_version:
160 if edit_content.get("vim_password"):
161 final_content["vim_password"] = self.db.encrypt(edit_content["vim_password"],
162 schema_version=schema_version, salt=_id)
163 if edit_content.get("config"):
164 for p in self.vim_config_encrypted:
165 if edit_content["config"].get(p):
166 final_content["config"][p] = self.db.encrypt(edit_content["config"][p],
167 schema_version=schema_version, salt=_id)
168
169 def format_on_new(self, content, project_id=None, make_public=False):
170 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
171 content["schema_version"] = schema_version = "1.1"
172
173 # encrypt passwords
174 if content.get("vim_password"):
175 content["vim_password"] = self.db.encrypt(content["vim_password"], schema_version=schema_version,
176 salt=content["_id"])
177 if content.get("config"):
178 for p in self.vim_config_encrypted:
179 if content["config"].get(p):
180 content["config"][p] = self.db.encrypt(content["config"][p], schema_version=schema_version,
181 salt=content["_id"])
182
183 content["_admin"]["operationalState"] = "PROCESSING"
184
185 def delete(self, session, _id, force=False, dry_run=False):
186 """
187 Delete item by its internal _id
188 :param session: contains the used login username, working project, and admin rights
189 :param _id: server internal id
190 :param force: indicates if deletion must be forced in case of conflict
191 :param dry_run: make checking but do not delete
192 :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
193 """
194 # TODO add admin to filter, validate rights
195 if dry_run or force: # delete completely
196 return BaseTopic.delete(self, session, _id, force, dry_run)
197 else: # if not, sent to kafka
198 v = BaseTopic.delete(self, session, _id, force, dry_run=True)
199 self.db.set_one("vim_accounts", {"_id": _id}, {"_admin.to_delete": True}) # TODO change status
200 self._send_msg("delete", {"_id": _id})
201 return v # TODO indicate an offline operation to return 202 ACCEPTED
202
203
204 class WimAccountTopic(BaseTopic):
205 topic = "wim_accounts"
206 topic_msg = "wim_account"
207 schema_new = wim_account_new_schema
208 schema_edit = wim_account_edit_schema
209 wim_config_encrypted = ()
210
211 def __init__(self, db, fs, msg):
212 BaseTopic.__init__(self, db, fs, msg)
213
214 def check_conflict_on_new(self, session, indata, force=False):
215 self.check_unique_name(session, indata["name"], _id=None)
216
217 def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
218 if not force and edit_content.get("name"):
219 self.check_unique_name(session, edit_content["name"], _id=_id)
220
221 # encrypt passwords
222 schema_version = final_content.get("schema_version")
223 if schema_version:
224 if edit_content.get("wim_password"):
225 final_content["wim_password"] = self.db.encrypt(edit_content["wim_password"],
226 schema_version=schema_version, salt=_id)
227 if edit_content.get("config"):
228 for p in self.wim_config_encrypted:
229 if edit_content["config"].get(p):
230 final_content["config"][p] = self.db.encrypt(edit_content["config"][p],
231 schema_version=schema_version, salt=_id)
232
233 def format_on_new(self, content, project_id=None, make_public=False):
234 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
235 content["schema_version"] = schema_version = "1.1"
236
237 # encrypt passwords
238 if content.get("wim_password"):
239 content["wim_password"] = self.db.encrypt(content["wim_password"], schema_version=schema_version,
240 salt=content["_id"])
241 if content.get("config"):
242 for p in self.wim_config_encrypted:
243 if content["config"].get(p):
244 content["config"][p] = self.db.encrypt(content["config"][p], schema_version=schema_version,
245 salt=content["_id"])
246
247 content["_admin"]["operationalState"] = "PROCESSING"
248
249 def delete(self, session, _id, force=False, dry_run=False):
250 """
251 Delete item by its internal _id
252 :param session: contains the used login username, working project, and admin rights
253 :param _id: server internal id
254 :param force: indicates if deletion must be forced in case of conflict
255 :param dry_run: make checking but do not delete
256 :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
257 """
258 # TODO add admin to filter, validate rights
259 if dry_run or force: # delete completely
260 return BaseTopic.delete(self, session, _id, force, dry_run)
261 else: # if not, sent to kafka
262 v = BaseTopic.delete(self, session, _id, force, dry_run=True)
263 self.db.set_one("wim_accounts", {"_id": _id}, {"_admin.to_delete": True}) # TODO change status
264 self._send_msg("delete", {"_id": _id})
265 return v # TODO indicate an offline operation to return 202 ACCEPTED
266
267
268 class SdnTopic(BaseTopic):
269 topic = "sdns"
270 topic_msg = "sdn"
271 schema_new = sdn_new_schema
272 schema_edit = sdn_edit_schema
273
274 def __init__(self, db, fs, msg):
275 BaseTopic.__init__(self, db, fs, msg)
276
277 def check_conflict_on_new(self, session, indata, force=False):
278 self.check_unique_name(session, indata["name"], _id=None)
279
280 def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
281 if not force and edit_content.get("name"):
282 self.check_unique_name(session, edit_content["name"], _id=_id)
283
284 # encrypt passwords
285 schema_version = final_content.get("schema_version")
286 if schema_version and edit_content.get("password"):
287 final_content["password"] = self.db.encrypt(edit_content["password"], schema_version=schema_version,
288 salt=_id)
289
290 def format_on_new(self, content, project_id=None, make_public=False):
291 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
292 content["schema_version"] = schema_version = "1.1"
293 # encrypt passwords
294 if content.get("password"):
295 content["password"] = self.db.encrypt(content["password"], schema_version=schema_version,
296 salt=content["_id"])
297
298 content["_admin"]["operationalState"] = "PROCESSING"
299
300 def delete(self, session, _id, force=False, dry_run=False):
301 """
302 Delete item by its internal _id
303 :param session: contains the used login username, working project, and admin rights
304 :param _id: server internal id
305 :param force: indicates if deletion must be forced in case of conflict
306 :param dry_run: make checking but do not delete
307 :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
308 """
309 if dry_run or force: # delete completely
310 return BaseTopic.delete(self, session, _id, force, dry_run)
311 else: # if not sent to kafka
312 v = BaseTopic.delete(self, session, _id, force, dry_run=True)
313 self.db.set_one("sdns", {"_id": _id}, {"_admin.to_delete": True}) # TODO change status
314 self._send_msg("delete", {"_id": _id})
315 return v # TODO indicate an offline operation to return 202 ACCEPTED