#
# Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident	"@(#)RBAC.pm 1.5     03/05/27 SMI"
#
# Role Based Access Control methods and constants
#

package Cluster::RBAC;
use Exporter;
use Cluster::Cgi;
use Cluster::RunCommand;
use Cluster::CookieStorage;
use Sun::Solaris::Utils qw(gettext);
use vars qw(@ISA $VERSION @EXPORT);
@ISA = ("Exporter");
@EXPORT = qw($CL_AUTH_DEVICE_ADMIN $CL_AUTH_DEVICE_MODIFY
	     $CL_AUTH_DEVICE_READ $CL_AUTH_NODE_MODIFY
	     $CL_AUTH_NODE_READ $CL_AUTH_QUORUM_MODIFY
	     $CL_AUTH_QUORUM_READ $CL_AUTH_QUORUM_ADMIN
	     $CL_AUTH_RESOURCE_ADMIN $CL_AUTH_RESOURCE_MODIFY
	     $CL_AUTH_RESOURCE_READ $CL_AUTH_SYSTEM_ADMIN
	     $CL_AUTH_SYSTEM_MODIFY $CL_AUTH_SYSTEM_READ
	     $CL_AUTH_TRANSPORT_MODIFY $CL_AUTH_TRANSPORT_READ
	     $CL_AUTH_APPINSTALL $CL_AUTH_NETWORK_READ
	     $CL_AUTH_NETWORK_MODIFY $CL_AUTH_NETWORK_ADMIN
	     $CL_AUTH_TRANSPORT_ADMIN);

##############################################################################
#
# Class constructor
#
##############################################################################

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $self  = {};
	bless ($self, $class);
	return $self;
}

##############################################################################
#
# Class Variables
#
##############################################################################

# RBAC authentication data structures
my %RBAC_USERS               = ();
my %RBAC_AUTHS               = ();

# Sun Cluster RBAC autorizations
$CL_AUTH_DEVICE_READ      = "solaris.cluster.device.read";
$CL_AUTH_DEVICE_ADMIN     = "solaris.cluster.device.admin";
$CL_AUTH_DEVICE_MODIFY    = "solaris.cluster.device.modify";
$CL_AUTH_NODE_READ        = "solaris.cluster.node.read";
$CL_AUTH_NODE_MODIFY      = "solaris.cluster.node.modify";
$CL_AUTH_QUORUM_READ      = "solaris.cluster.quorum.read";
$CL_AUTH_QUORUM_ADMIN     = "solaris.cluster.quorum.admin";
$CL_AUTH_QUORUM_MODIFY    = "solaris.cluster.quorum.modify";
$CL_AUTH_RESOURCE_READ    = "solaris.cluster.resource.read";
$CL_AUTH_RESOURCE_ADMIN   = "solaris.cluster.resource.admin";
$CL_AUTH_RESOURCE_MODIFY  = "solaris.cluster.resource.modify";
$CL_AUTH_SYSTEM_READ      = "solaris.cluster.system.read";
$CL_AUTH_SYSTEM_ADMIN     = "solaris.cluster.system.admin";
$CL_AUTH_SYSTEM_MODIFY    = "solaris.cluster.system.modify";
$CL_AUTH_TRANSPORT_READ   = "solaris.cluster.transport.read";
$CL_AUTH_TRANSPORT_ADMIN  = "solaris.cluster.transport.admin";
$CL_AUTH_TRANSPORT_MODIFY = "solaris.cluster.transport.modify";
$CL_AUTH_NETWORK_READ     = "solaris.cluster.network.read";
$CL_AUTH_NETWORK_ADMIN    = "solaris.cluster.network.admin";
$CL_AUTH_NETWORK_MODIFY   = "solaris.cluster.network.modify";
$CL_AUTH_APPINSTALL       = "solaris.cluster.appinstall";

# Helper classes to instantiate
my $cmd = new Cluster::RunCommand;

##############################################################################
#
# Package Methods
#
##############################################################################

# Get the key to the current user's cookie so that we can lookup user info
sub get_cookie_key() {
	return $ENV{"AuthCookie"};
}

# Get the authenticated user
sub get_user() {
	my ($self) = @_; 
	my (@entry) = 
		&Cluster::CookieStorage::get_cookie($self->get_cookie_key());
	my $user = $entry[0];
	my $role = $entry[2];

	# If there's a role, return that as the "user" for the purpose
	# of looking up authorizations.
	if (defined $role && $role ne "") {
		$user = $role;
	}

	return $user;
}

# Get the available roles for the current user
sub get_roles() {
	my ($self, $user) = @_;
	if (!defined $user || $user eq "") {
		$user = $self->get_user();
	}	
	my @roles = $cmd->roles($user);
	return @roles;
}

# Register this user's RBAC authentications. If the cookie key under
# which the user's auths were registered is out of date, reread the
# auths from Solaris, and register them along with the new
# key. Otherwise, this function is a no-op.
sub register_auths() {
	my ($self, $user) = @_;

	# Get the user if none was passed
	if (!defined $user || $user eq "") {
		$user = $self->get_user();
	}

	my $key = $self->get_cookie_key();

	if (!defined $RBAC_USERS{$user} ||
	    $RBAC_USERS{$user} ne $key) {
		my @auths = $cmd->auths($user);
		$RBAC_USERS{$user} = $key;
		@{ $RBAC_AUTHS{$user} } = @auths;
	}
}

# Get the RBAC auths for the current user from the cache
sub get_auths() {
	my ($self, $user) = @_;

	# Get the user if none was passed
	if (!defined $user || $user eq "") {
		$user = $self->get_user();
	}	

	# Register the auths for this user if necessary
	$self->register_auths($user);

	# Read the auths
	return @{ $RBAC_AUTHS{$user} };
}

# Check if the current user has the specified authorization or
# anything more general, up to "solaris.*".
sub check_auth() {
	my ($self, $auth) = @_;
	my @auths = $self->get_auths();
	
	# Check for the specified auth
	return 1 if ($self->member($auth, @auths) != -1);

	# Check for more general auths
	my @authparts = split /\./, $auth;

	for (my $i=($#authparts-1); $i>=0; $i--) {
		my $newauth = join(".", @authparts[0 .. $i]) . ".*";
		return 1 if ($self->member($newauth, @auths) != -1);
	}
}

# Check for rg, rs, or application installation authorizations
sub check_ds_auth() {
	my ($self, $title, $cmd) = @_;
	my $q = new Cluster::Cgi;

	# These must match the actions in scstat.pl, oracle.pl, nfs.pl,
	# ldap.pl, iplanet.pl, dns.pl, apache.pl, rs_add.pl, rg_config.pl,
	# common.pm, and scrgcommon.pm.
	my @rg_admin_actions = ("cmd_rgoffline",
				 "cmd_rgprim",
				 "cmd_rgrestart",
				 "cmd_rgunmanage",
				 "cmd_rgmanage",
				 "cmd_rsenable",
				 "cmd_rsdisable",
				 "cmd_rsmenable",
				 "cmd_rsmdisable",
				 "cmd_rs_failed"
				 );

	my @rg_modify_actions = ("cmd_rs_delete",
				"cmd_rs_weights",
				"cmd_rs_weights_1",
				"cmd_rsmodify",
				"cmd_rsmodify2",
				"cmd_rgrs_delete",
				"cmd_params",
				"cmd_additional",
				"cmd_create",
				"cmd_rg_create_1",
				"cmd_rgsf_create_1",
				"cmd_mainpage"
				);

	my @app_install_actions = ("cmd_install",
				   "cmd_install_type",
				   "cmd_install_type_1",
				   "cmd_register_type",
				   "cmd_mainpage",
				   "cmd_setup",
				   "cmd_setup_1",
				   "cmd_run",
				   "cmd_resources",
				   "cmd_install_pkgs",
				   "cmd_install_oracle_db",
				   "cmd_setup_oracle",
				   "cmd_install_oracle",
				   "cmd_install_oracle_1",
				   "cmd_modify",
				   "cmd_modify_1",
				   "cmd_files",
				   "cmd_etcsystem",
				   "cmd_postinstall"
				   );

	# Check for authorizations based on requested command
	my $error = 1;
	if (($self->member($cmd, @app_install_actions) != -1 && 
	     $self->check_auth($CL_AUTH_APPINSTALL)) ||
	    ($self->member($cmd, @rg_modify_actions) != -1 &&
	     $self->check_auth($CL_AUTH_RESOURCE_MODIFY)) ||
	    ($self->member($cmd, @rg_admin_actions) != -1 &&
	     $self->check_auth($CL_AUTH_RESOURCE_ADMIN))) {
		$error = 0;
	}

	# Print an authorization error message & exit if we have any errors
	if ($error != 0) {
		print $q->start_html({ TITLE => $title,
				       STYLE  => { 'src' =>
						   '/css/clustmgr-style.css'},
				       CLASS  => "default-body"
				       });
		$self->auth_error($q, $title, $cmd);
		exit;
	}
}

# Invalidate the authorization for the current user/role. Delete the
# hash entries for this user to free up memory.
sub invalidate_auths() {
	my ($self) = @_;
	my $user = $self->get_user();
	delete $RBAC_USERS{$user};
	delete $RBAC_AUTHS{$user};
}

# Look for an item in the given array. Return position of element if
# we find it, else return -1.
sub member {
    my ($self, $item, @array) = @_;

    for (my $i = 0; $i <= $#array; $i++) {
        if ($item eq $array[$i]) {
            return $i;
        }
    }

    return -1;
}

#
# Present authentication failures
#

sub auth_error() {
	my ($self, $q, $title, $cmd) = @_;

	if (ref($title) eq "HASH") {
	    $title = $title->{$cmd};
	}
	if (ref($title) eq "ARRAY") {
	    for (my $i = 0; $i <= $#{@{$title}}; $i += 2) {
		$title->[$i] = gettext($title->[$i]);
	    }
	} else {
	    $title = gettext($title);
	}

	my $header = gettext("Authorization Error");
	my $text = gettext("User has insufficient authorization " .
			   "to perform the requested action.");
	$q->start_action_bar_table($q->join_links($title));
	$q->mid_action_bar_table();
	$q->end_action_bar_table();
	print $q->start_table();
	print $q->login_alert($header, $text);
	print $q->end_table();
}

#
# Check to see if the current user is root
#

sub check_root() {
	my ($self) = @_;
	my $user = $self->get_user();
	my $uid = (getpwnam($user))[2];
	
	if ($uid == 0) {
		return 1;
	}

	return 0;
}
