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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # pylint: disable=line-too-long, too-few-public-methods
18 from copy import deepcopy
19 from hostcli.helper import ListerHelper, ShowOneHelper, CommandHelper
25 RESOURCE_PREFIX = 'am/%s/' % API_VERSION
26 DEFAULTPROJECTID = 'default_project_id'
27 DOMAINID = 'domain_id'
37 PASSWORDEXP = 'password_expires_at'
40 ROLENAME = 'role_name'
43 ISSERVICE = 'is_service'
44 ISCHROOT = 'is_chroot'
45 NEWPASSWORD = 'npassword'
46 OLDPASSWORD = 'opassword'
47 PROJECTID = 'project_id'
49 RESOURCEPATH = 'res_path'
51 PERMISSIONNAME = 'permission_name'
52 PERMISSIONRES = 'resources'
58 DEFAULTPROJECTID: {'display': 'Default-Project-ID',
59 'help': 'The ID of the default project for the user.'},
60 DOMAINID: {'display': 'Domain-ID',
61 'help': 'The ID of the domain.'},
62 ENABLED: {'display': 'Enabled',
63 'help': 'Whether the user is able to log in or not.'},
64 UUID: {'display': 'User-ID',
65 'help': 'The user ID.'},
66 USER: {'display': 'User',
67 'help': 'The user ID, or user name.'},
68 USERS: {'display': 'User-IDs',
69 'help': 'List of the user IDs.'},
70 LINKS: {'display': 'Links',
71 'help': 'The links for the user resource.'},
72 USERNAME: {'help': 'The user name.'},
73 NAME: {'display': 'User-Name',
74 'help': 'The user name.'},
75 OPTIONS: {'display': 'Options',
77 PASSWORDEXP: {'display': 'Password-Expires',
78 'help': 'The date and time when the password expires. The time zone is UTC. A null value indicates that the password never expires.'},
79 PASSWORD: {'default': '',
80 'help': 'The password'},
81 EMAIL: {'display': 'E-mail',
83 ROLENAME: {'display': 'Role',
84 'help': 'The role name.'},
85 ROLEDESC: {'display': 'Description',
86 'help': 'The description of the role. It should be enclosed in apostrophes if it contains spaces'},
87 ROLES: {'display': 'Roles',
88 'help': 'The roles of the user.'},
89 ISSERVICE: {'display': 'Immutable',
90 'help': 'Whether the role is a service role. It is non-modifiable.'},
91 ISCHROOT: {'display': 'Log File Access right',
92 'help': 'Permission to use chroot file transfer.'},
93 NEWPASSWORD: {'default': '',
94 'help': 'The new password.'},
95 OLDPASSWORD: {'default': '',
96 'help': 'The old password'},
97 PROJECTID: {'help': 'The ID of the project'},
98 PROJECT: {'help': 'The ID of the project'},
99 RESOURCEPATH: {'help': 'Resource path is the corresponding REST API URL.'},
100 RESOURCEOP: {'help': 'The resource operation'},
101 PERMISSIONNAME: {'display': 'Permission-Name',
102 'help': 'Existing operations for the REST API endpoint.'},
103 PERMISSIONRES: {'display': 'Permission-Resources',
104 'help': 'Path of the REST API endpoint.'},
105 PUBSSHKEY: {'help': 'The public ssh key string itself (not a key file).'},
106 SORT: {'help': 'Comma-separated list of sort keys and directions in the form of <key>[:<asc|desc>]. The direction defaults to ascending if not specified. '
107 'Sort keys are the case sensitive column names in the command output table. For this command they are: User-ID, User-Name, Enabled and Password-Expires.'}
110 PASSWORDPOLICY_DOCSTRING = """
111 The password must have a minimum length of 8 characters (maximum is 255 characters).
112 The allowed characters are lower case letters (a-z), upper case letters (A-Z), digits (0-9), and special characters (.,:;/(){}<>~\!?@#$%^&*_=+-).
113 The password must contain at least one upper case letter, one digit and one special character.
114 The new password is always checked against a password dictionary and it cannot be the same with any of the last 12 passwords already used."""
117 def password_policy_docstring(a):
118 a.__doc__ = a.__doc__.replace("%PASSWORDPOLICY_DOCSTRING%", PASSWORDPOLICY_DOCSTRING)
122 class AmCliLister(ListerHelper):
123 """Helper class for Lister"""
124 def __init__(self, app, app_args, cmd_name=None):
125 super(AmCliLister, self).__init__(app, app_args, cmd_name)
126 self.fieldmap = deepcopy(FIELDMAP)
127 self.resource_prefix = RESOURCE_PREFIX
130 class AmCliShowOne(ShowOneHelper):
131 """Helper class for ShowOne"""
132 def __init__(self, app, app_args, cmd_name=None):
133 super(AmCliShowOne, self).__init__(app, app_args, cmd_name)
134 self.fieldmap = deepcopy(FIELDMAP)
135 self.resource_prefix = RESOURCE_PREFIX
138 class AmCliCommand(CommandHelper):
139 """Helper class for Command"""
140 def __init__(self, app, app_args, cmd_name=None):
141 super(AmCliCommand, self).__init__(app, app_args, cmd_name)
142 self.fieldmap = deepcopy(FIELDMAP)
143 self.resource_prefix = RESOURCE_PREFIX
146 @password_policy_docstring
147 class CreateNewUser(AmCliCommand):
148 """A command for creating new user in keystone.
149 The password is prompted if not given as parameter.
150 %PASSWORDPOLICY_DOCSTRING%"""
151 def __init__(self, app, app_args, cmd_name=None):
152 super(CreateNewUser, self).__init__(app, app_args, cmd_name)
154 self.operation = 'post'
155 self.endpoint = 'users'
156 self.mandatory_positional = True
157 self.positional_count = 1
158 self.arguments = [USERNAME, EMAIL, PASSWORD, PROJECT]
159 self.message = 'User created. The UUID is ##id'
161 def take_action(self, parsed_args):
163 if parsed_args.password == '':
164 password1 = getpass.getpass(prompt='Password: ')
165 password2 = getpass.getpass(prompt='Password again: ')
166 if password1 == password2:
167 parsed_args.password = password1
169 raise Exception('New passwords do not match')
170 result = self.send_receive(self.app, parsed_args)
172 self.app.stdout.write(ResetUserPassword.construct_message(self.message, result))
173 except Exception as exp:
174 self.app.stderr.write('Failed with error %s\n' % str(exp))
178 class DeleteUsers(AmCliCommand):
179 """A command for deleting one or more existing users."""
180 def __init__(self, app, app_args, cmd_name=None):
181 super(DeleteUsers, self).__init__(app, app_args, cmd_name)
182 self.operation = 'delete'
183 self.endpoint = 'users'
184 self.mandatory_positional = True
185 self.positional_count = 1
186 self.arguments = [USER]
187 self.message = 'User deleted.'
190 class ListUsers(AmCliLister):
191 """A command for listing existing users."""
192 def __init__(self, app, app_args, cmd_name=None):
193 super(ListUsers, self).__init__(app, app_args, cmd_name)
194 self.operation = 'get'
195 self.endpoint = 'users'
196 self.positional_count = 0
197 self.arguments = [SORT]
198 self.columns = [UUID, NAME, ENABLED, PASSWORDEXP]
199 self.default_sort = [NAME, 'asc']
202 @password_policy_docstring
203 class ChangeUserPassword(AmCliCommand):
204 """A command for changing the current user password (i.e. own password).
205 The old and new passwords are prompted if not given as parameter.
206 %PASSWORDPOLICY_DOCSTRING%"""
207 def __init__(self, app, app_args, cmd_name=None):
208 super(ChangeUserPassword, self).__init__(app, app_args, cmd_name)
210 self.operation = 'post'
211 self.endpoint = 'users/ownpasswords'
212 #self.mandatory_positional = False
213 self.no_positional = True
214 self.arguments = [OLDPASSWORD, NEWPASSWORD]
215 self.message = 'Your password has been changed.'
216 self.auth_required = False
218 def take_action(self, parsed_args):
220 if parsed_args.opassword == '':
221 parsed_args.opassword = getpass.getpass(prompt='Old password: ')
222 if parsed_args.npassword == '':
223 npassword1 = getpass.getpass(prompt='New password: ')
224 npassword2 = getpass.getpass(prompt='New password again: ')
225 if npassword1 == npassword2:
226 parsed_args.npassword = npassword1
228 raise Exception('New passwords do not match')
229 parsed_args.username = os.environ['OS_USERNAME']
230 self.arguments.append(USERNAME)
231 result = self.send_receive(self.app, parsed_args)
233 self.app.stdout.write(ResetUserPassword.construct_message(self.message, result))
234 except Exception as exp:
235 self.app.stderr.write('Failed with error %s\n' % str(exp))
239 @password_policy_docstring
240 class ResetUserPassword(AmCliCommand):
241 """A command for user administrators for changing other user's password.
242 Own password cannot be changed with this command.
243 Note that user management admin role is required.
244 The new password is prompted if not given as parameter.
245 %PASSWORDPOLICY_DOCSTRING%"""
246 def __init__(self, app, app_args, cmd_name=None):
247 super(ResetUserPassword, self).__init__(app, app_args, cmd_name)
249 self.operation = 'post'
250 self.endpoint = 'users/passwords'
251 self.mandatory_positional = True
252 self.positional_count = 1
253 self.arguments = [USER, NEWPASSWORD]
254 self.message = 'Password has been reset for the user.'
256 def take_action(self, parsed_args):
258 if parsed_args.npassword == '':
259 npassword1 = getpass.getpass(prompt='New password: ')
260 npassword2 = getpass.getpass(prompt='New password again: ')
261 if npassword1 == npassword2:
262 parsed_args.npassword = npassword1
264 raise Exception('New passwords do not match')
265 result = self.send_receive(self.app, parsed_args)
267 self.app.stdout.write(ResetUserPassword.construct_message(self.message, result))
268 except Exception as exp:
269 self.app.stderr.write('Failed with error %s\n' % str(exp))
273 class SetUserParameters(AmCliCommand):
274 """A command for setting user parameters."""
275 def __init__(self, app, app_args, cmd_name=None):
276 super(SetUserParameters, self).__init__(app, app_args, cmd_name)
277 self.operation = 'post'
278 self.endpoint = 'users/parameters'
279 self.mandatory_positional = True
280 self.positional_count = 1
281 self.arguments = [USER, PROJECTID, EMAIL]
282 self.message = 'Parameter of the user is changed.'
285 class ShowUserDetails(AmCliShowOne):
286 """A command for displaying the details of a user."""
287 def __init__(self, app, app_args, cmd_name=None):
288 super(ShowUserDetails, self).__init__(app, app_args, cmd_name)
289 self.operation = 'get'
290 self.endpoint = 'users/details'
291 self.mandatory_positional = True
292 self.positional_count = 1
293 self.arguments = [USER]
294 self.columns = [DEFAULTPROJECTID, DOMAINID, EMAIL, ENABLED, UUID, LINKS, NAME, OPTIONS, PASSWORDEXP, ROLES]
297 class ShowUserOwnDetails(AmCliShowOne):
298 """A command for displaying the details of a user."""
299 def __init__(self, app, app_args, cmd_name=None):
300 super(ShowUserOwnDetails, self).__init__(app, app_args, cmd_name)
301 self.operation = 'get'
302 self.endpoint = 'users/owndetails'
303 self.mandatory_positional = True
304 self.positional_count = 0
305 self.columns = [DEFAULTPROJECTID, DOMAINID, EMAIL, ENABLED, UUID, LINKS, NAME, OPTIONS, PASSWORDEXP, ROLES]
308 class AddRoleForUser(AmCliCommand):
309 """A command for adding role to a user."""
310 def __init__(self, app, app_args, cmd_name=None):
311 super(AddRoleForUser, self).__init__(app, app_args, cmd_name)
312 self.operation = 'post'
313 self.endpoint = 'users/roles'
314 self.mandatory_positional = True
315 self.positional_count = 2
316 self.arguments = [USER, ROLENAME]
317 self.message = 'Role has been added to the user.'
320 class RemoveRoleFromUser(AmCliCommand):
321 """A command for removing role from a user."""
322 def __init__(self, app, app_args, cmd_name=None):
323 super(RemoveRoleFromUser, self).__init__(app, app_args, cmd_name)
324 self.operation = 'delete'
325 self.endpoint = 'users/roles'
326 self.mandatory_positional = True
327 self.positional_count = 2
328 self.arguments = [USER, ROLENAME]
329 self.message = 'Role has been removed from the user.'
332 class LockUser(AmCliCommand):
333 """A command for locking an account."""
334 def __init__(self, app, app_args, cmd_name=None):
335 super(LockUser, self).__init__(app, app_args, cmd_name)
336 self.operation = 'post'
337 self.endpoint = 'users/locks'
338 self.mandatory_positional = True
339 self.positional_count = 1
340 self.arguments = [USER]
341 self.message = 'User has been locked.'
344 class UnlockUser(AmCliCommand):
345 """A command for enabling a locked account."""
346 def __init__(self, app, app_args, cmd_name=None):
347 super(UnlockUser, self).__init__(app, app_args, cmd_name)
348 self.operation = 'delete'
349 self.endpoint = 'users/locks'
350 self.mandatory_positional = True
351 self.positional_count = 1
352 self.arguments = [USER]
353 self.message = 'User has been enabled.'
356 class CreateNewRole(AmCliCommand):
357 """A command for creating a new role."""
358 def __init__(self, app, app_args, cmd_name=None):
359 super(CreateNewRole, self).__init__(app, app_args, cmd_name)
360 self.operation = 'post'
361 self.endpoint = 'roles'
362 self.mandatory_positional = True
363 self.positional_count = 1
364 self.arguments = [ROLENAME, ROLEDESC]
365 self.message = 'Role has been created.'
368 class ModifyRole(AmCliCommand):
369 """A command for modifying an existing role."""
370 def __init__(self, app, app_args, cmd_name=None):
371 super(ModifyRole, self).__init__(app, app_args, cmd_name)
372 self.operation = 'put'
373 self.endpoint = 'roles'
374 self.mandatory_positional = True
375 self.positional_count = 1
376 self.arguments = [ROLENAME, ROLEDESC]
377 self.message = 'Role has been modified.'
380 class DeleteRole(AmCliCommand):
381 """A command for deleting one or more existing roles."""
382 def __init__(self, app, app_args, cmd_name=None):
383 super(DeleteRole, self).__init__(app, app_args, cmd_name)
384 self.operation = 'delete'
385 self.endpoint = 'roles'
386 self.mandatory_positional = True
387 self.positional_count = 1
388 self.arguments = [ROLENAME]
389 self.message = 'Role has been deleted.'
392 class ListRoles(AmCliLister):
393 """A command for listing existing roles. Openstack roles won't be listed."""
394 def __init__(self, app, app_args, cmd_name=None):
395 super(ListRoles, self).__init__(app, app_args, cmd_name)
396 self.operation = 'get'
397 self.endpoint = 'roles'
398 self.positional_count = 0
399 self.arguments = [SORT]
400 self.columns = [ROLENAME, ROLEDESC, ISSERVICE, ISCHROOT]
401 self.default_sort = [ROLENAME, 'asc']
404 class ShowRoleDetails(AmCliLister):
405 """A command for displaying the details of a role."""
406 def __init__(self, app, app_args, cmd_name=None):
407 super(ShowRoleDetails, self).__init__(app, app_args, cmd_name)
408 self.operation = 'get'
409 self.endpoint = 'roles/details'
410 self.mandatory_positional = True
411 self.positional_count = 1
412 self.arguments = [ROLENAME]
413 self.columns = [PERMISSIONNAME, PERMISSIONRES]
416 class ListUsersOfRole(AmCliLister):
417 """A command for listing the users of a role."""
418 def __init__(self, app, app_args, cmd_name=None):
419 super(ListUsersOfRole, self).__init__(app, app_args, cmd_name)
420 self.operation = 'get'
421 self.endpoint = 'roles/users'
422 self.mandatory_positional = True
423 self.positional_count = 1
424 self.arguments = [ROLENAME]
425 self.columns = [ROLENAME, USERS]
428 class AddPermissionToRole(AmCliCommand):
429 """A command for adding a new permission to a role."""
430 def __init__(self, app, app_args, cmd_name=None):
431 super(AddPermissionToRole, self).__init__(app, app_args, cmd_name)
432 self.operation = 'post'
433 self.endpoint = 'roles/permissions'
434 self.mandatory_positional = True
435 self.positional_count = 3
436 self.arguments = [ROLENAME, RESOURCEPATH, RESOURCEOP]
437 self.message = 'New permission added to role.'
440 class RemovePermissionFromRole(AmCliCommand):
441 """A command for removing a permission from a role."""
442 def __init__(self, app, app_args, cmd_name=None):
443 super(RemovePermissionFromRole, self).__init__(app, app_args, cmd_name)
444 self.operation = 'delete'
445 self.endpoint = 'roles/permissions'
446 self.mandatory_positional = True
447 self.positional_count = 3
448 self.arguments = [ROLENAME, RESOURCEPATH, RESOURCEOP]
449 self.message = 'Permission deleted from role.'
452 class ListPermissions(AmCliLister):
453 """A command for listing all the permissions and endpoints."""
454 def __init__(self, app, app_args, cmd_name=None):
455 super(ListPermissions, self).__init__(app, app_args, cmd_name)
456 self.operation = 'get'
457 self.endpoint = 'permissions'
458 self.positional_count = 0
459 self.arguments = [SORT]
460 self.columns = [PERMISSIONNAME, PERMISSIONRES]
461 self.default_sort = [PERMISSIONNAME, 'asc']
464 class AddKey(AmCliCommand):
465 """A command for adding a public ssh key to a user."""
466 def __init__(self, app, app_args, cmd_name=None):
467 super(AddKey, self).__init__(app, app_args, cmd_name)
468 self.operation = 'post'
469 self.endpoint = 'users/keys'
470 self.mandatory_positional = True
471 self.positional_count = 2
472 self.arguments = [USER, PUBSSHKEY]
473 self.message = 'Key added to the user.'
476 class RemoveKey(AmCliCommand):
477 """A command for removing a public ssh key from a user."""
478 def __init__(self, app, app_args, cmd_name=None):
479 super(RemoveKey, self).__init__(app, app_args, cmd_name)
480 self.operation = 'delete'
481 self.endpoint = 'users/keys'
482 self.mandatory_positional = True
483 self.positional_count = 1
484 self.arguments = [USER]
485 self.message = 'Key removed from the user.'