#! /usr/bin/perl -w
#
# Example of plugin module
# This is the API part of UsersPluginLDAPAll plugin - configuration of
# all user/group LDAP attributes
#

package UsersPluginLDAPAll;

use strict;

use YaST::YCP qw(:LOGGING);
use YaPI;

textdomain("users");

our %TYPEINFO;

##--------------------------------------
##--------------------- global imports

YaST::YCP::Import ("SCR");
YaST::YCP::Import ("Ldap");

##--------------------------------------
##--------------------- global variables

# default object classes of LDAP users
my @user_object_class                  =
    ("top","posixaccount","shadowaccount", "inetorgperson");

# default object classes of LDAP groups
my @group_object_class                 =
    ( "top", "posixgroup", "groupofnames");

# object classes of LDAP groups using uniqmember attribute
my @unique_group_object_class          =
    ( "top", "posixgroup", "groupofuniquenames");

# error message, returned when some plugin function fails
my $error	= "";

##--------------------------------------

# All functions have 2 "any" parameters: this will probably mean
# 1st: configuration map (hash) - e.g. saying if we work with user or group
# 2nd: data map (hash) of user (group) to work with

# in 'config' map there is a info of this type:
# "what"		=> "user" / "group"
# "modified"		=> "added"/"edited"/"deleted"
# "enabled"		=> 1/ key not present
# "disabled"		=> 1/ key not present
# "plugins_to_remove"	=> list of plugins which has to be removed 

# 'data' map contains the atrtributes of the user. It could also contain
# some keys, which Users module uses internaly (like 'groupname' for name of
# user's default group). Just ignore these values
    
##------------------------------------


# return names of provided functions
BEGIN { $TYPEINFO{Interface} = ["function", ["list", "string"], "any", "any"];}
sub Interface {

    my $self		= shift;
    my @interface 	= (
	    "GUIClient",
	    "Check",
	    "Name",
	    "Summary",
	    "Restriction",
	    "WriteBefore",
	    "Write",
	    "AddBefore",
	    "Add",
	    "EditBefore",
	    "Edit",
	    "Interface",
	    "Disable",
	    "Enable",
	    "PluginPresent",
	    "PluginRemovable",
#	    "InternalAttributes",
	    "Error",
    );
    return \@interface;
}

# return error message, generated by plugin
BEGIN { $TYPEINFO{Error} = ["function", "string", "any", "any"];}
sub Error {

    my $self		= shift;
    return $error;
}


# return plugin name, used for GUI (translated)
BEGIN { $TYPEINFO{Name} = ["function", "string", "any", "any"];}
sub Name {

    my $self		= shift;
    # plugin name
    return __("LDAP Attributes");
}

##------------------------------------
# return plugin summary (to be shown in table with all plugins)
BEGIN { $TYPEINFO{Summary} = ["function", "string", "any", "any"];}
sub Summary {

    my $self	= shift;
    my $what	= "user";
    # summary
    my $ret 	= __("Edit Remaining Attributes of LDAP User");

    if (defined $_[0]->{"what"} && $_[0]->{"what"} eq "group") {
	$ret 	= __("Edit Remaining Attributes of LDAP Group");
    }
    return $ret;
}

##------------------------------------
# return plugin internal attributes (which shouldn't be shown to user)
BEGIN { $TYPEINFO{InternalAttributes} = ["function",
    [ "list", "string" ], "any", "any"];
}
sub InternalAttributes {

    my $self	= shift;
    my @ret 	= ();

    if (defined $_[0]->{"what"} && $_[0]->{"what"} eq "group") {
	@ret 	= ();
    }
    return \@ret;
}

##------------------------------------
# checks the current data map of user/group (2nd parameter) and returns
# true if given user (group) has our plugin
BEGIN { $TYPEINFO{PluginPresent} = ["function", "boolean", "any", "any"];}
sub PluginPresent {

    my $self	= shift;

    # Yes, all LDAP users/groups have this plugin as default
    # (and this plugin is used only for LDAP objects, see Restriction function)
    return YaST::YCP::Boolean (1);
}

##------------------------------------
# Is it possible to remove this plugin from user/group?
BEGIN { $TYPEINFO{PluginRemovable} = ["function", "boolean", "any", "any"];}
sub PluginRemovable {
    # No, this plugin must be present for all LDAP objects
    return YaST::YCP::Boolean (0);
}


##------------------------------------
# return name of YCP client defining YCP GUI
BEGIN { $TYPEINFO{GUIClient} = ["function", "string", "any", "any"];}
sub GUIClient {

    my $self	= shift;
    return "users_plugin_ldap_all";
}

##------------------------------------
# Type of objects this plugin is restricted to.
# It defines:
#	1. type of objects which it should be applied to (ldap/nis/local/system)
#	2. type of objects at all (user/group)
# If this function doesn't exist, plugin is applied for all users of all types
BEGIN { $TYPEINFO{Restriction} = ["function",
    ["map", "string", "any"], "any", "any"];}
sub Restriction {

    my $self	= shift;
    return {
	    # This plugin applies only for LDAP entries,
	    "ldap"	=> 1,
	    # both for users and groups:
	    "user"	=> 1,
	    "group"	=> 1
    };
}


##------------------------------------
# check if all required atributes of LDAP entry are present
# parameter is (whole) map of entry (user/group)
# return error message
BEGIN { $TYPEINFO{Check} = ["function",
    "string",
    "any",
    "any"];
}
sub Check {

    my $self	= shift;
    my $config	= $_[0];
    my $data	= $_[1];
    
    # attribute conversion
    my @required_attrs		= ();
    my @object_classes		= ();
    if (defined $data->{"objectclass"} && ref ($data->{"objectclass"}) eq "ARRAY") {
	@object_classes		= @{$data->{"objectclass"}};
    }

    # get the attributes required for entry's object classes
    foreach my $class (@object_classes) {
	my $req = Ldap->GetRequiredAttributes ($class);
	if (defined $req && ref ($req) eq "ARRAY") {
	    foreach my $r (@{$req}) {
		if (!contains (\@required_attrs, $r, 1)) {
		    push @required_attrs, $r;
		}
	    }
	}
    }
    my $action		= $data->{"what"} || "";
    # check the presence of required attributes
    foreach my $req (@required_attrs) {
	my $attr	= lc ($req);
	my $val		= $data->{$attr};
	if (substr ($action, 0, 5) eq "edit_" && !defined $val) {
	    # when editing using YaPI, attribute dosn't have to be loaded
	    next;
	}
	if (!defined $val || $val eq "" || 
	    (ref ($val) eq "ARRAY" && 
		((@{$val} == 0) || (@{$val} == 1 && $val->[0] eq "")))) {
	    # error popup (user forgot to fill in some attributes)
	    return sprintf (__("The attribute '%s' is required for this object according
to its LDAP configuration, but it is currently empty."), $attr);
	}
    }
    return "";
}

# this will be called from Users::EnableUser
BEGIN { $TYPEINFO{Enable} = ["function",
    ["map", "string", "any"],
    "any", "any"];
}
sub Enable {

    my $self	= shift;
    my $config	= $_[0];
    my $data	= $_[1];

    my $pw	= $data->{"userpassword"} || "";
    if ($pw eq "x") { $pw	= ""; } # do not allow "!x"
    
    if ($pw =~ m/^\!/) {
	$pw	=~ s/^\!//;
	$data->{"userpassword"}	= $pw;
    }
    $data->{"shadowexpire"}	= "";
#FIXME how to set shadowexpire on Enable??? UsersLDAP->GetDefaultShadow???
    y2debug ("Enable LDAPAll called");
    return $data;
}

# this will be called from Users::DisableUser
#    	set "shadowExpire" to "0",
#	set a "!" before the hash-value in the "userpassword"
BEGIN { $TYPEINFO{Disable} = ["function",
    ["map", "string", "any"],
    "any", "any"];
}
sub Disable {

    my $self	= shift;
    my $config	= $_[0];
    my $data	= $_[1];

    my $pw	= $data->{"userpassword"} || "";
    if ($pw eq "x") { $pw	= ""; } # do not allow "!x"
    
    if ($pw !~ m/^\!/) {
	$data->{"userpassword"}	= "!".$pw;
    }
    $data->{"shadowexpire"}	= 0;
    y2debug ("Disable LDAPAll called");
    return $data;
}


# internal function:
# check if given key (second parameter) is contained in a list (1st parameter)
# if 3rd parameter is true (>0), ignore case
sub contains {
    my ( $list, $key, $ignorecase ) = @_;
    if ( $ignorecase ) {
        if ( grep /^$key$/i, @{$list} ) {
            return 1;
        }
    } else {
        if ( grep /^$key$/, @{$list} ) {
            return 1;
        }
    }
    return 0;
}

# update the list of current object classes
sub update_object_classes {

    my $config	= $_[0];
    my $data	= $_[1];

    # define the object class for new user/groupa
    my @orig_object_class	= ();
    if (defined $data->{"objectclass"} && ref $data->{"objectclass"} eq "ARRAY")
    {
	@orig_object_class	= @{$data->{"objectclass"}};
    }
    my @ocs			= @user_object_class;
    if (($config->{"what"} || "") eq "group") {
	if (lc (Ldap->member_attribute ()) eq "uniquemember") {
	    @ocs		= @unique_group_object_class;
	}
	else {
	    @ocs		= @group_object_class;
	}
    }
    foreach my $oc (@ocs) {
	if (!contains (\@orig_object_class, $oc, 1)) {
	    push @orig_object_class, $oc;
	}
    }

    $data->{"objectclass"}	= \@orig_object_class;

    return $data;
}

# this will be called at the beggining of Users::Add
# Could be called multiple times for one user/group!
BEGIN { $TYPEINFO{AddBefore} = ["function",
    ["map", "string", "any"],
    "any", "any"];
}
sub AddBefore {

    my $self	= shift;
    my $config	= $_[0];
    my $data	= $_[1]; # only new data that will be copied to current user map

    $data	= update_object_classes ($config, $data);

    y2debug ("AddBefore LDAPAll called");
    return $data;
}


# This will be called just after Users::Add - the data map probably contains
# the values which we could use to create new ones
# Could be called multiple times for one user/group!
BEGIN { $TYPEINFO{Add} = ["function", ["map", "string", "any"], "any", "any"];}
sub Add {

    my $self	= shift;
    my $config	= $_[0];
    my $data	= $_[1]; # the whole map of current user/group after Users::Edit
    y2debug("Add LDAPAll called");
    return $data;
}

# this will be called at the beggining of Users::Edit
BEGIN { $TYPEINFO{EditBefore} = ["function",
    ["map", "string", "any"],
    "any", "any"];
}
sub EditBefore {

    my $self	= shift;
    my $config	= $_[0];
    my $data	= $_[1]; # only new data that will be copied to current user map
    # data of original user/group are saved as a submap of $config
    # data with key "org_data"

    # in $data hash, there could be "plugins_to_remove": list of plugins which
    # has to be removed from the user

    y2debug ("EditBefore LDAPAll called");
    return $data;
}

# this will be called just after Users::Edit
BEGIN { $TYPEINFO{Edit} = ["function",
    ["map", "string", "any"],
    "any", "any"];
}
sub Edit {

    my $self	= shift;
    my $config	= $_[0];
    my $data	= $_[1]; # the whole map of current user/group after Users::Edit

    # in $data hash, there could be "plugins_to_remove": list of plugins which
    # has to be removed from the user

    y2debug ("Edit LDAPAll called");
    return $data;
}



# what should be done before user is finally written to LDAP
BEGIN { $TYPEINFO{WriteBefore} = ["function", "boolean", "any", "any"];}
sub WriteBefore {

    my $self	= shift;
    my $config	= $_[0];
    my $data	= $_[1];

    # this means what was done with a user/group: added/edited/deleted
    my $action = $config->{"modified"} || "";
    
    y2debug ("WriteBefore LDAPAll called");
    return YaST::YCP::Boolean (1);
}

# what should be done after user is finally written to LDAP
BEGIN { $TYPEINFO{Write} = ["function", "boolean", "any", "any"];}
sub Write {

    my $self	= shift;
    my $config	= $_[0];
    my $data	= $_[1];

    # this means what was done with a user: added/edited/deleted
    my $action = $config->{"modified"} || "";
    y2debug ("Write LDAPAll called");
    return YaST::YCP::Boolean (1);
}
1
# EOF
