Statistics
| Branch: | Tag: | Revision:

one / src / cli / oneuser @ f7d34f2d

History | View | Annotate | Download (23.2 KB)

1
#!/usr/bin/env ruby
2

    
3
# -------------------------------------------------------------------------- #
4
# Copyright 2002-2017, OpenNebula Project, OpenNebula Systems                #
5
#                                                                            #
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
7
# not use this file except in compliance with the License. You may obtain    #
8
# a copy of the License at                                                   #
9
#                                                                            #
10
# http://www.apache.org/licenses/LICENSE-2.0                                 #
11
#                                                                            #
12
# Unless required by applicable law or agreed to in writing, software        #
13
# distributed under the License is distributed on an "AS IS" BASIS,          #
14
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
15
# See the License for the specific language governing permissions and        #
16
# limitations under the License.                                             #
17
#--------------------------------------------------------------------------- #
18

    
19
ONE_LOCATION=ENV["ONE_LOCATION"]
20

    
21
if !ONE_LOCATION
22
    RUBY_LIB_LOCATION="/usr/lib/one/ruby"
23
else
24
    RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby"
25
end
26

    
27
$: << RUBY_LIB_LOCATION
28
$: << RUBY_LIB_LOCATION+"/cli"
29

    
30
require 'command_parser'
31
require 'one_helper/oneuser_helper'
32
require 'one_helper/onequota_helper'
33

    
34
require 'uri'
35

    
36
NO_CLIENT_CMDS = [ :key, :"token-create", :login ]
37

    
38
cmd = CommandParser::CmdParser.new(ARGV) do
39
    usage "`oneuser` <command> [<args>] [<options>]"
40
    version OpenNebulaHelper::ONE_VERSION
41

    
42
    helper = OneUserHelper.new
43

    
44
    before_proc do
45
        if !NO_CLIENT_CMDS.include?(@comm_name)
46
            begin
47
                helper.set_client(options)
48
            rescue Exception => e
49
                STDERR.puts e.message
50

    
51
                if e.message != "ONE_AUTH file not present"
52
                    STDERR.puts e.backtrace
53
                end
54

    
55
                exit 1
56
            end
57
        end
58
    end
59

    
60
    ########################################################################
61
    # Global Options
62
    ########################################################################
63
    set :option, CommandParser::OPTIONS+OpenNebulaHelper::CLIENT_OPTIONS
64

    
65
    list_options = CLIHelper::OPTIONS
66
    list_options << OpenNebulaHelper::XML
67
    list_options << OpenNebulaHelper::NUMERIC
68
    list_options << OpenNebulaHelper::DESCRIBE
69

    
70
    READ_FILE={
71
        :name => "read_file",
72
        :short => "-r",
73
        :large => "--read-file",
74
        :description => "Read password from file"
75
    }
76

    
77
    SHA1={
78
        :name => "sha1",
79
        :large => "--sha1",
80
        :description => "The password will be hashed using the sha1\n"<<
81
                        " "*31<<"algorithm"
82
    }
83

    
84
    SSH={
85
        :name => "ssh",
86
        :large => "--ssh",
87
        :description => "SSH Auth system",
88
        :proc => lambda { |o, options|
89
            options[:driver] = OpenNebula::User::SSH_AUTH
90
        }
91
    }
92

    
93
    X509={
94
        :name => "x509",
95
        :large => "--x509",
96
        :description => "x509 Auth system for x509 certificates",
97
        :proc => lambda { |o, options|
98
            options[:driver] = OpenNebula::User::X509_AUTH
99
        }
100
    }
101

    
102
    X509_PROXY={
103
        :name => "x509_proxy",
104
        :large => "--x509_proxy",
105
        :description => "x509 Auth system based on x509 proxy certificates",
106
        :proc => lambda { |o, options|
107
            options[:driver] = OpenNebula::User::X509_PROXY_AUTH
108
        }
109
    }
110

    
111
    KEY={
112
        :name => "key",
113
        :short => "-k path_to_private_key_pem",
114
        :large => "--key path_to_private_key_pem",
115
        :format => String,
116
        :description => "Path to the Private Key of the User"
117
    }
118

    
119
    CERT={
120
        :name => "cert",
121
        :short => "-c path_to_user_cert_pem",
122
        :large => "--cert path_to_user_cert_pem",
123
        :format => String,
124
        :description => "Path to the Certificate of the User"
125
    }
126

    
127
    PROXY={
128
        :name => "proxy",
129
        :large => "--proxy path_to_user_proxy_pem",
130
        :format => String,
131
        :description => "Path to the user proxy certificate"
132
    }
133

    
134
    TIME={
135
        :name  => "time",
136
        :large => "--time x",
137
        :format => Integer,
138
        :description => "Token duration in seconds, defaults to 36000 (10 h). "\
139
                        "To reset the token set time to 0." \
140
                        "To generate a non-expiring token use -1"\
141
                        " (not valid for ssh and x509 tokens). "\
142
    }
143

    
144
    DRIVER={
145
        :name => "driver",
146
        :large => "--driver driver",
147
        :format => String,
148
        :description => "Driver to authenticate this user"
149
    }
150

    
151
    FORCE = {
152
        :name   => "force",
153
        :large  => "--force" ,
154
        :description => "Force one_auth file rewrite"
155
    }
156

    
157
    TOKEN = {
158
        :name => "token",
159
        :large => "--token token_hint",
160
        :format => String,
161
        :description => "The Token to be loaded."
162
    }
163

    
164
    GROUP = {
165
        :name   => "group",
166
        :large  => "--group id|name" ,
167
        :description => "Effective GID to use with this token.",
168
        :format => String,
169
        :proc   => lambda { |o, options|
170
            OpenNebulaHelper.rname_to_id(o, "GROUP")
171
        }
172
    }
173

    
174
    GROUP_CREATE = {
175
        :name   => "group",
176
        :large  => "--group id|name" ,
177
        :description => "Comma-separated list of Groups for the new User. "\
178
                        "The first Group will be the main one.",
179
        :format => String,
180
        :proc   => lambda { |o, options|
181
            gids = o.split(",").map { |g|
182
                id = OpenNebulaHelper.rname_to_id(g, "GROUP")
183

    
184
                if (id[0] == -1)
185
                    puts id[1]
186
                    exit -1
187
                end
188

    
189
                id[1]
190
            }
191

    
192
            [0, gids]
193
        }
194
    }
195

    
196
    GLOBAL = {
197
        :name   => "global",
198
        :large  => "--global" ,
199
        :description => "Find a global Token."
200
    }
201

    
202
    STDIN_PASSWORD = {
203
        :name => "stdin_password",
204
        :large => "--stdin_password",
205
        :description => "enable stdin password"
206
    }
207

    
208
    auth_options = [READ_FILE, SHA1, SSH, X509, KEY, CERT, DRIVER]
209

    
210
    create_options = auth_options.clone.unshift(GROUP_CREATE)
211

    
212
    login_options  = [SSH,
213
                      X509,
214
                      X509_PROXY,
215
                      KEY,
216
                      CERT,
217
                      PROXY,
218
                      TIME,
219
                      FORCE,
220
                      GROUP,
221
                      STDIN_PASSWORD]
222

    
223
    set_options = [TOKEN, GLOBAL]
224

    
225
    ########################################################################
226
    # Formatters for arguments
227
    ########################################################################
228
    set :format, :groupid, OpenNebulaHelper.rname_to_id_desc("GROUP") do |arg|
229
        OpenNebulaHelper.rname_to_id(arg, "GROUP")
230
    end
231

    
232
    set :format, :userid, OneUserHelper.to_id_desc do |arg|
233
        helper.to_id(arg)
234
    end
235

    
236
    set :format, :userid_list, OneUserHelper.list_to_id_desc do |arg|
237
        helper.list_to_id(arg)
238
    end
239

    
240
    set :format, :password, OneUserHelper.password_to_str_desc do |arg|
241
        OneUserHelper.password_to_str(arg, options)
242
    end
243

    
244
    ########################################################################
245
    # Commands
246
    ########################################################################
247

    
248
    create_desc = <<-EOT.unindent
249
        Creates a new User
250
        Examples:
251
          oneuser create my_user my_password
252
          oneuser create my_user -r /tmp/mypass
253
          oneuser create my_user my_password --group users,102,testers
254
          oneuser create my_user --ssh --key /tmp/id_rsa
255
          oneuser create my_user --ssh -r /tmp/public_key
256
          oneuser create my_user --x509 --cert /tmp/my_cert.pem
257
    EOT
258

    
259
    command :create, create_desc, :username, [:password, nil],
260
            :options=>create_options do
261
        if args[1]
262
            pass = args[1]
263
        else
264
            rc = helper.password(options)
265
            if rc.first == 0
266
                pass = rc[1]
267
            else
268
                exit_with_code *rc
269
            end
270
        end
271

    
272
        driver = options[:driver] || OpenNebula::User::CORE_AUTH
273

    
274
        gids = options[:group] || []
275

    
276
        helper.create_resource(options) do |user|
277
            user.allocate(args[0], pass, driver, gids)
278
        end
279
    end
280

    
281
    update_desc = <<-EOT.unindent
282
        Update the template contents. If a path is not provided the editor will
283
        be launched to modify the current content.
284
    EOT
285

    
286
    command :update, update_desc, :userid, [:file, nil],
287
    :options=>OpenNebulaHelper::APPEND do
288
        helper.perform_action(args[0],options,"modified") do |obj|
289
            if options[:append]
290
                str = OpenNebulaHelper.append_template(args[0], obj, args[1])
291
            else
292
                str = OpenNebulaHelper.update_template(args[0], obj, args[1])
293
            end
294

    
295
            helper.set_client(options)
296
            obj = helper.retrieve_resource(obj.id)
297

    
298
            obj.update(str, options[:append])
299
        end
300
    end
301

    
302
    quota_desc = <<-EOT.unindent
303
        Set the quota limits for the user. If a path is not provided the editor
304
        will be launched to modify the current quotas.
305
    EOT
306

    
307
    command :quota, quota_desc, :userid, [:file, nil] do
308
        helper.perform_action(args[0], options, "modified") do |user|
309
            rc = user.info
310

    
311
            if OpenNebula.is_error?(rc)
312
                puts rc.message
313
                exit -1
314
            end
315

    
316
            str = OneQuotaHelper.set_quota(user, args[1])
317

    
318
            helper.set_client(options)
319
            user = helper.retrieve_resource(user.id)
320

    
321
            rc  = user.set_quota(str)
322

    
323
            if OpenNebula.is_error?(rc)
324
                puts rc.message
325
                exit -1
326
            end
327
        end
328
    end
329

    
330
    batchquota_desc = <<-EOT.unindent
331
        Sets the quota limits in batch for various users. If a path is not
332
        provided the editor will be launched to create new quotas.
333
    EOT
334

    
335
    command :batchquota, batchquota_desc, [:range, :userid_list],
336
            [:file, nil] do
337
        batch_str = OneQuotaHelper.get_batch_quota(args[1])
338

    
339
	helper.set_client(options)
340
        helper.perform_actions(args[0], options, "modified") do |user|
341
            str = OneQuotaHelper.merge_quota(user, batch_str)
342

    
343
            if OpenNebula.is_error?(str)
344
                str
345
            else
346
                user.set_quota(str)
347
            end
348
        end
349
    end
350

    
351
    defaultquota_desc = <<-EOT.unindent
352
        Sets the default quota limits for the users. If a path is not provided
353
        the editor will be launched to modify the current default quotas.
354
    EOT
355

    
356
    command :defaultquota, defaultquota_desc, [:file, nil] do
357
        system = System.new(OneUserHelper.get_client(options))
358

    
359
        default_quotas = system.get_user_quotas()
360

    
361
        if OpenNebula.is_error?(default_quotas)
362
            puts default_quotas.message
363
            exit(-1)
364
        end
365

    
366
        str = OneQuotaHelper.set_quota(default_quotas, args[0], true)
367

    
368
        system = System.new(OneUserHelper.get_client(options, true))
369
        rc  = system.set_user_quotas(str)
370

    
371
        if OpenNebula.is_error?(rc)
372
            puts rc.message
373
            exit(-1)
374
        end
375

    
376
        exit 0
377
    end
378

    
379
    umask_desc = <<-EOT.unindent
380
        Changes the umask used to create the default permissions. In a similar
381
        way to the Unix umask command, the expected value is a three-digit
382
        base-8 number. Each digit is a mask that disables permissions for the
383
        owner, group and other, respectively.
384

    
385
        If mask is not given, or if it is an empty string, the umask will
386
        be unset
387
    EOT
388

    
389
    command :umask, umask_desc, [:range, :userid_list], [:mask, nil] do
390
        helper.perform_actions(args[0],options,
391
                "umask changed") do |user|
392

    
393
            rc = user.info
394

    
395
            if OpenNebula.is_error?(rc)
396
                puts rc.message
397
                exit -1
398
            end
399

    
400
            user.delete_element('/USER/TEMPLATE/UMASK')
401

    
402
            tmp_str = user.template_str
403

    
404
            if !args[1].nil? && args[1] != ""
405
                tmp_str << "\nUMASK = #{args[1]}"
406
            end
407

    
408
            user.update(tmp_str)
409
        end
410
    end
411

    
412
    login_desc = <<-EOT.unindent
413
        Alias of token-create.
414
    EOT
415

    
416
    command :login, login_desc, [:username, nil], :options=>login_options do
417
        helper.token_create(args, options)
418
    end
419

    
420
    key_desc = <<-EOT.unindent
421
        Shows a public key from a private SSH key. Use it as password
422
        for the SSH authentication mechanism.
423
    EOT
424

    
425
    command :key, key_desc, :options=>[KEY] do
426
        require 'opennebula/ssh_auth'
427

    
428
        options[:key] ||= ENV['HOME']+'/.ssh/id_rsa'
429

    
430
        begin
431
            sshauth = SshAuth.new(:private_key=>options[:key])
432
        rescue Exception => e
433
            exit_with_code -1, e.message
434
        end
435

    
436
        puts sshauth.password
437
        exit_with_code 0
438
    end
439

    
440
    delete_desc = <<-EOT.unindent
441
        Deletes the given User
442
    EOT
443

    
444
    command :delete, delete_desc, [:range, :userid_list] do
445
        helper.perform_actions(args[0], options, "deleted") do |user|
446
            user.delete
447
        end
448
    end
449

    
450
    passwd_desc = <<-EOT.unindent
451
        Changes the given User's password
452
    EOT
453

    
454
    command :passwd, passwd_desc, :userid, [:password, nil],
455
            :options=>auth_options do
456
        if args[1]
457
            pass = args[1]
458
        else
459
            rc = helper.password(options)
460
            if rc.first == 0
461
                pass = rc[1]
462
            else
463
                exit_with_code *rc
464
            end
465
        end
466

    
467
        helper.perform_action(args[0],options,"Password changed") do |user|
468
            user.passwd(pass)
469
        end
470
    end
471

    
472
    chgrp_desc = <<-EOT.unindent
473
        Changes the User's primary group
474
    EOT
475

    
476
    command :chgrp, chgrp_desc, [:range, :userid_list], :groupid do
477
        helper.perform_actions(args[0],options,"Group changed") do |user|
478
            user.chgrp(args[1].to_i)
479
        end
480
    end
481

    
482
    addgroup_desc = <<-EOT.unindent
483
        Adds the User to a secondary group
484
    EOT
485

    
486
    command :addgroup, addgroup_desc, [:range, :userid_list], :groupid do
487
        gid = args[1]
488

    
489
        helper.perform_actions(args[0],options,"group added") do |user|
490
            user.addgroup( gid )
491
        end
492
    end
493

    
494
    delgroup_desc = <<-EOT.unindent
495
        Removes the User from a secondary group
496
    EOT
497

    
498
    command :delgroup, delgroup_desc, [:range, :userid_list], :groupid do
499
        gid = args[1]
500

    
501
        helper.perform_actions(args[0],options,"group deleted") do |user|
502
            user.delgroup( gid )
503
        end
504
    end
505

    
506
    chauth_desc = <<-EOT.unindent
507
        Changes the User's auth driver and its password (optional)
508
        Examples:
509
          oneuser chauth my_user core
510
          oneuser chauth my_user core new_password
511
          oneuser chauth my_user core -r /tmp/mypass
512
          oneuser chauth my_user --ssh --key /home/oneadmin/.ssh/id_rsa
513
          oneuser chauth my_user --ssh -r /tmp/public_key
514
          oneuser chauth my_user --x509 --cert /tmp/my_cert.pem
515
    EOT
516

    
517
    command :chauth, chauth_desc, :userid, [:auth, nil], [:password, nil],
518
            :options=>auth_options do
519
        if options[:driver]
520
            driver = options[:driver]
521
        elsif args[1]
522
            driver = args[1]
523
        else
524
            exit_with_code 0, "An Auth driver should be specified"
525
        end
526

    
527
        if args[2]
528
            pass = args[2]
529
        else
530
            rc = helper.password(options)
531
            if rc.first == 0
532
                pass = rc[1]
533
            else
534
                pass = ""
535
            end
536
        end
537

    
538
        helper.perform_action(args[0],
539
                            options,
540
                            "Auth driver and password changed") do |user|
541
            user.chauth(driver, pass)
542
        end
543
    end
544

    
545
    list_desc = <<-EOT.unindent
546
        Lists Users in the pool
547
    EOT
548

    
549
    command :list, list_desc, :options=>list_options do
550
        helper.list_pool(options)
551
    end
552

    
553
    show_desc = <<-EOT.unindent
554
        Shows information for the given User
555
    EOT
556

    
557
    command :show, show_desc, [:userid, nil],
558
            :options=>OpenNebulaHelper::XML do
559
        user=args[0] || OpenNebula::User::SELF
560
        helper.show_resource(user,options)
561
    end
562

    
563
    show_desc = <<-EOT.unindent
564
        Encodes user and password to use it with ldap
565
    EOT
566

    
567
    command :encode, show_desc, :username, [:password, nil] do
568
        ar=args.compact
569

    
570
        if defined?(URI::Parser)
571
            parser=URI::Parser.new
572
        else
573
            parser=URI
574
        end
575

    
576
        puts ar.map{|a| parser.escape(a) }.join(':')
577

    
578
        0
579
    end
580

    
581
    passwdsearch_desc = <<-EOT.unindent
582
        Searches for users with a specific auth driver that has the given
583
        string in their password field
584
    EOT
585

    
586
    command :passwdsearch, passwdsearch_desc, :driver, :password,
587
            :options=>[CLIHelper::CSV_OPT, OpenNebulaHelper::XML] do
588

    
589
        options[:list] = ["ID", "NAME", "AUTH", "PASSWORD"]
590
        options[:filter] = ["AUTH=#{args[0]}", "PASSWORD=#{args[1]}"]
591

    
592
        helper.list_pool(options)
593
    end
594

    
595
    token_add_desc = <<-EOT.unindent
596
        Creates the login token for authentication. The token can be used
597
        together with any authentication driver. The token will be stored in
598
        $HOME/.one/one_auth, and can be used subsequently to authenticate with
599
        oned through API, CLI or Sunstone.
600

    
601
        If <username> is ommited, it will infer it from the ONE_AUTH file.
602

    
603
        Example, request a valid token for a generic driver (e.g. core auth, LDAP...):
604
          oneuser token-create my_user --time 3600
605

    
606
        Example, request a group spefici token (new resources will be created in that
607
        group and only resources that belong to that group will be listed):
608
          oneuser token-create my_user --group <id|group>
609

    
610
        Example, generate and set a token for SSH based authentication:
611
          oneuser token-create my_user --ssh --key /tmp/id_rsa --time 72000
612

    
613
        Example, same using X509 certificates:
614
          oneuser token-create my_user --x509 --cert /tmp/my_cert.pem
615
                                --key /tmp/my_key.pk --time 72000
616

    
617
        Example, now with a X509 proxy certificate
618
          oneuser token-create my_user --x509_proxy --proxy /tmp/my_cert.pem
619
                                --time 72000
620
    EOT
621

    
622
    command :"token-create", token_add_desc, [:username, nil],
623
            :options=>login_options do
624

    
625
        helper.token_create(args, options)
626
    end
627

    
628
    token_set_desc = <<-EOT.unindent
629
        Generates a ONE_AUTH file that contains the token.
630

    
631
        You must provide one (and only one) of the following options:
632

    
633
        --token <token>    searches for a token that starts with that string. It must be
634
                           unique
635

    
636
        --group <id|group> returns the most durable token that provides access to that
637
                           specific group.
638

    
639
        --global           returns the most durable global token (non group specific).
640

    
641
        The argument 'username' is optional, if omitted it is inferred from the ONE_AUTH
642
        file.
643

    
644
        Example, set a token:
645
          $ oneuser token-set my_user --token 1d47
646
          export ONE_AUTH=/var/lib/one/.one/<file>.token; export ONE_EGID=-1
647

    
648
        You can copy & paste the output of the command and will load the proper
649
        environment variables.
650
    EOT
651

    
652
    command :"token-set", token_set_desc, [:username, nil],
653
            :options=>login_options+set_options do
654

    
655
        username = args[0]
656

    
657
        if username
658
            if username =~ /^\d+$/
659
                exit_with_code 1, "The argument should be the username, not the ID."
660
            end
661

    
662
            helper.client = helper.get_login_client(username, options)
663
        end
664

    
665
        user = helper.retrieve_resource(OpenNebula::User::SELF)
666

    
667
        rc = user.info
668
        if OpenNebula.is_error?(rc)
669
            puts rc.message
670
            exit_with_code 1, rc.message
671
        end
672

    
673
        # Process the options
674
        if options[:token]
675
            token_hint = options[:token]
676
            group = nil
677
        elsif options[:group]
678
            token_hint = nil
679
            group = options[:group]
680
        elsif options[:global]
681
            token_hint = nil
682
            group = -1
683
        else
684
            exit_with_code 1, "One of these options must be supplied:\n" <<
685
            "[--token <token>] [--group <id|group>] [--global]"
686
        end
687

    
688
        tokens = helper.find_token(user, token_hint, group, false)
689

    
690
        if tokens.length == 0
691
            exit_with_code 1, "No valid tokens found."
692
        end
693

    
694
        if token_hint && tokens.length > 1
695
            msg = "More than one token starting with '#{token_hint}' found."
696
            exit_with_code 1, msg
697
        end
698

    
699
        token = tokens[0]["TOKEN"]
700

    
701
        egid  = user["LOGIN_TOKEN[TOKEN='#{token}']/EGID"]
702

    
703
        auth_string = "#{user['NAME']}:#{token}"
704
        auth_file   = helper.auth_file(auth_string)
705

    
706
        begin
707
            FileUtils.mkdir_p(File.dirname(auth_file))
708
        rescue Errno::EEXIST
709
        end
710

    
711
        file = File.open(auth_file, "w")
712
        file.write(auth_string)
713
        file.close
714

    
715
        File.chmod(0600, auth_file)
716

    
717
        msg ="export ONE_AUTH=" + auth_file
718
        msg << "; export ONE_EGID=#{egid}" if egid
719

    
720
        exit_with_code 0, msg
721
    end
722

    
723
    token_delete_desc = <<-EOT.unindent
724
        Expires a token and removes the associated ONE_AUTH file if present.
725
    EOT
726

    
727
    command :"token-delete", token_delete_desc, [:username, nil], :token,
728
            :options=>login_options do
729

    
730
        if args.length == 1
731
            token_hint = args[0]
732
        else
733
            username, token_hint = args
734
        end
735

    
736
        if username
737
            helper.client = helper.get_login_client(username, options)
738
        end
739

    
740
        user = helper.retrieve_resource(OpenNebula::User::SELF)
741

    
742
        rc = user.info
743
        if OpenNebula.is_error?(rc)
744
            puts rc.message
745
            exit_with_code 1, rc.message
746
        end
747

    
748
        token = helper.find_token(user, token_hint, nil, true)
749

    
750
        if token.count > 1
751
            exit_with_code 1, "More than one token starting with '#{token_hint}' found."
752
        elsif token.count == 0
753
            exit_with_code 1, "No tokens found."
754
        end
755

    
756
        token = token[0]["TOKEN"]
757

    
758
        rc = user.login(user['NAME'], token, 0)
759

    
760
        if OpenNebula.is_error?(rc)
761
            puts rc.message
762
            exit_with_code 1, rc.message
763
        else
764
            puts "Token removed."
765
        end
766

    
767
        auth_string = "#{user['NAME']}:#{token}"
768
        auth_file   = helper.auth_file(auth_string)
769

    
770
        begin
771
            File.unlink(auth_file)
772
            puts "Removing #{auth_file}"
773
        rescue Errno::ENOENT
774
        end
775

    
776
        0
777
    end
778

    
779
    token_delete_all = <<-EOT.unindent
780
        Delete all the tokens of a user. This command is intented to be executed by a
781
        user that has MANAGE permissions of the target user.
782
    EOT
783

    
784
    command :"token-delete-all", token_delete_all, :username,
785
            :options=>login_options do
786

    
787
        username = args[0]
788

    
789
        if username =~ /^\d+$/
790
            exit_with_code 1, "The argument should be the username, not the ID."
791
        end
792

    
793
        helper.perform_action(username, options, "Tokens expired") do |user|
794
            user.login(username, "", 0)
795
        end
796
    end
797
end