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