Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (16.6 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
            resource, capacity = set_base_quota
336
            self.set_limits([user], resource, capacity, force)
337

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

    
345
            self.stdout.write("\n")
346
            confirm = raw_input(m)
347
            if confirm == "yes":
348
                user.delete()
349

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

    
360
            if AstakosUser.objects.user_exists(newemail):
361
                m = "A user with this email address already exists."
362
                raise CommandError(m)
363

    
364
            user.email = newemail
365
            user.save()
366

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

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

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

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

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

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

    
420
        if capacity == "default":
421
            capacity = r.uplimit
422
        quotas.update_base_quota(users, resource, capacity)
423

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

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