Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / management / commands / user-modify.py @ 733f013d

History | View | Annotate | Download (16.8 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
from datetime import datetime
36

    
37
from optparse import make_option
38

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

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

    
53
activation_backend = activation_backends.get_backend()
54

    
55

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

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

    
168
    @transaction.commit_on_success
169
    def handle(self, *args, **options):
170
        if options['all']:
171
            if not args:
172
                return self.handle_all_users(*args, **options)
173
            else:
174
                raise CommandError("Please provide a user ID or --all")
175

    
176
        if len(args) != 1:
177
            raise CommandError("Please provide a user ID or --all")
178

    
179
        if options["exclude"] is not None:
180
            m = "Option --exclude is meaningful only combined with --all."
181
            raise CommandError(m)
182

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

    
199
        if options.get('admin'):
200
            user.is_superuser = True
201
        elif options.get('noadmin'):
202
            user.is_superuser = False
203

    
204
        if options.get('reject'):
205
            reject_reason = options.get('reject_reason', None)
206
            res = activation_backend.handle_moderation(
207
                user,
208
                accept=False,
209
                reject_reason=reject_reason)
210
            activation_backend.send_result_notifications(res, user)
211
            if res.is_error():
212
                print "Failed to reject.", res.message
213
            else:
214
                print "Account rejected"
215

    
216
        if options.get('verify'):
217
            res = activation_backend.handle_verification(
218
                user,
219
                user.verification_code)
220
            #activation_backend.send_result_notifications(res, user)
221
            if res.is_error():
222
                print "Failed to verify.", res.message
223
            else:
224
                print "Account verified (%s)" % res.status_display()
225

    
226
        if options.get('accept'):
227
            res = activation_backend.handle_moderation(user, accept=True)
228
            activation_backend.send_result_notifications(res, user)
229
            if res.is_error():
230
                print "Failed to accept.", res.message
231
            else:
232
                print "Account accepted and activated"
233

    
234
        if options.get('active'):
235
            res = activation_backend.activate_user(user)
236
            if res.is_error():
237
                print "Failed to activate.", res.message
238
            else:
239
                print "Account %s activated" % user.username
240

    
241
        elif options.get('inactive'):
242
            res = activation_backend.deactivate_user(
243
                user,
244
                reason=options.get('inactive_reason', None))
245
            if res.is_error():
246
                print "Failed to deactivate,", res.message
247
            else:
248
                print "Account %s deactivated" % user.username
249

    
250
        invitations = options.get('invitations')
251
        if invitations is not None:
252
            user.invitations = int(invitations)
253

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

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

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

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

    
303
        level = options.get('level')
304
        if level is not None:
305
            user.level = int(level)
306

    
307
        password = options.get('password')
308
        if password is not None:
309
            user.set_password(password)
310

    
311
        password = None
312
        if options['renew_password']:
313
            password = AstakosUser.objects.make_random_password()
314
            user.set_password(password)
315

    
316
        if options['renew_token']:
317
            user.renew_token()
318

    
319
        if options['sign_terms']:
320
            user.has_signed_terms = True
321
            user.date_signed_terms = datetime.now()
322

    
323
        try:
324
            user.save()
325
        except ValidationError, e:
326
            raise CommandError(e)
327

    
328
        if password:
329
            self.stdout.write('User\'s new password: %s\n' % password)
330

    
331
        force = options['force']
332

    
333
        set_base_quota = options.get('set_base_quota')
334
        if set_base_quota is not None:
335
            if not user.is_accepted():
336
                m = "%s is not an accepted user." % user
337
                raise CommandError(m)
338
            resource, capacity = set_base_quota
339
            self.set_limits([user], resource, capacity, force)
340

    
341
        delete = options.get('delete')
342
        if delete:
343
            management.call_command('user-show', str(user.pk),
344
                                    list_quotas=True)
345
            m = "Are you sure you want to permanently delete the user " \
346
                "(yes/no) ? "
347

    
348
            self.stdout.write("\n")
349
            confirm = raw_input(m)
350
            if confirm == "yes":
351
                user.delete()
352

    
353
        # Change users email address
354
        newemail = options.get('set-email', None)
355
        if newemail is not None:
356
            newemail = newemail.strip()
357
            try:
358
                validate_email(newemail)
359
            except ValidationError:
360
                m = "Invalid email address."
361
                raise CommandError(m)
362

    
363
            if AstakosUser.objects.user_exists(newemail):
364
                m = "A user with this email address already exists."
365
                raise CommandError(m)
366

    
367
            user.set_email(newemail)
368
            user.save()
369

    
370
    def confirm(self):
371
        self.stdout.write("Confirm? [y/N] ")
372
        response = raw_input()
373
        if string.lower(response) not in ['y', 'yes']:
374
            self.stdout.write("Aborted.\n")
375
            exit()
376

    
377
    def handle_limits_user(self, user, res, capacity, style):
378
        default_capacity = res.uplimit
379
        resource = res.name
380
        quota = user.get_resource_policy(resource)
381
        s_default = show_resource_value(default_capacity, resource, style)
382
        s_current = show_resource_value(quota.capacity, resource, style)
383
        s_capacity = (show_resource_value(capacity, resource, style)
384
                      if capacity != 'default' else capacity)
385
        self.stdout.write("user: %s (%s)\n" % (user.uuid, user.username))
386
        self.stdout.write("default capacity: %s\n" % s_default)
387
        self.stdout.write("current capacity: %s\n" % s_current)
388
        self.stdout.write("new capacity: %s\n" % s_capacity)
389
        self.confirm()
390

    
391
    def handle_limits_all(self, res, capacity, exclude, style):
392
        m = "This will set base quota for all users"
393
        app = (" except %s" % ", ".join(exclude)) if exclude else ""
394
        self.stdout.write(m+app+".\n")
395
        resource = res.name
396
        self.stdout.write("resource: %s\n" % resource)
397
        s_capacity = (show_resource_value(capacity, resource, style)
398
                      if capacity != 'default' else capacity)
399
        self.stdout.write("capacity: %s\n" % s_capacity)
400
        self.confirm()
401

    
402
    def set_limits(self, users, resource, capacity, force=False, exclude=None):
403
        try:
404
            r = Resource.objects.get(name=resource)
405
        except Resource.DoesNotExist:
406
            raise CommandError("No such resource '%s'." % resource)
407

    
408
        style = None
409
        if capacity != "default":
410
            try:
411
                capacity, style = units.parse_with_style(capacity)
412
            except:
413
                m = ("Please specify capacity as a decimal integer or "
414
                     "'default'")
415
                raise CommandError(m)
416

    
417
        if not force:
418
            if len(users) == 1:
419
                self.handle_limits_user(users[0], r, capacity, style)
420
            else:
421
                self.handle_limits_all(r, capacity, exclude, style)
422

    
423
        if capacity == "default":
424
            capacity = r.uplimit
425
        quotas.update_base_quota(users, resource, capacity)
426

    
427
    def handle_all_users(self, *args, **options):
428
        force = options["force"]
429
        exclude = options["exclude"]
430
        if exclude is not None:
431
            exclude = exclude.split(',')
432

    
433
        set_base_quota = options.get('set_base_quota')
434
        if set_base_quota is not None:
435
            users = AstakosUser.objects.accepted().select_for_update()
436
            if exclude:
437
                users = users.exclude(uuid__in=exclude)
438
            resource, capacity = set_base_quota
439
            self.set_limits(users, resource, capacity, force, exclude)