]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | # -*- coding: utf-8 -*- |
2 | from __future__ import absolute_import | |
3 | ||
9f95a23c | 4 | import time |
f67539c2 | 5 | from datetime import datetime |
9f95a23c | 6 | |
11fdf7f2 | 7 | import cherrypy |
f67539c2 | 8 | from ceph_argparse import CephString |
11fdf7f2 | 9 | |
11fdf7f2 | 10 | from .. import mgr |
f67539c2 TL |
11 | from ..exceptions import DashboardException, PasswordPolicyException, \ |
12 | PwdExpirationDateNotValid, UserAlreadyExists, UserDoesNotExist | |
11fdf7f2 | 13 | from ..security import Scope |
9f95a23c | 14 | from ..services.access_control import SYSTEM_ROLES, PasswordPolicy |
11fdf7f2 | 15 | from ..services.auth import JwtManager |
f67539c2 TL |
16 | from . import ApiController, BaseController, ControllerDoc, Endpoint, \ |
17 | EndpointDoc, RESTController, allow_empty_body, validate_ceph_type | |
18 | ||
19 | USER_SCHEMA = ([{ | |
20 | "username": (str, 'Username of the user'), | |
21 | "roles": ([str], 'User Roles'), | |
22 | "name": (str, 'User Name'), | |
23 | "email": (str, 'User email address'), | |
24 | "lastUpdate": (int, 'Details last updated'), | |
25 | "enabled": (bool, 'Is the user enabled?'), | |
26 | "pwdExpirationDate": (str, 'Password Expiration date'), | |
27 | "pwdUpdateRequired": (bool, 'Is Password Update Required?') | |
28 | }], '') | |
11fdf7f2 TL |
29 | |
30 | ||
9f95a23c TL |
31 | def validate_password_policy(password, username=None, old_password=None): |
32 | """ | |
33 | :param password: The password to validate. | |
34 | :param username: The name of the user (optional). | |
35 | :param old_password: The old password (optional). | |
36 | :return: Returns the password complexity credits. | |
37 | :rtype: int | |
38 | :raises DashboardException: If a password policy fails. | |
39 | """ | |
40 | pw_policy = PasswordPolicy(password, username, old_password) | |
41 | try: | |
42 | pw_policy.check_all() | |
43 | return pw_policy.complexity_credits | |
44 | except PasswordPolicyException as ex: | |
45 | raise DashboardException(msg=str(ex), | |
46 | code='password_policy_validation_failed', | |
47 | component='user') | |
48 | ||
49 | ||
11fdf7f2 | 50 | @ApiController('/user', Scope.USER) |
f67539c2 | 51 | @ControllerDoc("Display User Details", "User") |
11fdf7f2 | 52 | class User(RESTController): |
9f95a23c | 53 | |
11fdf7f2 TL |
54 | @staticmethod |
55 | def _user_to_dict(user): | |
56 | result = user.to_dict() | |
57 | del result['password'] | |
58 | return result | |
59 | ||
60 | @staticmethod | |
61 | def _get_user_roles(roles): | |
62 | all_roles = dict(mgr.ACCESS_CTRL_DB.roles) | |
63 | all_roles.update(SYSTEM_ROLES) | |
64 | try: | |
65 | return [all_roles[rolename] for rolename in roles] | |
66 | except KeyError: | |
67 | raise DashboardException(msg='Role does not exist', | |
68 | code='role_does_not_exist', | |
69 | component='user') | |
70 | ||
f67539c2 TL |
71 | @EndpointDoc("Get List Of Users", |
72 | responses={200: USER_SCHEMA}) | |
11fdf7f2 TL |
73 | def list(self): |
74 | users = mgr.ACCESS_CTRL_DB.users | |
75 | result = [User._user_to_dict(u) for _, u in users.items()] | |
76 | return result | |
77 | ||
78 | def get(self, username): | |
79 | try: | |
80 | user = mgr.ACCESS_CTRL_DB.get_user(username) | |
81 | except UserDoesNotExist: | |
82 | raise cherrypy.HTTPError(404) | |
83 | return User._user_to_dict(user) | |
84 | ||
f67539c2 | 85 | @validate_ceph_type([('username', CephString())], 'user') |
9f95a23c TL |
86 | def create(self, username=None, password=None, name=None, email=None, |
87 | roles=None, enabled=True, pwdExpirationDate=None, pwdUpdateRequired=True): | |
11fdf7f2 TL |
88 | if not username: |
89 | raise DashboardException(msg='Username is required', | |
90 | code='username_required', | |
91 | component='user') | |
92 | user_roles = None | |
93 | if roles: | |
94 | user_roles = User._get_user_roles(roles) | |
9f95a23c TL |
95 | if password: |
96 | validate_password_policy(password, username) | |
11fdf7f2 | 97 | try: |
9f95a23c TL |
98 | user = mgr.ACCESS_CTRL_DB.create_user(username, password, name, |
99 | email, enabled, pwdExpirationDate, | |
100 | pwdUpdateRequired) | |
11fdf7f2 TL |
101 | except UserAlreadyExists: |
102 | raise DashboardException(msg='Username already exists', | |
103 | code='username_already_exists', | |
104 | component='user') | |
9f95a23c TL |
105 | except PwdExpirationDateNotValid: |
106 | raise DashboardException(msg='Password expiration date must not be in ' | |
107 | 'the past', | |
108 | code='pwd_past_expiration_date', | |
109 | component='user') | |
110 | ||
11fdf7f2 TL |
111 | if user_roles: |
112 | user.set_roles(user_roles) | |
113 | mgr.ACCESS_CTRL_DB.save() | |
114 | return User._user_to_dict(user) | |
115 | ||
116 | def delete(self, username): | |
117 | session_username = JwtManager.get_username() | |
118 | if session_username == username: | |
119 | raise DashboardException(msg='Cannot delete current user', | |
120 | code='cannot_delete_current_user', | |
121 | component='user') | |
122 | try: | |
123 | mgr.ACCESS_CTRL_DB.delete_user(username) | |
124 | except UserDoesNotExist: | |
125 | raise cherrypy.HTTPError(404) | |
126 | mgr.ACCESS_CTRL_DB.save() | |
127 | ||
9f95a23c TL |
128 | def set(self, username, password=None, name=None, email=None, roles=None, |
129 | enabled=None, pwdExpirationDate=None, pwdUpdateRequired=False): | |
130 | if JwtManager.get_username() == username and enabled is False: | |
131 | raise DashboardException(msg='You are not allowed to disable your user', | |
132 | code='cannot_disable_current_user', | |
133 | component='user') | |
134 | ||
11fdf7f2 TL |
135 | try: |
136 | user = mgr.ACCESS_CTRL_DB.get_user(username) | |
137 | except UserDoesNotExist: | |
138 | raise cherrypy.HTTPError(404) | |
139 | user_roles = [] | |
140 | if roles: | |
141 | user_roles = User._get_user_roles(roles) | |
142 | if password: | |
9f95a23c | 143 | validate_password_policy(password, username) |
11fdf7f2 | 144 | user.set_password(password) |
9f95a23c TL |
145 | if pwdExpirationDate and \ |
146 | (pwdExpirationDate < int(time.mktime(datetime.utcnow().timetuple()))): | |
147 | raise DashboardException( | |
148 | msg='Password expiration date must not be in the past', | |
149 | code='pwd_past_expiration_date', component='user') | |
11fdf7f2 TL |
150 | user.name = name |
151 | user.email = email | |
9f95a23c TL |
152 | if enabled is not None: |
153 | user.enabled = enabled | |
154 | user.pwd_expiration_date = pwdExpirationDate | |
11fdf7f2 | 155 | user.set_roles(user_roles) |
9f95a23c | 156 | user.pwd_update_required = pwdUpdateRequired |
11fdf7f2 TL |
157 | mgr.ACCESS_CTRL_DB.save() |
158 | return User._user_to_dict(user) | |
9f95a23c TL |
159 | |
160 | ||
161 | @ApiController('/user') | |
f67539c2 | 162 | @ControllerDoc("Get User Password Policy Details", "UserPasswordPolicy") |
9f95a23c TL |
163 | class UserPasswordPolicy(RESTController): |
164 | ||
165 | @Endpoint('POST') | |
f91f0fd5 | 166 | @allow_empty_body |
9f95a23c TL |
167 | def validate_password(self, password, username=None, old_password=None): |
168 | """ | |
169 | Check if the password meets the password policy. | |
170 | :param password: The password to validate. | |
171 | :param username: The name of the user (optional). | |
172 | :param old_password: The old password (optional). | |
f67539c2 TL |
173 | :return: An object with properties valid, credits and valuation. |
174 | 'credits' contains the password complexity credits and | |
175 | 'valuation' the textual summary of the validation. | |
9f95a23c TL |
176 | """ |
177 | result = {'valid': False, 'credits': 0, 'valuation': None} | |
178 | try: | |
179 | result['credits'] = validate_password_policy(password, username, old_password) | |
180 | if result['credits'] < 15: | |
181 | result['valuation'] = 'Weak' | |
182 | elif result['credits'] < 20: | |
183 | result['valuation'] = 'OK' | |
184 | elif result['credits'] < 25: | |
185 | result['valuation'] = 'Strong' | |
186 | else: | |
187 | result['valuation'] = 'Very strong' | |
188 | result['valid'] = True | |
189 | except DashboardException as ex: | |
190 | result['valuation'] = str(ex) | |
191 | return result | |
192 | ||
193 | ||
194 | @ApiController('/user/{username}') | |
f67539c2 | 195 | @ControllerDoc("Change User Password", "UserChangePassword") |
9f95a23c TL |
196 | class UserChangePassword(BaseController): |
197 | ||
198 | @Endpoint('POST') | |
199 | def change_password(self, username, old_password, new_password): | |
200 | session_username = JwtManager.get_username() | |
201 | if username != session_username: | |
202 | raise DashboardException(msg='Invalid user context', | |
203 | code='invalid_user_context', | |
204 | component='user') | |
205 | try: | |
206 | user = mgr.ACCESS_CTRL_DB.get_user(session_username) | |
207 | except UserDoesNotExist: | |
208 | raise cherrypy.HTTPError(404) | |
209 | if not user.compare_password(old_password): | |
210 | raise DashboardException(msg='Invalid old password', | |
211 | code='invalid_old_password', | |
212 | component='user') | |
213 | validate_password_policy(new_password, username, old_password) | |
214 | user.set_password(new_password) | |
215 | mgr.ACCESS_CTRL_DB.save() |