#! /usr/bin/perl -w
# ------------------------------------------------------------------------------
# Copyright (c) 2006-2012 Novell, Inc. All Rights Reserved.
#
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of version 2 of the GNU General Public License as published by the
# Free Software Foundation.
#
# 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.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail, you may find
# current contact information at www.novell.com.
# ------------------------------------------------------------------------------
#

#
# 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;
use Data::Dumper;

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", "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

# -- Warning messages --
# There is a special way, when you want to give user additional information
# (warning) about some issues appeared during the function.
#
# These keys can be saved by plugin to the result structure of AddBefore,
# Add, EditBefore, Edit, Enable, Disable calls:
#    "warning_message"		=> STRING
#        Translated message that should be shown to user (probably as a popup)
#    "warning_message_ID"	=> STRING
#	The ID of the message (optional).
#
# This key can be present in user/group $data hash:
#    "confirmed_warnings"	=> HASH (in the form { message_ID_1 => 1 })
#	(This has sense only if plugin uses optional "warning_message_ID" key)
#	Indicates which messages were already shown to this user/group.
#	Plugin function may check for existence of the message_ID_1 in this
#	hash before generating "warning_message", to realize if this message
#	was alredy shown before (in the same situation).
# See example in AddBefore function.
    
##------------------------------------


# 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";
    # plugin summary (table item)
    my $ret 	= __("Edit Remaining LDAP Attributes");

    if (defined $_[0]->{"what"} && $_[0]->{"what"} eq "group") {
	# plugin summary (table item)
	$ret 	= __("Edit Remaining LDAP Attributes");
    }
    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 $val		= $data->{$req};
	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."), $req);
	}
    }
    return "";
}

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

    my ($self, $config, $data)  = @_;
    y2debug ("Enable LDAPAll called");
    return $data;
}

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

    my ($self, $config, $data)  = @_;
    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");

    my $warning_id	= "something_wrong";
    my $warning		= __("An error occurred.");

    if (ref ($data->{"confirmed_warnings"}) eq "HASH" &&
	defined $data->{"confirmed_warnings"}{$warning_id}) {
	y2debug ("warning already shown");
    }
    elsif (0) {
	$data->{"warning_message"}	= $warning;
	$data->{"warning_message_ID"}	= $warning_id;
    }
    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
