Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (16.9 kB)

1
# Copyright 2012, 2013, 2014 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 a non-accepted 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.stderr.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.stderr.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.stderr.write(
278
                        'Permission: %s created successfully\n' % pname)
279
                if r > 0:
280
                    self.stderr.write(
281
                        'Permission: %s added successfully\n' % pname)
282
                elif r == 0:
283
                    self.stderr.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.stderr.write(
294
                        'Invalid permission codename: %s\n' % pname)
295
                elif r == 0:
296
                    self.stderr.write('User has not permission: %s\n' % pname)
297
                elif r > 0:
298
                    self.stderr.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
            if user.is_accepted():
344
                m = "Cannot delete. User %s is accepted." % user
345
                raise CommandError(m)
346
            management.call_command('user-show', str(user.pk),
347
                                    list_quotas=True)
348

    
349
            if not force:
350
                self.stdout.write("About to delete user %s. " % user.uuid)
351
                self.confirm()
352
            user.delete()
353

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

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

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

    
371
    def confirm(self):
372
        self.stdout.write("Confirm? [y/N] ")
373
        try:
374
            response = raw_input()
375
        except EOFError:
376
            response = "ABORT"
377
        if string.lower(response) not in ['y', 'yes']:
378
            self.stderr.write("Aborted.\n")
379
            exit()
380

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

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

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

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

    
421
        if not force:
422
            if len(users) == 1:
423
                self.handle_limits_user(users[0], r, capacity, style)
424
            else:
425
                self.handle_limits_all(r, capacity, exclude, style)
426

    
427
        if capacity == "default":
428
            capacity = r.uplimit
429
        quotas.update_base_quota(users, resource, capacity)
430

    
431
    def handle_all_users(self, *args, **options):
432
        force = options["force"]
433
        exclude = options["exclude"]
434
        if exclude is not None:
435
            exclude = exclude.split(',')
436

    
437
        set_base_quota = options.get('set_base_quota')
438
        if set_base_quota is not None:
439
            users = AstakosUser.objects.accepted().select_for_update()
440
            if exclude:
441
                users = users.exclude(uuid__in=exclude)
442
            resource, capacity = set_base_quota
443
            self.set_limits(users, resource, capacity, force, exclude)