Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / management / commands / user-modify.py @ 45112d5a

History | View | Annotate | Download (16.3 kB)

1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import string
35

    
36
from optparse import make_option
37

    
38
from django.core import management
39
from django.db import transaction
40
from django.core.management.base import BaseCommand, CommandError
41
from django.contrib.auth.models import Group
42
from django.core.exceptions import ValidationError
43
from django.core.validators import validate_email
44

    
45
from synnefo.util import units
46
from astakos.im.models import AstakosUser, Resource
47
from astakos.im import quotas
48
from astakos.im import activation_backends
49
from ._common import (remove_user_permission, add_user_permission, is_uuid,
50
                      show_resource_value)
51

    
52
activation_backend = activation_backends.get_backend()
53

    
54

    
55
class Command(BaseCommand):
56
    args = "<user ID> (or --all)"
57
    help = "Modify a user's attributes"
58

    
59
    option_list = BaseCommand.option_list + (
60
        make_option('--all',
61
                    action='store_true',
62
                    default=False,
63
                    help=("Operate on all users. Currently only setting "
64
                          "base quota is supported in this mode. Can be "
65
                          "combined with `--exclude'.")),
66
        make_option('--exclude',
67
                    help=("If `--all' is given, exclude users given as a "
68
                          "list of uuids: uuid1,uuid2,uuid3")),
69
        make_option('--invitations',
70
                    dest='invitations',
71
                    metavar='NUM',
72
                    help="Update user's invitations"),
73
        make_option('--level',
74
                    dest='level',
75
                    metavar='NUM',
76
                    help="Update user's level"),
77
        make_option('--password',
78
                    dest='password',
79
                    metavar='PASSWORD',
80
                    help="Set user's password"),
81
        make_option('--renew-token',
82
                    action='store_true',
83
                    dest='renew_token',
84
                    default=False,
85
                    help="Renew the user's token"),
86
        make_option('--renew-password',
87
                    action='store_true',
88
                    dest='renew_password',
89
                    default=False,
90
                    help="Renew the user's password"),
91
        make_option('--set-admin',
92
                    action='store_true',
93
                    dest='admin',
94
                    default=False,
95
                    help="Give user admin rights"),
96
        make_option('--set-noadmin',
97
                    action='store_true',
98
                    dest='noadmin',
99
                    default=False,
100
                    help="Revoke user's admin rights"),
101
        make_option('--set-active',
102
                    action='store_true',
103
                    dest='active',
104
                    default=False,
105
                    help="Change user's state to active"),
106
        make_option('--set-inactive',
107
                    action='store_true',
108
                    dest='inactive',
109
                    default=False,
110
                    help="Change user's state to inactive"),
111
        make_option('--inactive-reason',
112
                    dest='inactive_reason',
113
                    help="Reason user got inactive"),
114
        make_option('--add-group',
115
                    dest='add-group',
116
                    help="Add user group"),
117
        make_option('--delete-group',
118
                    dest='delete-group',
119
                    help="Delete user group"),
120
        make_option('--add-permission',
121
                    dest='add-permission',
122
                    help="Add user permission"),
123
        make_option('--delete-permission',
124
                    dest='delete-permission',
125
                    help="Delete user permission"),
126
        make_option('--accept',
127
                    dest='accept',
128
                    action='store_true',
129
                    help="Accept user"),
130
        make_option('--verify',
131
                    dest='verify',
132
                    action='store_true',
133
                    help="Verify user email"),
134
        make_option('--reject',
135
                    dest='reject',
136
                    action='store_true',
137
                    help="Reject user"),
138
        make_option('--reject-reason',
139
                    dest='reject_reason',
140
                    help="Reason user got rejected"),
141
        make_option('--base-quota',
142
                    dest='set_base_quota',
143
                    metavar='<resource> <capacity>',
144
                    nargs=2,
145
                    help=("Set base quota for a specified resource. "
146
                          "The special value 'default' sets the user base "
147
                          "quota to the default value.")
148
                    ),
149
        make_option('-f', '--no-confirm',
150
                    action='store_true',
151
                    default=False,
152
                    dest='force',
153
                    help="Do not ask for confirmation"),
154
        make_option('--set-email',
155
                    dest='set-email',
156
                    help="Change user's email"),
157
        make_option('--delete',
158
                    dest='delete',
159
                    action='store_true',
160
                    help="Delete user"),
161
    )
162

    
163
    @transaction.commit_on_success
164
    def handle(self, *args, **options):
165
        if options['all']:
166
            if not args:
167
                return self.handle_all_users(*args, **options)
168
            else:
169
                raise CommandError("Please provide a user ID or --all")
170

    
171
        if len(args) != 1:
172
            raise CommandError("Please provide a user ID or --all")
173

    
174
        if options["exclude"] is not None:
175
            m = "Option --exclude is meaningful only combined with --all."
176
            raise CommandError(m)
177

    
178
        if args[0].isdigit():
179
            try:
180
                user = AstakosUser.objects.select_for_update().\
181
                    get(id=int(args[0]))
182
            except AstakosUser.DoesNotExist:
183
                raise CommandError("Invalid user ID")
184
        elif is_uuid(args[0]):
185
            try:
186
                user = AstakosUser.objects.get(uuid=args[0])
187
            except AstakosUser.DoesNotExist:
188
                raise CommandError("Invalid user UUID")
189
        else:
190
            raise CommandError(("Invalid user identification: "
191
                                "you should provide a valid user ID "
192
                                "or a valid user UUID"))
193

    
194
        if options.get('admin'):
195
            user.is_superuser = True
196
        elif options.get('noadmin'):
197
            user.is_superuser = False
198

    
199
        if options.get('reject'):
200
            reject_reason = options.get('reject_reason', None)
201
            res = activation_backend.handle_moderation(
202
                user,
203
                accept=False,
204
                reject_reason=reject_reason)
205
            activation_backend.send_result_notifications(res, user)
206
            if res.is_error():
207
                print "Failed to reject.", res.message
208
            else:
209
                print "Account rejected"
210

    
211
        if options.get('verify'):
212
            res = activation_backend.handle_verification(
213
                user,
214
                user.verification_code)
215
            #activation_backend.send_result_notifications(res, user)
216
            if res.is_error():
217
                print "Failed to verify.", res.message
218
            else:
219
                print "Account verified (%s)" % res.status_display()
220

    
221
        if options.get('accept'):
222
            res = activation_backend.handle_moderation(user, accept=True)
223
            activation_backend.send_result_notifications(res, user)
224
            if res.is_error():
225
                print "Failed to accept.", res.message
226
            else:
227
                print "Account accepted and activated"
228

    
229
        if options.get('active'):
230
            res = activation_backend.activate_user(user)
231
            if res.is_error():
232
                print "Failed to activate.", res.message
233
            else:
234
                print "Account %s activated" % user.username
235

    
236
        elif options.get('inactive'):
237
            res = activation_backend.deactivate_user(
238
                user,
239
                reason=options.get('inactive_reason', None))
240
            if res.is_error():
241
                print "Failed to deactivate,", res.message
242
            else:
243
                print "Account %s deactivated" % user.username
244

    
245
        invitations = options.get('invitations')
246
        if invitations is not None:
247
            user.invitations = int(invitations)
248

    
249
        groupname = options.get('add-group')
250
        if groupname is not None:
251
            try:
252
                group = Group.objects.get(name=groupname)
253
                user.groups.add(group)
254
            except Group.DoesNotExist, e:
255
                self.stdout.write(
256
                    "Group named %s does not exist\n" % groupname)
257

    
258
        groupname = options.get('delete-group')
259
        if groupname is not None:
260
            try:
261
                group = Group.objects.get(name=groupname)
262
                user.groups.remove(group)
263
            except Group.DoesNotExist, e:
264
                self.stdout.write(
265
                    "Group named %s does not exist\n" % groupname)
266

    
267
        pname = options.get('add-permission')
268
        if pname is not None:
269
            try:
270
                r, created = add_user_permission(user, pname)
271
                if created:
272
                    self.stdout.write(
273
                        'Permission: %s created successfully\n' % pname)
274
                if r > 0:
275
                    self.stdout.write(
276
                        'Permission: %s added successfully\n' % pname)
277
                elif r == 0:
278
                    self.stdout.write(
279
                        'User has already permission: %s\n' % pname)
280
            except Exception, e:
281
                raise CommandError(e)
282

    
283
        pname = options.get('delete-permission')
284
        if pname is not None and not user.has_perm(pname):
285
            try:
286
                r = remove_user_permission(user, pname)
287
                if r < 0:
288
                    self.stdout.write(
289
                        'Invalid permission codename: %s\n' % pname)
290
                elif r == 0:
291
                    self.stdout.write('User has not permission: %s\n' % pname)
292
                elif r > 0:
293
                    self.stdout.write(
294
                        'Permission: %s removed successfully\n' % pname)
295
            except Exception, e:
296
                raise CommandError(e)
297

    
298
        level = options.get('level')
299
        if level is not None:
300
            user.level = int(level)
301

    
302
        password = options.get('password')
303
        if password is not None:
304
            user.set_password(password)
305

    
306
        password = None
307
        if options['renew_password']:
308
            password = AstakosUser.objects.make_random_password()
309
            user.set_password(password)
310

    
311
        if options['renew_token']:
312
            user.renew_token()
313

    
314
        try:
315
            user.save()
316
        except ValidationError, e:
317
            raise CommandError(e)
318

    
319
        if password:
320
            self.stdout.write('User\'s new password: %s\n' % password)
321

    
322
        force = options['force']
323

    
324
        set_base_quota = options.get('set_base_quota')
325
        if set_base_quota is not None:
326
            resource, capacity = set_base_quota
327
            self.set_limits([user], resource, capacity, force)
328

    
329
        delete = options.get('delete')
330
        if delete:
331
            management.call_command('user-show', str(user.pk),
332
                                    list_quotas=True)
333
            m = "Are you sure you want to permanently delete the user " \
334
                "(yes/no) ? "
335

    
336
            self.stdout.write("\n")
337
            confirm = raw_input(m)
338
            if confirm == "yes":
339
                user.delete()
340

    
341
        # Change users email address
342
        newemail = options.get('set-email', None)
343
        if newemail is not None:
344
            newemail = newemail.strip()
345
            try:
346
                validate_email(newemail)
347
            except ValidationError:
348
                m = "Invalid email address."
349
                raise CommandError(m)
350

    
351
            if AstakosUser.objects.user_exists(newemail):
352
                m = "A user with this email address already exists."
353
                raise CommandError(m)
354

    
355
            user.email = newemail
356
            user.save()
357

    
358
    def confirm(self):
359
        self.stdout.write("Confirm? [y/N] ")
360
        response = raw_input()
361
        if string.lower(response) not in ['y', 'yes']:
362
            self.stdout.write("Aborted.\n")
363
            exit()
364

    
365
    def handle_limits_user(self, user, res, capacity, style):
366
        default_capacity = res.uplimit
367
        resource = res.name
368
        quota = user.get_resource_policy(resource)
369
        s_default = show_resource_value(default_capacity, resource, style)
370
        s_current = show_resource_value(quota.capacity, resource, style)
371
        s_capacity = (show_resource_value(capacity, resource, style)
372
                      if capacity != 'default' else capacity)
373
        self.stdout.write("user: %s (%s)\n" % (user.uuid, user.username))
374
        self.stdout.write("default capacity: %s\n" % s_default)
375
        self.stdout.write("current capacity: %s\n" % s_current)
376
        self.stdout.write("new capacity: %s\n" % s_capacity)
377
        self.confirm()
378

    
379
    def handle_limits_all(self, res, capacity, exclude, style):
380
        m = "This will set base quota for all users"
381
        app = (" except %s" % ", ".join(exclude)) if exclude else ""
382
        self.stdout.write(m+app+".\n")
383
        resource = res.name
384
        self.stdout.write("resource: %s\n" % resource)
385
        s_capacity = (show_resource_value(capacity, resource, style)
386
                      if capacity != 'default' else capacity)
387
        self.stdout.write("capacity: %s\n" % s_capacity)
388
        self.confirm()
389

    
390
    def set_limits(self, users, resource, capacity, force=False, exclude=None):
391
        try:
392
            r = Resource.objects.get(name=resource)
393
        except Resource.DoesNotExist:
394
            raise CommandError("No such resource '%s'." % resource)
395

    
396
        style = None
397
        if capacity != "default":
398
            try:
399
                capacity, style = units.parse_with_style(capacity)
400
            except:
401
                m = ("Please specify capacity as a decimal integer or "
402
                     "'default'")
403
                raise CommandError(m)
404

    
405
        if not force:
406
            if len(users) == 1:
407
                self.handle_limits_user(users[0], r, capacity, style)
408
            else:
409
                self.handle_limits_all(r, capacity, exclude, style)
410

    
411
        if capacity == "default":
412
            capacity = r.uplimit
413
        quotas.update_base_quota(users, resource, capacity)
414

    
415
    def handle_all_users(self, *args, **options):
416
        force = options["force"]
417
        exclude = options["exclude"]
418
        if exclude is not None:
419
            exclude = exclude.split(',')
420

    
421
        set_base_quota = options.get('set_base_quota')
422
        if set_base_quota is not None:
423
            users = AstakosUser.objects.accepted().select_for_update()
424
            if exclude:
425
                users = users.exclude(uuid__in=exclude)
426
            resource, capacity = set_base_quota
427
            self.set_limits(users, resource, capacity, force, exclude)