Add command to generate super_client_info files

Change-Id: Id995295df35d62d277ebd78eb9f8ea184648799f
diff --git a/.gitignore b/.gitignore
index 84cb25a..910a8e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@
 /lib/Kalamar/Plugin/Announcements.pm
 /log
 /blib
+/super_client_info
 /script*
 /MYMETA*
 /Makefile
diff --git a/Changes b/Changes
index 50b2458..fc20a0c 100755
--- a/Changes
+++ b/Changes
@@ -1,3 +1,6 @@
+0.47 2022-11-22
+        - Add command to generate super_client_info file. (diewald)
+
 0.46 2022-08-08
         - Increased max query length from 1000 to 4096 characters. (kupietz)
         - Fix handling of public clients. (diewald)
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index 36b2524..8ba4cae 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -8,7 +8,7 @@
 use List::Util 'none';
 
 # Minor version - may be patched from package.json
-our $VERSION = '0.46';
+our $VERSION = '0.47';
 
 # Supported version of Backend API
 our $API_VERSION = '1.0';
@@ -37,6 +37,9 @@
   # Add additional plugin path
   push(@{$self->plugins->namespaces}, __PACKAGE__ . '::Plugin');
 
+  # Add additional commands
+  push(@{$self->commands->namespaces}, __PACKAGE__ . '::Command');
+
   # Set secrets for signed cookies
   my $secret_file = $self->home->rel_file('kalamar.secret.json');
 
diff --git a/lib/Kalamar/Command/super_client_info.pm b/lib/Kalamar/Command/super_client_info.pm
new file mode 100644
index 0000000..4670689
--- /dev/null
+++ b/lib/Kalamar/Command/super_client_info.pm
@@ -0,0 +1,104 @@
+package Kalamar::Command::super_client_info;
+use Mojo::Base 'Mojolicious::Command';
+
+has description => 'Generate random super_client_info file';
+has usage       => sub { shift->extract_usage };
+
+sub run {
+  my $self = shift;
+  my $client_id = shift;
+
+  # Path for super client file
+  my $path = shift || $self->rel_file('super_client_info');
+
+  my $app = $self->app;
+
+  $app->plugin(
+    'Mojolicious::Plugin::Util::RandomString',
+    entropy => 256,
+    printable => {
+      alphabet => '2345679bdfhmnprtFGHJLMNPRT',
+      length   => 20
+    }
+  );
+
+  my $secret = $app->random_string('printable');
+
+  unless ($client_id) {
+    $client_id = $app->random_string('printable');
+  };
+
+  my $c = $self->app->build_controller;
+
+  $self->write_file(
+    $path,
+    $c->render_to_string(
+      json => {
+        client_id => $client_id,
+        client_secret => $secret
+      }
+    )
+  );
+};
+
+1;
+
+=encoding utf8
+
+=head1 NAME
+
+Kalamar::Command::super_client_info - Generate random super_client_info file
+
+=head1 SYNOPSIS
+
+  Usage: APPLICATION super_client_info
+
+    kalamar super_client_info
+    kalamar super_client_info 'my-client'
+    kalamar super_client_info 'my-client' './output/super_client_info'
+
+  Options:
+    -h, --help   Show this summary of available options
+
+=head1 DESCRIPTION
+
+L<Kalamar::Command::super_client_info> generates C<super_client_info>
+files with a client ID (either random or passed) and a randomized client secret.
+
+This file can be used by Kalamar and Kustvakt (full) to have a common secret.
+
+=head1 ATTRIBUTES
+
+L<Kalamar::Command::Author::generate::super_client_info> inherits all attributes from L<Mojolicious::Command> and implements
+the following new ones.
+
+=head2 description
+
+  my $description    = $super_client_info->description;
+  $super_client_info = $super_client_info->description('Foo');
+
+Short description of this command, used for the command list.
+
+=head2 usage
+
+  my $usage          = $super_client_info->usage;
+  $super_client_info = $super_client_info->usage('Foo');
+
+Usage information for this command, used for the help screen.
+
+=head1 METHODS
+
+L<Kalamar::Command::super_client_info> inherits all methods from L<Mojolicious::Command> and implements
+the following new ones.
+
+=head2 run
+
+  $super_client_info->run(@ARGV);
+
+Run this command.
+
+=head1 SEE ALSO
+
+L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
+
+=cut
diff --git a/t/command/super_client_info.t b/t/command/super_client_info.t
new file mode 100644
index 0000000..d058aac
--- /dev/null
+++ b/t/command/super_client_info.t
@@ -0,0 +1,104 @@
+#!usr/bin/env perl
+use Mojolicious::Lite;
+use Mojolicious::Commands;
+use Data::Dumper;
+use Test::Output qw/:stdout :stderr :combined :functions/;
+use Test::More;
+use Test::Mojo;
+use Mojo::Util qw/decode encode/;
+use Mojo::JSON qw/decode_json/;
+use Mojo::File 'path';
+use File::Temp 'tempdir';
+
+my $t = Test::Mojo->new('Kalamar');
+
+my $dir = tempdir CLEANUP => 1;
+chdir $dir;
+
+my $dir_ext = tempdir CLEANUP => 1;
+
+my $cmds = $t->app->commands;
+
+like(
+  join(' ', @{$cmds->namespaces}),
+  qr!Mojolicious::Command!,
+  'Namespace is set'
+);
+
+like(
+  join(' ', @{$cmds->namespaces}),
+  qr!Kalamar::Command!,
+  'Namespace is set'
+);
+
+stdout_like(
+  sub {
+    local $ENV{HARNESS_ACTIVE} = 0;
+    $cmds->run('super-client-info');
+  },
+  qr/\[write\].*super_client_info/,
+  'Write'
+);
+
+stdout_unlike(
+  sub {
+    local $ENV{HARNESS_ACTIVE} = 0;
+    $cmds->run('super-client-info');
+  },
+  qr/\[write\].*super_client_info/,
+  'Already exists'
+);
+
+my $file = path($dir, 'super_client_info');
+
+my $out = decode_json($file->slurp);
+
+like($out->{client_id},qr!^.{20}$!);
+like($out->{client_secret},qr!^.{20}$!);
+
+unlink $file->to_string;
+
+stdout_like(
+  sub {
+    local $ENV{HARNESS_ACTIVE} = 0;
+    $cmds->run('super-client-info', 'my-client');
+  },
+  qr/\[write\].*super_client_info/,
+  'Write with client id'
+);
+
+$out = decode_json($file->slurp);
+
+like($out->{client_id}, qr!my-client$!);
+like($out->{client_secret}, qr!^.{20}$!);
+
+my $file_ext = path($dir_ext, 'super_client_fun');
+
+stdout_like(
+  sub {
+    local $ENV{HARNESS_ACTIVE} = 0;
+    $cmds->run('super-client-info', 'my-client-2', $file_ext);
+  },
+  qr/\[write\].*super_client_fun/,
+  'Error'
+);
+
+$out = decode_json($file->slurp);
+
+like($out->{client_id}, qr!my-client$!);
+like($out->{client_secret}, qr!^.{20}$!);
+
+my $secret_1 = $out->{client_secret};
+
+$out = decode_json($file_ext->slurp);
+
+like($out->{client_id}, qr!my-client-2$!);
+like($out->{client_secret}, qr!^.{20}$!);
+
+my $secret_2 = $out->{client_secret};
+
+isnt($secret_1, $secret_2, 'Generated secrets differ');
+
+
+done_testing;
+__END__