#  Copyright (c) 1997-2021
#  Ewgenij Gawrilow, Michael Joswig, and the polymake team
#  Technische Universität Berlin, Germany
#  https://polymake.org
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the
#  Free Software Foundation; either version 2, or (at your option) any
#  later version: http://www.gnu.org/licenses/gpl.txt.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#-------------------------------------------------------------------------------
#
#  This file is part of the polymake database interface polyDB.
#
#   @author Silke Horn, Andreas Paffenholz
#   http://www.mathematik.tu-darmstadt.de/~paffenholz
#

package PolyDB::Client;

# @category Database Access
user_method change_password($) {
   my ($self, $pwd) = @_;

   my $db = try {
      $self->SUPER::get_database($default::db_auth_db);
   } catch {
      die_neatly($_);
   };
   my $command = [
         updateUser => $self->username,
         pwd => $pwd,
         mechanisms => [ "SCRAM-SHA-1", "SCRAM-SHA-256" ]
   ];

   try {
      $db->run_command($command);
   } catch {
      die_neatly($_);
   };

}

# @category Database Administration
# options for creating a user
options %create_user_options = (
   # String user the username
   user => undef, 
   # String password the passowrd
   password => undef,
   #Bool public_read_access whether the new user should have read access to all public collections, default true
   public_read_access => true, 
   # ARRAY collections a list of collections the user should have read access to
   collections => [],
   # ARRAY admin_collections a list of collections the user should have read and write access to
   admin_collections => []
);


# @category Database Administration
# create a new user
# @options %create_user_options
user_method create_user (%create_user_options) {
   my ($self, $options) = @_;

   if ( !$options->{user} || !$options->{password} ) {
      croak("user and passowrd must be given\n");
   }
   croak "user already exists\n" if $self->user_exists($options->{user});

   my $roles = [ "changeOwnAccount" ];
   if ( $options->{public_read_access}) {
      push @$roles, { role => 'polymakeUser', db => $PolyDB::default::db_auth_db };
   }
   
   foreach my $col (@{$options->{collections}}) {
      if ($col !~ /\./) {
         $col = disambiguate_collection_name($self, $col);
      }
      push @$roles, { role => $col, db => $PolyDB::default::db_auth_db };
   }

   foreach my $col (@{$options->{admin_collections}}) {
      if ($col !~ /\./) {
         $col = disambiguate_collection_name($self, $col);
      }
      push @$roles, { role => $col.".admin", db => $PolyDB::default::db_auth_db };
   }

   my $command = [
      createUser   => $options->{user},
      pwd => $options->{password},
      roles => $roles
   ];

   my $db = try {
      $self->SUPER::get_database($default::db_auth_db);
   } catch {
      die_neatly($_);
   };
   
   try {
      $db->run_command($command);
   } catch {
      die_neatly($_);
   };

   return $options->{user};
}

# @category Database Administration
# add a user to a collection
# @option String user the username
# @option String collection the collection
# @option Bool admin whether the user should get write access, default false
user_method add_user_to_collection( { user=>undef, collection=>$default::db_collection_name, admin=>false } ) {
   my ($self,$options) = @_;

   croak "username must be given\n" if !$options->{user};
   my $collection = $options->{collection};
   croak "no collection name specified and PolyDB::default::db_collection_name not set\n" if !$collection;
   if ($collection !~ /\./) {
      $collection = disambiguate_collection_name($self, $collection);
   }

   my $db = try {
      $self->SUPER::get_database($default::db_auth_db);
   } catch {
      die_neatly($_);
   };

   my $roles = [ $collection ];
   push @$roles, $collection.".admin" if $options->{admin};
   my $command = [
      grantRolesToUser => $options->{user},
      roles => $roles
   ];
   
   try {
      $db->run_command($command);
   } catch {
      die_neatly($_);
   };

   return [$options->{user}, $collection];
}

# @category Database Administration
# remove a user to a collection
# note: access to public collections is not removed by this
# @option String user the username
# @option String collection the collection
user_method remove_user_from_collection({user => undef, collection => $default::db_collection_name}) {
   my ($self,$options) = @_;

   die "user and collection must be given\n" if !(defined($options->{user}) && defined($options->{collection}));
   my $collection = $options->{collection};
   if ($collection !~ /\./) {
      $collection = disambiguate_collection_name($self, $collection);
   }

   my $db = try {
      $self->SUPER::get_database($default::db_auth_db);
   } catch {
      die_neatly($_);
   };

   my $roles = [ $collection, $collection.".admin" ];
   my $command = [
      revokeRolesFromUser   => $options->{user},
      roles => $roles
   ];

   try {
      $db->run_command($command);
   } catch {
      die_neatly($_);
   };
}

# @category Database Administration
# delete a user
# @param String user the username
user_method delete_user($) {
   my ($self,$user) = @_;

   return if !$self->user_exists($user);

   my $db = try {
      $self->SUPER::get_database($default::db_auth_db);
   } catch {
      die_neatly($_);
   };

   my $command = [
      dropUser   => $user,
   ];

   $db->run_command($command);
   return $user;
}

# @category Database Administration
# create a database administrator
# @option String user the username
# @option String password the password
user_method create_database_admin($, $ ) {
   my ($self, $user, $password) = @_;

   croak("User already exists\n") if $self->user_exists($user);
   my $db = try {
      $self->SUPER::get_database($default::db_auth_db);
   } catch {
      die_neatly($_);
   };

   my $command = [
      createUser   => $user,
      pwd => $password,
      roles => [ 
         { db => $PolyDB::default::db_auth_db, role => "userAdmin" }, 
         { db => $PolyDB::default::db_name, role => "readWrite" } 
      ]
   ];

   $db->run_command($command);
   return $user;
}


# @category Database Administration
# get all users
# @param ARRAY usernames list of usernames
# return HASH
user_method get_users({ usernames=>undef, with_permissions=>false } ) {
   my ($self, $options ) = @_;

   my $db = try {
      $self->SUPER::get_database($default::db_auth_db);
   } catch {
      die_neatly($_);
   };

   my $command;
   my $user_query;
   if ( defined( $options->{usernames} ) ) {
      my $users = [];
      foreach my $user (@$usernames) {
         push @$user_query, ( user=>$user, db=>$PolyDB::default::db_auth_db);
      }
      $command = [
         usersInfo => $user_query
      ];   
   } else {
      $command = [
         usersInfo => 1
      ];
   }

   my $res = $db->run_command($command);

   my $users;
   foreach my $u (@{$res->{users}}) {
      my $user = { canChangePassword => false, hasPublicAccess => false, collections=>{}, admin_collections => {}, admin_roles => {} };
      foreach my $role (@{$u->{roles}} ) {
         if ( $role->{role} eq "polymakeUser" ) {
            $user->{hasPublicAccess} = true;
            next;
         }
         if ( $role->{role} eq "changeOwnAccount" ) {
            $user->{canChangePassword} = true;
            next;
         }
         if ( $role->{role} eq "changeOwnAccount" ) {
            $user->{canChangePassword} = true;
            next;
         }
         if ( $role->{role} =~ /[\w]+\.[\w.]+\.admin$/ ) {
            (my $collection = $role->{role}) =~ s/.admin$//;
            $user->{admin_collections}->{$collection} = true;
            next;
         }
         if ( $role->{role} =~ /[\w]+\.[\w.]+$/ ) {
            $user->{collections}->{$role->{role}} = true;
            next;
         }
         foreach my $r (keys %{$user->{collections}}) {
            if ( exists($user->{admin_collections}->{$r}) ) {
               delete($usrt->{collections}->{$r});
            }
         }
         $user->{admin_roles}->{$role->{role}} = true;
      }
      $users->{$u->{user}} = $user;
   }

   return $users;
}

# Local Variables:
# mode: perl
# cperl-indent-level:3
# indent-tabs-mode:nil
# End:
