GeniUser.pm.in 13.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2008 University of Utah and the Flux Group.
# All rights reserved.
#
package GeniUser;

use strict;
use Exporter;
use vars qw(@ISA @EXPORT);

@ISA    = "Exporter";
@EXPORT = qw ( );

# Must come after package declaration!
use lib '@prefix@/lib';
use GeniDB;
Leigh Stoller's avatar
Leigh Stoller committed
19
use GeniRegistry;
Leigh Stoller's avatar
Leigh Stoller committed
20
use GeniAuthority;
21
use GeniCertificate;
22 23 24
use libtestbed;
# Hate to import all this crap; need a utility library.
use libdb qw(TBGetUniqueIndex);
Leigh Stoller's avatar
Leigh Stoller committed
25
use User;
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
use English;
use overload ('""' => 'Stringify');
use vars qw();

# Configure variables
my $TB		   = "@prefix@";
my $TBOPS          = "@TBOPSEMAIL@";
my $TBAPPROVAL     = "@TBAPPROVALEMAIL@";
my $TBAUDIT   	   = "@TBAUDITEMAIL@";
my $BOSSNODE       = "@BOSSNODE@";
my $CONTROL	   = "@USERNODE@";
my $OURDOMAIN      = "@OURDOMAIN@";

# Cache of instances to avoid regenerating them.
my %users      = ();
my $debug      = 0;

# Little helper and debug function.
sub mysystem($)
{
    my ($command) = @_;

    print STDERR "Running '$command'\n"
	if ($debug);
    return system($command);
}

#
Leigh Stoller's avatar
Leigh Stoller committed
54
# Lookup by idx, or uuid.
55
#
56
sub Lookup($$;$)
57
{
58
    my ($class, $token, $includelocal) = @_;
59
    my $query_result;
Leigh Stoller's avatar
Leigh Stoller committed
60
    my $idx;
61

62 63 64
    $includelocal = 0
	if (!defined($includelocal));

65
    if ($token =~ /^\d+$/) {
Leigh Stoller's avatar
Leigh Stoller committed
66
	$idx = $token;
Leigh Stoller's avatar
Leigh Stoller committed
67 68 69
    }
    elsif ($token =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/) {
	$query_result =
Leigh Stoller's avatar
Leigh Stoller committed
70
	    DBQueryWarn("select idx from geni_users ".
Leigh Stoller's avatar
Leigh Stoller committed
71
			"where uuid='$token' and status='active'");
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

	return undef
	    if (!$query_result);

	if (!$query_result->numrows) {
	    return undef
		if (!$includelocal);

	    #
	    # Check Emulab users table. 
	    #
	    my $user = User->LookupByUUID($token);
	    return undef
		if (!defined($user));
	    return GeniUser->CreateFromLocal($user);
	}
	($idx) = $query_result->fetchrow_array();
    }
Leigh Stoller's avatar
Leigh Stoller committed
90
    elsif ($token =~ /^[-\w\.]*$/) {
91 92 93 94 95 96 97 98
	$query_result =
	    DBQueryWarn("select idx from geni_users ".
			"where hrn='$token' and status='active'");

	return undef
	    if (!$query_result);

	if (!$query_result->numrows) {
Leigh Stoller's avatar
Leigh Stoller committed
99
	    return undef
100
		if (!$includelocal);
Leigh Stoller's avatar
Leigh Stoller committed
101

102 103 104 105 106 107 108 109 110 111 112
	    #
	    # Check Emulab users table for last part of hrn.
	    #
	    ($token) = ($token =~ /\.(\w*)$/);

	    my $user = User->Lookup($token);
	    return undef
		if (!defined($user));
	    return GeniUser->CreateFromLocal($user);
	}
	($idx) = $query_result->fetchrow_array();
113 114 115 116 117
    }
    else {
	return undef;
    }
    
Leigh Stoller's avatar
Leigh Stoller committed
118 119 120 121 122 123
    # Look in cache first
    return $users{"$idx"}
        if (exists($users{"$idx"}));

    $query_result =
	DBQueryWarn("select * from geni_users where idx='$idx'");
124
    
125 126 127 128 129 130
    return undef
	if (!$query_result || !$query_result->numrows);

    my $self         = {};
    $self->{'USER'}  = $query_result->fetchrow_hashref();
    bless($self, $class);
Leigh Stoller's avatar
Leigh Stoller committed
131 132 133 134 135

    #
    # Grab the certificate, since we will probably want it.
    #
    my $uuid = $self->{'USER'}->{'uuid'};
136 137 138
    my $certificate = GeniCertificate->Lookup($uuid);
    if (!defined($certificate)) {
	print STDERR "Could not find certificate for user $idx ($uuid)\n";
Leigh Stoller's avatar
Leigh Stoller committed
139 140
	return undef;
    }
141
    $self->{'CERT'} = $certificate;
142 143
    
    # Add to cache. 
Leigh Stoller's avatar
Leigh Stoller committed
144
    $users{$self->{'USER'}->{'idx'}} = $self;
145 146 147 148 149
    
    return $self;
}
# accessors
sub field($$) { return ((! ref($_[0])) ? -1 : $_[0]->{'USER'}->{$_[1]}); }
Leigh Stoller's avatar
Leigh Stoller committed
150
sub idx($)		{ return field($_[0], "idx"); }
151
sub uid($)		{ return field($_[0], "uid"); }
Leigh Stoller's avatar
Leigh Stoller committed
152
sub hrn($)		{ return field($_[0], "hrn"); }
153 154 155 156 157 158
sub uuid($)		{ return field($_[0], "uuid"); }
sub status($)		{ return field($_[0], "status"); }
sub created($)		{ return field($_[0], "created"); }
sub archived($)		{ return field($_[0], "archived"); }
sub name($)		{ return field($_[0], "name"); }
sub email($)		{ return field($_[0], "email"); }
159
sub cert($)		{ return $_[0]->{'CERT'}->cert(); }
160
sub sa_uuid($)		{ return field($_[0], "sa_uuid"); }
161
sub GetCertificate($)   { return $_[0]->{'CERT'}; }
162

Leigh Stoller's avatar
Leigh Stoller committed
163 164 165 166 167 168 169
#
# Stringify for output.
#
sub Stringify($)
{
    my ($self) = @_;
    
Leigh Stoller's avatar
Leigh Stoller committed
170 171
    my $hrn = $self->hrn();
    my $idx = $self->idx();
172

Leigh Stoller's avatar
Leigh Stoller committed
173
    return "[GeniUser: $hrn, IDX: $idx]";
174 175
}

Leigh Stoller's avatar
Leigh Stoller committed
176 177 178 179 180 181 182
#
# Class method to check for an existing user that has the same
# uid/email. Lets not allow this for now. Return the number of
# users that match or -1 if an error. 
#
sub CheckExisting($$$)
{
Leigh Stoller's avatar
Leigh Stoller committed
183
    my ($class, $hrn, $email) = @_;
Leigh Stoller's avatar
Leigh Stoller committed
184

Leigh Stoller's avatar
Leigh Stoller committed
185
    my $safe_hrn   = DBQuoteSpecial($hrn);
Leigh Stoller's avatar
Leigh Stoller committed
186 187 188
    my $safe_email = DBQuoteSpecial($email);

    my $query_result =
Leigh Stoller's avatar
Leigh Stoller committed
189 190
	DBQueryFatal("select idx from geni_users ".
		     "where hrn=$safe_hrn and email=$safe_email");
Leigh Stoller's avatar
Leigh Stoller committed
191 192 193 194 195 196
    return -1
	if (!defined($query_result));

    return $query_result->numrows;
}

197
#
198
# Class function to create new Geni user in the DB and return object. 
199
#
200
sub Create($$$$;$)
201
{
202
    my ($class, $certificate, $authority, $info, $keys) = @_;
203 204
    my @insert_data = ();

Leigh Stoller's avatar
Leigh Stoller committed
205 206
    # Every user gets a new unique index.
    my $idx = User->NextIDX();
207

208 209 210 211 212 213
    if (!defined($authority)) {
	print STDERR "Need to specify an authority!\n";
	return undef;
    }
    my $sa_uuid = $authority->uuid();

214 215
    # Now tack on other stuff we need.
    push(@insert_data, "created=now()");
Leigh Stoller's avatar
Leigh Stoller committed
216
    push(@insert_data, "idx='$idx'");
Leigh Stoller's avatar
Leigh Stoller committed
217
    push(@insert_data, "status='active'");
218

219 220 221
    my $safe_hrn   = DBQuoteSpecial($certificate->hrn());
    my $safe_uuid  = DBQuoteSpecial($certificate->uuid());
    my $safe_email = DBQuoteSpecial($certificate->email());
Leigh Stoller's avatar
Leigh Stoller committed
222 223
    push(@insert_data, "hrn=$safe_hrn");
    push(@insert_data, "uuid=$safe_uuid");
224
    push(@insert_data, "email=$safe_email");
225
    push(@insert_data, "sa_uuid='$sa_uuid'");
226

227
    #
Leigh Stoller's avatar
Leigh Stoller committed
228
    # uid comes from last component of the hrn.
229 230
    #
    my ($uid) = ($certificate->hrn() =~ /^.*\.(\w*)$/);
Leigh Stoller's avatar
Leigh Stoller committed
231 232 233 234
    if (!defined($uid)) {
	print STDERR "HRN is not dotted: " . $certificate->hrn() . "\n";
	return undef;
    }
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252

    #
    # This comes from either an info record or the cert.
    #
    my $safe_name;
    if (defined($info) && exists($info->{'name'})) {
	$safe_name = DBQuoteSpecial($info->{'name'});
    }
    else {
	$safe_name = DBQuoteSpecial($uid);
    }
    my $safe_uid = DBQuoteSpecial($uid);

    push(@insert_data, "uid=$safe_uid");
    push(@insert_data, "name=$safe_name");

    if ($certificate->Store() != 0) {
	print STDERR "Could not store certificate for new user.\n";
253 254 255
	return undef;
    }

Leigh Stoller's avatar
Leigh Stoller committed
256
    # Insert the sshkey if we got one.
257 258 259 260 261 262 263
    if (defined($keys)) {
	foreach my $keyref (@{ $keys }) {
	    my $safe_key  = DBQuoteSpecial($keyref->{'key'});
	    my $safe_type = DBQuoteSpecial($keyref->{'type'});
	    
	    DBQueryWarn("replace into geni_userkeys set ".
			"  uuid=$safe_uuid, created=now(), ".
264 265
			"  `key`=$safe_key, `type`=$safe_type")
		or return undef;
266
	}
Leigh Stoller's avatar
Leigh Stoller committed
267 268
    }

269
    # Insert into DB.
Leigh Stoller's avatar
Leigh Stoller committed
270 271
    if (! DBQueryWarn("insert into geni_users set " .
		      join(",", @insert_data))) {
272
	DBQueryWarn("delete from geni_userkeys where uuid=$safe_uuid");
Leigh Stoller's avatar
Leigh Stoller committed
273 274
	return undef;
    }
275

Leigh Stoller's avatar
Leigh Stoller committed
276
    return GeniUser->Lookup($idx);
277 278
}

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
#
# Modify a record; only partial.
#
sub Modify($$$$)
{
    my ($self, $name, $email, $keys) = @_;
    my @insert_data = ();

    my $idx  = $self->idx();
    my $uuid = $self->uuid();
    my $safe_name  = DBQuoteSpecial($name);
    my $safe_email = DBQuoteSpecial($email);

    return -1
	if (!DBQueryWarn("update geni_users set ".
			 " name=$safe_name, email=$safe_email ".
			 "where idx='$idx'"));

    if (defined($keys)) {
	return -1
	    if (!DBQueryWarn("delete from geni_userkeys where uuid='$uuid'"));
	
	foreach my $keyref (@{ $keys }) {
	    my $safe_key  = DBQuoteSpecial($keyref->{'key'});
	    my $safe_type = DBQuoteSpecial($keyref->{'type'});
	    
	    DBQueryWarn("replace into geni_userkeys set ".
			"  uuid='$uuid', created=now(), ".
			"  `key`=$safe_key, `type`=$safe_type")
		or return undef;
	}
    }
    return 0;
}

Leigh Stoller's avatar
Leigh Stoller committed
314 315 316 317 318 319 320 321 322 323 324
#
# We wrap up local users so that the interface to them is consistent, but
# do not want to duplicate any data, so use a different class wrapper.
#
sub CreateFromLocal($$)
{
    my ($class, $user) = @_;

    return GeniUser::LocalUser->Create($user);
}

325
#
326
# Delete the user.
327 328 329 330 331 332 333 334
#
sub Delete($)
{
    my ($self) = @_;

    return 0
	if (! ref($self));

335 336
    my $idx  = $self->idx();
    my $uuid = $self->uuid();
337

338 339
    DBQueryWarn("delete from geni_bindings where user_uuid='$uuid'")
	or return -1;
340 341
    DBQueryWarn("delete from geni_userkeys where user_uuid='$uuid'")
	or return -1;
342 343
    DBQueryWarn("delete from geni_certificates where uuid='$uuid'")
	or return -1;
Leigh Stoller's avatar
Leigh Stoller committed
344
    DBQueryWarn("delete from geni_users where idx='$idx'")
345 346 347 348 349
	or return -1;
    
    return 0;
}

Leigh Stoller's avatar
Leigh Stoller committed
350
#
351
# Get the sshkeys for a user.
Leigh Stoller's avatar
Leigh Stoller committed
352
#
353
sub GetSSHKeys($$)
Leigh Stoller's avatar
Leigh Stoller committed
354 355
{
    my ($self, $pref) = @_;
356
    my @results = ();
Leigh Stoller's avatar
Leigh Stoller committed
357 358 359 360

    return -1
	if (! (ref($self) && ref($pref)));

361 362
    my $uuid = $self->uuid();
    my $query_result =
363
	DBQueryWarn("select `key` from geni_userkeys ".
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
		    "where uuid='$uuid' and type='ssh'");
    return -1
	if (!$query_result);

    while (my ($sshkey) = $query_result->fetchrow_array()) {
	push(@results, $sshkey);
    }
    @$pref = @results;
    return 0;
}

#
# Get the keys for a user.
#
sub GetKeys($$)
{
    my ($self, $pref) = @_;
    my @results = ();

    return -1
	if (! (ref($self) && ref($pref)));
Leigh Stoller's avatar
Leigh Stoller committed
385 386 387

    my $uuid = $self->uuid();
    my $query_result =
388
	DBQueryWarn("select `type`,`key` from geni_userkeys ".
389
		    "where uuid='$uuid'");
Leigh Stoller's avatar
Leigh Stoller committed
390 391 392
    return -1
	if (!$query_result);

393 394 395 396 397
    while (my ($type,$key) = $query_result->fetchrow_array()) {
	push(@results, {"type" => $type,
			"key"  => $key});
    }
    @$pref = @results;
Leigh Stoller's avatar
Leigh Stoller committed
398 399 400 401 402 403 404 405 406 407 408 409 410
    return 0;
}

#
# Create a nonlocal user.
#
sub CreateNonLocal($)
{
    my ($self) = @_;

    return undef
	if (! ref($self));

411 412
    my @sshkeys = ();
    $self->GetSSHKeys(\@sshkeys);
Leigh Stoller's avatar
Leigh Stoller committed
413 414 415 416 417 418

    return User::NonLocal->Create($self->idx(),
				  $self->uid(),
				  $self->uuid(),
				  $self->name(),
				  $self->email(),
419
				  \@sshkeys);
Leigh Stoller's avatar
Leigh Stoller committed
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
}

#
# Bind user to slice, which is another way of saying bind the nonlocal
# user to the experiment (for tmcd).
#
sub BindToSlice($$)
{
    my ($self, $slice) = @_;

    return -1
	if (! (ref($self) && ref($slice)));

    my $emulab_user = User::NonLocal->Lookup($self->uuid());
    if (!defined($emulab_user)) {
	$emulab_user = $self->CreateNonLocal();
	if (!defined($emulab_user)) {
	    print STDERR "Could not create nonlocal user for $self\n";
	    return -1;
	}
    }
    my $experiment = $slice->GetExperiment();
    if (!defined($experiment)) {
	print STDERR "Could not locate experiment object for $slice\n";
	return -1;
    }
    return $emulab_user->BindToExperiment($experiment);
}

Leigh Stoller's avatar
Leigh Stoller committed
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
sub UnBindFromSlice($$)
{
    my ($self, $slice) = @_;

    return -1
	if (! (ref($self) && ref($slice)));

    my $emulab_user = User::NonLocal->Lookup($self->uuid());
    return 0
	if (!defined($emulab_user));

    my $experiment = $slice->GetExperiment();
    if (!defined($experiment)) {
	print STDERR "Could not locate experiment object for $slice\n";
	return -1;
    }
    return $emulab_user->UnBindFromExperiment($experiment);
}

468 469 470 471 472 473 474 475 476 477
#
# Archive user. 
#
sub Archive($)
{
    my ($self) = @_;

    return 0
	if (! ref($self));

Leigh Stoller's avatar
Leigh Stoller committed
478
    my $idx = $self->idx();
479 480

    DBQueryWarn("update geni_users set status='archived' ".
Leigh Stoller's avatar
Leigh Stoller committed
481
		"where idx='$idx'")
482 483 484 485 486
	or return -1;
    
    return 0;
}

Leigh Stoller's avatar
Leigh Stoller committed
487 488 489 490 491
############################################################################
#
# Wrapper for local users.
#
package GeniUser::LocalUser;
492 493 494
use vars qw(@ISA);
@ISA = "GeniUser";

Leigh Stoller's avatar
Leigh Stoller committed
495 496
use English;
use GeniDB;
497
use GeniUser;
498
use GeniCertificate;
Leigh Stoller's avatar
Leigh Stoller committed
499 500 501 502 503 504 505 506 507 508 509 510
use overload ('""' => 'Stringify');

#
# Create a wrapper, with the same access names.
#
sub Create($$)
{
    my ($class, $user) = @_;

    my $self         = {};
    $self->{'USER'}  = $user;

511 512
    # And the certificate wrapper.
    my $certificate = GeniCertificate::LocalUser->Create($user);
Leigh Stoller's avatar
Leigh Stoller committed
513 514 515 516
    if (!defined($certificate)) {
	print STDERR "No certificate found for $user\n";
	return undef;
    }
517
    $self->{'CERT'} = $certificate;
518

Leigh Stoller's avatar
Leigh Stoller committed
519 520 521 522
    bless($self, $class);
    return $self;
}

523
sub idx($)		{ return $_[0]->{'USER'}->uid_idx(); }
Leigh Stoller's avatar
Leigh Stoller committed
524 525 526 527 528
sub uid($)		{ return $_[0]->{'USER'}->uid(); }
sub uuid                { return $_[0]->{'USER'}->uuid(); }
sub created($)		{ return $_[0]->{'USER'}->created(); }
sub name($)		{ return $_[0]->{'USER'}->name(); }
sub email($)		{ return $_[0]->{'USER'}->email(); }
529
sub GetSSHKeys($$)      { return $_[0]->{'USER'}->GetSSHKeys($_[1]); }
Leigh Stoller's avatar
Leigh Stoller committed
530 531

# Need to construct this since not in User structure.
532
sub hrn($)		{ return "emulab." . $_[0]->uid(); }
Leigh Stoller's avatar
Leigh Stoller committed
533

534 535 536
# And this is in another structure.
sub cert($)             { return $_[0]->{'CERT'}->cert(); }
sub GetCertificate($)   { return $_[0]->{'CERT'}; }
Leigh Stoller's avatar
Leigh Stoller committed
537 538 539 540 541 542 543 544

#
# Register this local user at the ClearingHouse.
#
sub Register($)
{
    my ($self) = @_;

Leigh Stoller's avatar
Leigh Stoller committed
545 546 547 548 549 550
    my $clearinghouse = GeniRegistry::ClearingHouse->Create();
    return -1
	if (!defined($clearinghouse));

    return $clearinghouse->RegisterUser($self->name(), $self->email(),
					$self->cert());
Leigh Stoller's avatar
Leigh Stoller committed
551
}
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566

#
# Get the keys for a local user, which are just sshkeys
#
sub GetKeys($$)
{
    my ($self, $pref) = @_;
    my @results = ();

    return -1
	if (! (ref($self) && ref($pref)));

    my $uuid = $self->uuid();
    my @sshkeys    = ();
    $self->GetSSHKeys(\@sshkeys);
Leigh Stoller's avatar
Leigh Stoller committed
567
    
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
    foreach my $sshkey (@sshkeys) {
	push(@results, {"type" => 'ssh',
			"key"  => $sshkey});
    }
    @$pref = @results;
    return 0;
}

#
# Override this for local users. For tmcd we need an entry in the
# nonlocal tables (not in the mood to change tmcd yet) so that local
# users get accounts in slivers that match their local identity. But
# no need to duplicate the ssh keys. Needs more thought. 
#
sub CreateNonLocal($)
{
    my ($self) = @_;

    return undef
	if (! ref($self));

    return User::NonLocal->Create($self->idx(),
				  $self->uid(),
				  $self->uuid(),
				  $self->name(),
				  $self->email(), undef);
}

Leigh Stoller's avatar
Leigh Stoller committed
596 597 598 599 600 601
#
# Stringify for output.
#
sub Stringify($)
{
    my ($self) = @_;
Leigh Stoller's avatar
Leigh Stoller committed
602
    my $user = $self->{'USER'};
Leigh Stoller's avatar
Leigh Stoller committed
603 604 605 606
    
    return "$user";
}

607 608
# _Always_ make sure that this 1 is at the end of the file...
1;