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 B. Stoller's avatar
Leigh B. Stoller committed
19
use GeniRegistry;
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
54
# Lookup by idx, or uuid.
55
#
56
sub Lookup($$;$)
57
{
58
    my ($class, $token, $includelocal) = @_;
59
    my $query_result;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
60
    my $idx;
61

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

65
    if ($token =~ /^\d+$/) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
66
	$idx = $token;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
67
68
69
    }
    elsif ($token =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/) {
	$query_result =
Leigh B. Stoller's avatar
Leigh B. Stoller committed
70
	    DBQueryWarn("select idx from geni_users ".
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
99
	    return undef
100
		if (!$includelocal);
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
139
140
	return undef;
    }
141
    $self->{'CERT'} = $certificate;
142
143
    
    # Add to cache. 
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
150
sub idx($)		{ return field($_[0], "idx"); }
151
sub uid($)		{ return field($_[0], "uid"); }
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
163
164
165
166
167
168
169
#
# Stringify for output.
#
sub Stringify($)
{
    my ($self) = @_;
    
Leigh B. Stoller's avatar
Leigh B. Stoller committed
170
171
    my $hrn = $self->hrn();
    my $idx = $self->idx();
172

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

Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
183
    my ($class, $hrn, $email) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
184

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

    my $query_result =
Leigh B. Stoller's avatar
Leigh B. Stoller committed
189
190
	DBQueryFatal("select idx from geni_users ".
		     "where hrn=$safe_hrn and email=$safe_email");
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
216
    push(@insert_data, "idx='$idx'");
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
228
    # uid comes from last component of the hrn.
229
230
    #
    my ($uid) = ($certificate->hrn() =~ /^.*\.(\w*)$/);
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
267
268
    }

269
    # Insert into DB.
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
273
274
	return undef;
    }
275

Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
344
    DBQueryWarn("delete from geni_users where idx='$idx'")
345
346
347
348
349
	or return -1;
    
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
350
#
351
# Get the sshkeys for a user.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
352
#
353
sub GetSSHKeys($$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
354
355
{
    my ($self, $pref) = @_;
356
    my @results = ();
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
478
    my $idx = $self->idx();
479
480

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

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
495
496
use English;
use GeniDB;
497
use GeniUser;
498
use GeniCertificate;
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
519
520
521
522
    bless($self, $class);
    return $self;
}

523
sub idx($)		{ return $_[0]->{'USER'}->uid_idx(); }
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. Stoller committed
530
531

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

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

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

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

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