Added seed code for access-management.
[ta/access-management.git] / src / access_management / rest-plugin / users.py
1 # Copyright 2019 Nokia
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 implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import time
16 import access_management.db.amdb as amdb
17 from am_api_base import *
18 from keystoneauth1 import exceptions
19 from cmframework.apis import cmclient
20
21
22 class Users(AMApiBase):
23
24     """
25     User create operations
26
27     .. :quickref: Users;User create operations
28
29     .. http:post:: /am/v1/users
30
31     **Start User create**
32
33     **Example request**:
34
35     .. sourcecode:: http
36
37         POST am/v1/users HTTP/1.1
38         Host: haproxyvip:61200
39         Accept: application/json
40         {
41             "username": "user_1",
42             "password": "Passwd_1",
43             "email": "test@mail.com",
44             "project": "10f8fa2c6efe409d8207517128f03265",
45             "description": "desc"
46         }
47
48     :> json string username: The created user name.
49     :> json string password: The user's password.
50     :> json string email: The user's e-mail.
51     :> json string project: ID of the project to be set as primary project for the user.
52     :> json string description: The user's description.
53
54     **Example response**:
55
56     .. sourcecode:: http
57
58         HTTP/1.1 200 OK
59         {
60             "code": 0,
61             "description": "",
62             "data":
63             {
64                 "id": <uuid>
65             }
66         }
67
68     :> json int code: the status code
69     :> json string description: the error description, present if code is non zero
70     :> json object data: a dictionary with the created user's id
71     :> json string id: The created user's id.
72
73     Users list operations
74
75     .. :quickref: Users;Users list operations
76
77     .. http:get:: /am/v1/users
78
79     **Start Users list**
80
81     **Example request**:
82
83     .. sourcecode:: http
84
85         GET am/v1/users HTTP/1.1
86         Host: haproxyvip:61200
87         Accept: application/json
88
89     **Example response**:
90
91     .. sourcecode:: http
92
93         HTTP/1.1 200 OK
94         {
95             "code": 0,
96             "description": "",
97             "data":
98             {
99                 "0edf341a27544c349b7c37bb76ab25d1":
100                 {
101                     "enabled": true,
102                     "id": "0edf341a27544c349b7c37bb76ab25d1",
103                     "name": "cinder",
104                     "password_expires_at": null
105                 },
106                 "32e8859519f94b1ea80f61d53d17e74e":
107                 {
108                     "enabled": true,
109                     "id": "32e8859519f94b1ea80f61d53d17e74e",
110                     "name": "nova",
111                     "password_expires_at": null
112                 }
113             }
114         }
115
116     :> json int code: the status code
117     :> json string description: the error description, present if code is non zero
118     :> json object data: The existing users.
119     :> json string enabled: The user's state.
120     :> json string id: The user's id.
121     :> json string name: The user's name.
122     :> json string password_expires_at: The user's password expiration date.
123
124     User delete operations
125
126     .. :quickref: Users;User delete operations
127
128     .. http:delete:: /am/v1/users
129
130     **Start User delete**
131
132     **Example request**:
133
134     .. sourcecode:: http
135
136         DELETE am/v1/users HTTP/1.1
137         Host: haproxyvip:61200
138         Accept: application/json
139         {
140             "user": <uuid> or <username>
141         }
142
143     :> json string user: The removed user's id or user name.
144
145     **Example response**:
146
147     .. sourcecode:: http
148
149         HTTP/1.1 200 OK
150         {
151             "code": 0,
152             "description": "User deleted!"
153         }
154
155     :> json int code: the status code
156     :> json string description: the error description, present if code is non zero
157     """
158
159     endpoints = ['users']
160     parser_arguments = ['username',
161                         'password',
162                         'email',
163                         'user',
164                         'project',
165                         'description']
166
167     def post(self):
168         self.logger.info("Received a user create request!")
169         args = self.parse_args()
170
171         if args["email"] is not None:
172             if re.match("^[\.a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\.[a-z]+$", args["email"]) is None:
173                 self.logger.error("E-mail validation failed!")
174                 return AMApiBase.embed_data({}, 1, "E-mail validation failed!")
175
176         if self.id_validator(args["username"]):
177             self.logger.error("{0} username is invalid, because cannot assign a valid uuid to it.".format(args["username"]))
178             return AMApiBase.embed_data({}, 1, "{0} username is invalid, because cannot assign a valid uuid to it.".format(args["username"]))
179
180         if args["project"]:
181             projectidstate = self.id_validator(args["project"])
182             if projectidstate == False:
183                 self.logger.error("Project id validation failed")
184                 return AMApiBase.embed_data({}, 1, "Project id validation failed")
185
186         if re.match("^[a-zA-Z0-9_-]+$", args["username"]) is None:
187             self.logger.error("Username validation failed!")
188             return AMApiBase.embed_data({}, 1, "Username validation failed!")
189
190         passstate = self.passwd_validator(args["password"])
191         if passstate is not None:
192             self.logger.error(passstate)
193             return AMApiBase.embed_data({}, 1, passstate)
194
195         state, result = self._create_user(args)
196         if state:
197             self.logger.info("User created!")
198             return AMApiBase.embed_data({"id": result}, 0, "")
199         else:
200             return AMApiBase.embed_data({}, 1, result)
201
202     def get(self):
203         self.logger.info("Received a user list request!")
204         user_list = {}
205         try:
206             self.keystone = self.auth_keystone()
207             u_list = self.keystone.users.list()
208         except Exception as ex:
209             self.logger.error("{0}".format(ex))
210             return False, "{0}".format(ex)
211
212         for element in u_list:
213             user_list.update({element.id : element._info})
214
215         self.logger.info("The user list response done!")
216         return AMApiBase.embed_data(user_list, 0, "User list.")
217
218     def delete(self):
219         self.logger.info("Received a user delete request!")
220         args = self.parse_args()
221
222         state, user_info = self.get_uuid_and_name(args["user"])
223         if state:
224             token_owner = self.get_uuid_from_token()
225             if user_info["id"] == token_owner:
226                 self.logger.error("The {0} user tried to delete own account!".format(user_info["id"]))
227                 return AMApiBase.embed_data({}, 1, "You cannot delete your own account!")
228
229             state, message = self._delete_user(user_info)
230
231             if state:
232                 self.logger.info("User deleted!")
233                 return AMApiBase.embed_data({}, 0, "User deleted!")
234             else:
235                 self.logger.error(message)
236                 return AMApiBase.embed_data({}, 1, message)
237         else:
238             self.logger.error(user_info)
239             return AMApiBase.embed_data({}, 1, user_info)
240
241     def _delete_user(self, user_info):
242         state, name = self._delete_user_from_db(user_info)
243         if state:
244             self.logger.info("User removed from the db!")
245             try:
246                 self.keystone.users.delete(user_info["id"])
247             except exceptions.http.NotFound as ex:
248                 self.logger.info("{0} user does not exist in the keystone!".format(user_info["name"]))
249                 return True, "Done, but this user didn't exist in the keystone!"
250             except Exception as ex:
251                 self.logger.error("{0}".format(ex))
252                 return False, "{0}".format(ex)
253             return True, "Done"
254         else:
255             return False, name
256
257     def _delete_user_from_db(self, user_info):
258         state_open, message_open = self._open_db()
259         if state_open:
260             try:
261                 roles = self.db.get_user_roles(user_info["id"])
262
263                 for role in roles:
264                     if self.db.is_chroot_role(role):
265                         self.logger.debug("This user has a chroot role.")
266                         for x in range(3):
267                             self.remove_chroot_linux_role_handling(user_info["id"], "Chroot", "cloud.chroot")
268                             time.sleep(2)
269                             if self.check_chroot_linux_state(user_info["name"], "cloud.chroot", "absent"):
270                                 self.db.delete_user(user_info["id"])
271                                 return True, user_info["name"]
272
273                     if role == "linux_user":
274                         self.logger.debug("This user has a linux_user role!")
275                         for x in range(3):
276                             self.remove_chroot_linux_role_handling(user_info["id"], "Linux", "cloud.linuxuser")
277                             time.sleep(2)
278                             if self.check_chroot_linux_state(user_info["name"], "cloud.linuxuser", "absent"):
279                                 self.db.delete_user(user_info["id"])
280                                 return True, user_info["name"]
281
282                 self.db.delete_user(user_info["id"])
283             except amdb.NotAllowedOperation:
284                 self.logger.error("Deleting service user is not allowed: {0}".format(user_info["name"]))
285                 return False, "Deleting service user is not allowed: {0}".format(user_info["name"])
286             except amdb.NotExist:
287                 self.logger.info("The {0} user does not exist!".format(user_info["name"]))
288                 return True, ""
289             except Exception as ex:
290                 self.logger.error("Internal error: {0}".format(ex))
291                 return False, "Internal error: {0}".format(ex)
292             finally:
293                 state_close, message_close = self._close_db()
294                 if not state_close:
295                     self._close_db()
296             return True, user_info["name"]
297         else:
298             return False, message_open
299
300     def _create_user(self, args):
301         roles = []
302         ks_member_roleid = self.get_role_id(defaults.KS_MEMBER_NAME)
303         if ks_member_roleid is None:
304             self.logger.error("Member user role not found!")
305             return False, "Member user role not found!"
306         else:
307             roles.append(ks_member_roleid)
308         basic_member_roleid = self.get_role_id(defaults.AM_MEMBER_NAME)
309         if basic_member_roleid is None:
310             self.logger.error("basic_member user role not found!")
311             return False, "basic_member user role not found!"
312         else:
313             roles.append(basic_member_roleid)
314
315         um_proj_id = self.get_project_id(defaults.PROJECT_NAME)
316         if um_proj_id is None:
317             self.logger.error("The user management project is not found!")
318             return False, "The user management project is not found!"
319
320         if args["email"] is None:
321             args["email"] = 'None'
322         if args["project"] is None:
323             args["project"] = um_proj_id
324
325         try:
326             c_user_out = self.keystone.users.create(name=args["username"], password=args["password"], email=args["email"], default_project=args["project"],  description=args["description"])
327         except exceptions.http.Conflict as ex:
328             self.logger.error("{0}".format(ex))
329             return False, "This user exists in the keystone!"
330         except Exception as ex:
331             self.logger.error("{0}".format(ex))
332             return False, "{0}".format(ex)
333
334         ID = c_user_out.id
335         state, message = self._add_basic_roles(um_proj_id, ID, roles)
336         if not state:
337             return False, message
338         if args["project"] != um_proj_id:
339             state, message = self._add_basic_roles(args["project"], ID, [ks_member_roleid])
340             if not state:
341                 return False, message
342         return self._create_user_in_db(ID, args)
343
344     def _add_basic_roles(self, project, ID, roles):
345         for role in roles:
346             try:
347                 self.keystone.roles.grant(role, user=ID, project=project)
348             except Exception:
349                 try:
350                     self.keystone.roles.grant(role, user=ID, project=project)
351                 except Exception as ex:
352                     self.logger.error("{0}".format(ex))
353                     self.keystone.users.delete(ID)
354                     return False, "{0}".format(ex)
355         return True, "OK"
356
357     def _create_user_in_db(self, ID, args):
358         state_open, message_open = self._open_db()
359         if state_open:
360             try:
361                 self.db.create_user(ID, args["username"])
362                 self.db.add_user_role(ID, defaults.AM_MEMBER_NAME)
363             except amdb.AlreadyExist as ex1:
364                 self.logger.error("User already exists in table!")
365                 try:
366                     self.keystone.users.delete(ID)
367                     self.db.delete_user(ID)
368                 except amdb.NotAllowedOperation as ex2:
369                     self.logger.error("Internal error: Except1: {0}, Except2: {1}".format(ex1, ex2))
370                     return False, "Except1: {0}, Except2: {1}".format(ex1, ex2)
371                 except Exception as ex3:
372                     self.logger.error("Internal error: Except1: {0}, Except2: {1}".format(ex1, ex3))
373                     return False, "Except1: {0}, Except2: {1}".format(ex1, ex3)
374                 return False, "User already exists!"
375             except Exception as ex:
376                 self.logger.error("Internal error: {0}".format(ex))
377                 try:
378                     self.keystone.users.delete(ID)
379                 except exceptions.http.NotFound as ex:
380                     self.logger.error("{0}".format(ex))
381                     return False, "This user does not exist in the keystone!"
382                 except Exception as ex:
383                     self.logger.error("{0}".format(ex))
384                     return False, "{0}".format(ex)
385                 return False, "Internal error: {0}".format(ex)
386             finally:
387                 state_close, message_close = self._close_db()
388                 if not state_close:
389                     self._close_db()
390             return True, ID
391         else:
392             return False, message_open
393
394     def remove_chroot_linux_role_handling(self, user_id, user_type, list_name):
395         cmc = cmclient.CMClient()
396         user_list = cmc.get_property(list_name)
397         user_list = json.loads(user_list)
398         self.logger.debug("{0} user list before the change: {1}".format(user_type, json.dumps(user_list)))
399         if user_list is not None:
400             self.logger.debug("The {0} user list exists!".format(user_type))
401             username, def_project = self.get_user_from_uuid(user_id)
402             self.logger.debug("User name: {0}".format(username))
403             for val in user_list:
404                 if val["name"] == username:
405                     val["public_key"] = ""
406                     val["state"] = "absent"
407                     val["remove"] = "yes"
408                     val["password"] = ""
409                     break
410             self.logger.debug("{0} user list after the change: {1}".format(user_type, json.dumps(user_list)))
411             cmc.set_property(list_name, json.dumps(user_list))