16382074085bc0f507901e881790e50e410d34ad
[sfa.git] / plc / registry.py
1 ##
2 # Registry is a GeniServer that implements the Registry interface
3
4 import tempfile
5 import os
6 import time
7 import sys
8
9 from util.hierarchy import Hierarchy
10 from util.trustedroot import TrustedRootList
11 from util.cert import Keypair, Certificate
12 from util.gid import GID
13 from util.geniserver import GeniServer
14 from util.record import GeniRecord
15 from util.genitable import GeniTable
16 from util.geniticket import Ticket
17 from util.excep import *
18 from util.misc import *
19
20 ##
21 # Convert geni fields to PLC fields for use when registering up updating
22 # registry record in the PLC database
23 #
24 # @param type type of record (user, slice, ...)
25 # @param hrn human readable name
26 # @param geni_fields dictionary of geni fields
27 # @param pl_fields dictionary of PLC fields (output)
28
29 def geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields):
30     if type == "user":
31         if not "email" in pl_fields:
32             if not "email" in geni_fields:
33                 raise MissingGeniInfo("email")
34             pl_fields["email"] = geni_fields["email"]
35
36         if not "first_name" in pl_fields:
37             pl_fields["first_name"] = "geni"
38
39         if not "last_name" in pl_fields:
40             pl_fields["last_name"] = hrn
41
42     elif type == "slice":
43         if not "instantiation" in pl_fields:
44             pl_fields["instantiation"] = "delegated"  # "plc-instantiated"
45         if not "name" in pl_fields:
46             pl_fields["name"] = hrn_to_pl_slicename(hrn)
47         if not "max_nodes" in pl_fields:
48             pl_fields["max_nodes"] = 10
49
50     elif type == "node":
51         if not "hostname" in pl_fields:
52             if not "dns" in geni_fields:
53                 raise MissingGeniInfo("dns")
54             pl_fields["hostname"] = geni_fields["dns"]
55
56         if not "model" in pl_fields:
57             pl_fields["model"] = "geni"
58
59     elif type == "sa":
60         pl_fields["login_base"] = hrn_to_pl_login_base(hrn)
61
62         if not "name" in pl_fields:
63             pl_fields["name"] = hrn
64
65         if not "abbreviated_name" in pl_fields:
66             pl_fields["abbreviated_name"] = hrn
67
68         if not "enabled" in pl_fields:
69             pl_fields["enabled"] = True
70
71         if not "is_public" in pl_fields:
72             pl_fields["is_public"] = True
73
74 ##
75 # Registry is a GeniServer that serves registry and slice operations at PLC.
76
77 class Registry(GeniServer):
78     ##
79     # Create a new registry object.
80     #
81     # @param ip the ip address to listen on
82     # @param port the port to listen on
83     # @param key_file private key filename of registry
84     # @param cert_file certificate filename containing public key (could be a GID file)
85
86     def __init__(self, ip, port, key_file, cert_file):
87         GeniServer.__init__(self, ip, port, key_file, cert_file)
88
89         # get PL account settings from config module
90         self.pl_auth = get_pl_auth()
91
92         # connect to planetlab
93         if "Url" in self.pl_auth:
94             self.connect_remote_shell()
95         else:
96             self.connect_local_shell()
97
98     ##
99     # Connect to a remote shell via XMLRPC
100
101     def connect_remote_shell(self):
102         import remoteshell
103         self.shell = remoteshell.RemoteShell()
104
105     ##
106     # Connect to a local shell via local API functions
107
108     def connect_local_shell(self):
109         import PLC.Shell
110         self.shell = PLC.Shell.Shell(globals = globals())
111
112     ##
113     # Register the server RPCs for the registry
114
115     def register_functions(self):
116         GeniServer.register_functions(self)
117         # registry interface
118         self.server.register_function(self.create_gid)
119         self.server.register_function(self.get_self_credential)
120         self.server.register_function(self.get_credential)
121         self.server.register_function(self.get_gid)
122         self.server.register_function(self.register)
123         self.server.register_function(self.remove)
124         self.server.register_function(self.update)
125         self.server.register_function(self.list)
126         self.server.register_function(self.resolve)
127
128     ##
129     # Given an authority name, return the information for that authority. This
130     # is basically a stub that calls the hierarchy module.
131     #
132     # @param auth_hrn human readable name of authority
133
134     def get_auth_info(self, auth_hrn):
135         return AuthHierarchy.get_auth_info(auth_hrn)
136
137     ##
138     # Given an authority name, return the database table for that authority. If
139     # the database table does not exist, then one will be automatically
140     # created.
141     #
142     # @param auth_name human readable name of authority
143
144     def get_auth_table(self, auth_name):
145         auth_info = self.get_auth_info(auth_name)
146
147         table = GeniTable(hrn=auth_name,
148                           cninfo=auth_info.get_dbinfo())
149
150         # if the table doesn't exist, then it means we haven't put any records
151         # into this authority yet.
152
153         if not table.exists():
154             report.trace("Registry: creating table for authority " + auth_name)
155             table.create()
156
157         return table
158
159     ##
160     # Verify that an authority belongs to this registry. This is basically left
161     # up to the implementation of the hierarchy module. If the specified name
162     # does not belong to this registry, an exception is thrown indicating the
163     # caller should contact someone else.
164     #
165     # @param auth_name human readable name of authority
166
167     def verify_auth_belongs_to_me(self, name):
168         # get_auth_info will throw an exception if the authority does not
169         # exist
170         self.get_auth_info(name)
171
172     ##
173     # Verify that an object belongs to this registry. By extension, this implies
174     # that the authority that owns the object belongs to this registry. If the
175     # object does not belong to this registry, then an exception is thrown.
176     #
177     # @param name human readable name of object
178
179     def verify_object_belongs_to_me(self, name):
180         auth_name = get_authority(name)
181         if not auth_name:
182             # the root authority belongs to the registry by default?
183             # TODO: is this true?
184             return
185         self.verify_auth_belongs_to_me(auth_name)
186
187     ##
188     # Verify that the object_gid that was specified in the credential allows
189     # permission to the object 'name'. This is done by a simple prefix test.
190     # For example, an object_gid for planetlab.us.arizona would match the
191     # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
192     #
193     # @param name human readable name to test
194
195     def verify_object_permission(self, name):
196         object_hrn = self.object_gid.get_hrn()
197         if object_hrn == name:
198             return
199         if name.startswith(object_hrn + "."):
200             return
201         raise PermissionError(name)
202
203     ##
204     # Fill in the planetlab-specific fields of a Geni record. This involves
205     # calling the appropriate PLC methods to retrieve the database record for
206     # the object.
207     #
208     # PLC data is filled into the pl_info field of the record.
209     #
210     # @param record record to fill in fields (in/out param)
211
212     def fill_record_pl_info(self, record):
213         type = record.get_type()
214         pointer = record.get_pointer()
215
216         # records with pointer==-1 do not have plc info associated with them.
217         # for example, the top level authority records which are
218         # authorities, but not PL "sites"
219         if pointer == -1:
220             record.set_pl_info({})
221             return
222
223         if (type == "sa") or (type == "ma"):
224             pl_res = self.shell.GetSites(self.pl_auth, [pointer])
225         elif (type == "slice"):
226             pl_res = self.shell.GetSlices(self.pl_auth, [pointer])
227         elif (type == "user"):
228             pl_res = self.shell.GetPersons(self.pl_auth, [pointer])
229         elif (type == "node"):
230             pl_res = self.shell.GetNodes(self.pl_auth, [pointer])
231         else:
232             raise UnknownGeniType(type)
233
234         if not pl_res:
235             # the planetlab record no longer exists
236             # TODO: delete the geni record ?
237             raise PlanetLabRecordDoesNotExist(record.get_name())
238
239         record.set_pl_info(pl_res[0])
240
241     ##
242     # Look up user records given PLC user-ids. This is used as part of the
243     # process for reverse-mapping PLC records into Geni records.
244     #
245     # @param auth_table database table for the authority that holds the user records
246     # @param user_id_list list of user ids
247     # @param role either "*" or a string describing the role to look for ("pi", "user", ...)
248     #
249     # TODO: This function currently only searches one authority because it would
250     # be inefficient to brute-force search all authorities for a user id. The
251     # solution would likely be to implement a reverse mapping of user-id to
252     # (type, hrn) pairs.
253
254     def lookup_users(self, auth_table, user_id_list, role="*"):
255         record_list = []
256         for person_id in user_id_list:
257             user_records = auth_table.find("user", person_id, "pointer")
258             for user_record in user_records:
259                 self.fill_record_info(user_record)
260
261                 user_roles = user_record.get_pl_info().get("roles")
262                 if (role=="*") or (role in user_roles):
263                     record_list.append(user_record.get_name())
264         return record_list
265
266     ##
267     # Fill in the geni-specific fields of the record.
268     #
269     # Note: It is assumed the fill_record_pl_info() has already been performed
270     # on the record.
271
272     def fill_record_geni_info(self, record):
273         geni_info = {}
274         type = record.get_type()
275
276         if (type == "slice"):
277             auth_table = self.get_auth_table(get_authority(record.get_name()))
278             person_ids = record.pl_info.get("person_ids", [])
279             researchers = self.lookup_users(auth_table, person_ids)
280             geni_info['researcher'] = researchers
281
282         elif (type == "sa"):
283             auth_table = self.get_auth_table(record.get_name())
284             person_ids = record.pl_info.get("person_ids", [])
285             pis = self.lookup_users(auth_table, person_ids, "pi")
286             geni_info['pi'] = pis
287             # TODO: OrganizationName
288
289         elif (type == "ma"):
290             auth_table = self.get_auth_table(record.get_name())
291             person_ids = record.pl_info.get("person_ids", [])
292             operators = self.lookup_users(auth_table, person_ids, "tech")
293             geni_info['operator'] = operators
294             # TODO: OrganizationName
295
296             auth_table = self.get_auth_table(record.get_name())
297             person_ids = record.pl_info.get("person_ids", [])
298             owners = self.lookup_users(auth_table, person_ids, "admin")
299             geni_info['owner'] = owners
300
301         elif (type == "node"):
302             geni_info['dns'] = record.pl_info.get("hostname", "")
303             # TODO: URI, LatLong, IP, DNS
304
305         elif (type == "user"):
306             geni_info['email'] = record.pl_info.get("email", "")
307             # TODO: PostalAddress, Phone
308
309         record.set_geni_info(geni_info)
310
311     ##
312     # Given a Geni record, fill in the PLC-specific and Geni-specific fields
313     # in the record.
314
315     def fill_record_info(self, record):
316         self.fill_record_pl_info(record)
317         self.fill_record_geni_info(record)
318
319     ##
320     # GENI API: register
321     #
322     # Register an object with the registry. In addition to being stored in the
323     # Geni database, the appropriate records will also be created in the
324     # PLC databases
325     #
326     # @param cred credential string
327     # @param record_dict dictionary containing record fields
328
329     def register(self, cred, record_dict):
330         self.decode_authentication(cred, "register")
331
332         record = GeniRecord(dict = record_dict)
333         type = record.get_type()
334         name = record.get_name()
335
336         auth_name = get_authority(name)
337         self.verify_object_permission(auth_name)
338         auth_info = self.get_auth_info(auth_name)
339         table = self.get_auth_table(auth_name)
340
341         pkey = None
342
343         # check if record already exists
344         existing_records = table.resolve(type, name)
345         if existing_records:
346             raise ExistingRecord(name)
347
348         if (type == "sa") or (type=="ma"):
349             # update the tree
350             if not AuthHierarchy.auth_exists(name):
351                 AuthHierarchy.create_auth(name)
352
353             # authorities are special since they are managed by the registry
354             # rather than by the caller. We create our own GID for the
355             # authority rather than relying on the caller to supply one.
356
357             # get the GID from the newly created authority
358             child_auth_info = self.get_auth_info(name)
359             gid = auth_info.get_gid_object()
360             record.set_gid(gid.save_to_string(save_parents=True))
361
362             geni_fields = record.get_geni_info()
363             site_fields = record.get_pl_info()
364
365             # if registering a sa, see if a ma already exists
366             # if registering a ma, see if a sa already exists
367             if (type == "sa"):
368                 other_rec = table.resolve("ma", record.get_name())
369             elif (type == "ma"):
370                 other_rec = table.resolve("sa", record.get_name())
371
372             if other_rec:
373                 print "linking ma and sa to the same plc site"
374                 pointer = other_rec[0].get_pointer()
375             else:
376                 geni_fields_to_pl_fields(type, name, geni_fields, site_fields)
377                 print "adding site with fields", site_fields
378                 pointer = self.shell.AddSite(self.pl_auth, site_fields)
379
380             record.set_pointer(pointer)
381
382         elif (type == "slice"):
383             geni_fields = record.get_geni_info()
384             slice_fields = record.get_pl_info()
385
386             geni_fields_to_pl_fields(type, name, geni_fields, slice_fields)
387
388             pointer = self.shell.AddSlice(self.pl_auth, slice_fields)
389             record.set_pointer(pointer)
390
391         elif (type == "user"):
392             geni_fields = record.get_geni_info()
393             user_fields = record.get_pl_info()
394
395             geni_fields_to_pl_fields(type, name, geni_fields, user_fields)
396
397             pointer = self.shell.AddPerson(self.pl_auth, user_fields)
398             record.set_pointer(pointer)
399
400         elif (type == "node"):
401             geni_fields = record.get_geni_info()
402             node_fields = record.get_pl_info()
403
404             geni_fields_to_pl_fields(type, name, geni_fields, node_fields)
405
406             login_base = hrn_to_pl_login_base(auth_name)
407
408             print "calling addnode with", login_base, node_fields
409             pointer = self.shell.AddNode(self.pl_auth, login_base, node_fields)
410             record.set_pointer(pointer)
411
412         else:
413             raise UnknownGeniType(type)
414
415         table.insert(record)
416
417         return record.get_gid_object().save_to_string(save_parents=True)
418
419     ##
420     # GENI API: remove
421     #
422     # Remove an object from the registry. If the object represents a PLC object,
423     # then the PLC records will also be removed.
424     #
425     # @param cred credential string
426     # @param record_dict dictionary containing record fields. The only relevant
427     #     fields of the record are 'name' and 'type', which are used to lookup
428     #     the current copy of the record in the Geni database, to make sure
429     #     that the appopriate record is removed.
430
431     def remove(self, cred, record_dict):
432         self.decode_authentication(cred, "remove")
433
434         record = GeniRecord(dict = record_dict)
435         type = record.get_type()
436
437         self.verify_object_permission(record.get_name())
438
439         auth_name = get_authority(record.get_name())
440         table = self.get_auth_table(auth_name)
441
442         # let's not trust that the caller has a well-formed record (a forged
443         # pointer field could be a disaster), so look it up ourselves
444         record_list = table.resolve(type, record.get_name())
445         if not record_list:
446             raise RecordNotFound(name)
447         record = record_list[0]
448
449         # TODO: sa, ma
450         if type == "user":
451             self.shell.DeletePerson(self.pl_auth, record.get_pointer())
452         elif type == "slice":
453             self.shell.DeleteSlice(self.pl_auth, record.get_pointer())
454         elif type == "node":
455             self.shell.DeleteNode(self.pl_auth, record.get_pointer())
456         elif (type == "sa") or (type == "ma"):
457             if (type == "sa"):
458                 other_rec = table.resolve("ma", record.get_name())
459             elif (type == "ma"):
460                 other_rec = table.resolve("sa", record.get_name())
461
462             if other_rec:
463                 # sa and ma both map to a site, so if we are deleting one
464                 # but the other still exists, then do not delete the site
465                 print "not removing site", record.get_name(), "because either sa or ma still exists"
466                 pass
467             else:
468                 print "removing site", record.get_name()
469                 self.shell.DeleteSite(self.pl_auth, record.get_pointer())
470         else:
471             raise UnknownGeniType(type)
472
473         table.remove(record)
474
475         return True
476
477     ##
478     # GENI API: Register
479     #
480     # Update an object in the registry. Currently, this only updates the
481     # PLC information associated with the record. The Geni fields (name, type,
482     # GID) are fixed.
483     #
484     # The record is expected to have the pl_info field filled in with the data
485     # that should be updated.
486     #
487     # TODO: The geni_info member of the record should be parsed and the pl_info
488     # adjusted as necessary (add/remove users from a slice, etc)
489     #
490     # @param cred credential string specifying rights of the caller
491     # @param record a record dictionary to be updated
492
493     def update(self, cred, record_dict):
494         self.decode_authentication(cred, "update")
495
496         record = GeniRecord(dict = record_dict)
497         type = record.get_type()
498
499         self.verify_object_permission(record.get_name())
500
501         auth_name = get_authority(record.get_name())
502         table = self.get_auth_table(auth_name)
503
504         # make sure the record exists
505         existing_record_list = table.resolve(type, record.get_name())
506         if not existing_record_list:
507             raise RecordNotFound(record.get_name())
508
509         existing_record = existing_record_list[0]
510         pointer = existing_record.get_pointer()
511
512         # update the PLC information that was specified with the record
513
514         if (type == "sa") or (type == "ma"):
515             self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
516
517         elif type == "slice":
518             self.shell.UpdateSlice(self.pl_auth, pointer, record.get_pl_info())
519
520         elif type == "user":
521             # SMBAKER: UpdatePerson only allows a limited set of fields to be
522             #    updated. Ideally we should have a more generic way of doing
523             #    this. I copied the field names from UpdatePerson.py...
524             update_fields = {}
525             all_fields = record.get_pl_info()
526             for key in all_fields.keys():
527                 if key in ['first_name', 'last_name', 'title', 'email',
528                            'password', 'phone', 'url', 'bio', 'accepted_aup',\r
529                            'enabled']:
530                     update_fields[key] = all_fields[key]
531             self.shell.UpdatePerson(self.pl_auth, pointer, update_fields)
532
533         elif type == "node":
534             self.shell.UpdateNode(self.pl_auth, pointer, record.get_pl_info())
535
536         else:
537             raise UnknownGeniType(type)
538
539     ##
540     # List the records in an authority. The objectGID in the supplied credential
541     # should name the authority that will be listed.
542     #
543     # TODO: List doesn't take an hrn and uses the hrn contained in the
544     #    objectGid of the credential. Does this mean the only way to list an
545     #    authority is by having a credential for that authority?
546     #
547     # @param cred credential string specifying rights of the caller
548     #
549     # @return list of record dictionaries
550     def list(self, cred):
551         self.decode_authentication(cred, "list")
552
553         auth_name = self.object_gid.get_hrn()
554         table = self.get_auth_table(auth_name)
555
556         records = table.list()
557
558         good_records = []
559         for record in records:
560             try:
561                 self.fill_record_info(record)
562                 good_records.append(record)
563             except PlanetLabRecordDoesNotExist:
564                 # silently drop the ones that are missing in PL.
565                 # is this the right thing to do?
566                 report.error("ignoring geni record " + record.get_name() + " because pl record does not exist")
567                 table.remove(record)
568
569         dicts = []
570         for record in good_records:
571             dicts.append(record.as_dict())
572
573         return dicts
574
575         return dict_list
576
577     ##
578     # Resolve a record. This is an internal version of the Resolve API call
579     # and returns records in record object format rather than dictionaries
580     # that may be sent over XMLRPC.
581     #
582     # @param type type of record to resolve (user | sa | ma | slice | node)
583     # @param name human readable name of object
584     # @param must_exist if True, throw an exception if no records are found
585     #
586     # @return a list of record objects, or an empty list []
587
588     def resolve_raw(self, type, name, must_exist=True):
589         auth_name = get_authority(name)
590
591         table = self.get_auth_table(auth_name)
592
593         records = table.resolve(type, name)
594
595         if (not records) and must_exist:
596             raise RecordNotFound(name)
597
598         good_records = []
599         for record in records:
600             try:
601                 self.fill_record_info(record)
602                 good_records.append(record)
603             except PlanetLabRecordDoesNotExist:
604                 # silently drop the ones that are missing in PL.
605                 # is this the right thing to do?
606                 report.error("ignoring geni record " + record.get_name() + " because pl record does not exist")
607                 table.remove(record)
608
609         return good_records
610
611     ##
612     # GENI API: Resolve
613     #
614     # This is a wrapper around resolve_raw that converts records objects into
615     # dictionaries before returning them to the user.
616     #
617     # @param cred credential string authorizing the caller
618     # @param name human readable name to resolve
619     #
620     # @return a list of record dictionaries, or an empty list
621
622     def resolve(self, cred, name):
623         self.decode_authentication(cred, "resolve")
624
625         records = self.resolve_raw("*", name)
626         dicts = []
627         for record in records:
628             dicts.append(record.as_dict())
629
630         return dicts
631
632     ##
633     # GENI API: get_gid
634     #
635     # Retrieve the GID for an object. This function looks up a record in the
636     # registry and returns the GID of the record if it exists.
637     # TODO: Is this function needed? It's a shortcut for Resolve()
638     #
639     # @param name hrn to look up
640     #
641     # @return the string representation of a GID object
642
643     def get_gid(self, name):
644         self.verify_object_belongs_to_me(name)
645         records = self.resolve_raw("*", name)
646         gid_string_list = []
647         for record in records:
648             gid = record.get_gid()
649             gid_string_list.append(gid.save_to_string(save_parents=True))
650         return gid_string_list
651
652     ##
653     # Determine tje rights that an object should have. The rights are entirely
654     # dependent on the type of the object. For example, users automatically
655     # get "refresh", "resolve", and "info".
656     #
657     # @param type the type of the object (user | sa | ma | slice | node)
658     # @param name human readable name of the object (not used at this time)
659     #
660     # @return RightList object containing rights
661
662     def determine_rights(self, type, name):
663         rl = RightList()
664
665         # rights seem to be somewhat redundant with the type of the credential.
666         # For example, a "sa" credential implies the authority right, because
667         # a sa credential cannot be issued to a user who is not an owner of
668         # the authority
669
670         if type == "user":
671             rl.add("refresh")
672             rl.add("resolve")
673             rl.add("info")
674         elif type == "sa":
675             rl.add("authority")
676         elif type == "ma":
677             rl.add("authority")
678         elif type == "slice":
679             rl.add("embed")
680             rl.add("bind")
681             rl.add("control")
682             rl.add("info")
683         elif type == "component":
684             rl.add("operator")
685
686         return rl
687
688     ##
689     # GENI API: Get_self_credential
690     #
691     # Get_self_credential a degenerate version of get_credential used by a
692     # client to get his initial credential when he doesn't have one. This is
693     # the same as get_credential(..., cred=None,...).
694     #
695     # The registry ensures that the client is the principal that is named by
696     # (type, name) by comparing the public key in the record's GID to the
697     # private key used to encrypt the client-side of the HTTPS connection. Thus
698     # it is impossible for one principal to retrieve another principal's
699     # credential without having the appropriate private key.
700     #
701     # @param type type of object (user | slice | sa | ma | node
702     # @param name human readable name of object
703     #
704     # @return the string representation of a credential object
705
706     def get_self_credential(self, type, name):
707         self.verify_object_belongs_to_me(name)
708
709         auth_hrn = get_authority(name)
710         auth_info = self.get_auth_info(auth_hrn)
711
712         # find a record that matches
713         records = self.resolve_raw(type, name, must_exist=True)
714         record = records[0]
715
716         gid = record.get_gid_object()
717         peer_cert = self.server.peer_cert
718         if not peer_cert.is_pubkey(gid.get_pubkey()):
719            raise ConnectionKeyGIDMismatch(gid.get_subject())
720
721         # create the credential
722         gid = record.get_gid_object()
723         cred = Credential(subject = gid.get_subject())
724         cred.set_gid_caller(gid)
725         cred.set_gid_object(gid)
726         cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
727         cred.set_pubkey(gid.get_pubkey())
728
729         rl = self.determine_rights(type, name)
730         cred.set_privileges(rl)
731
732         cred.set_parent(AuthHierarchy.get_auth_cred(auth_hrn))
733
734         cred.encode()
735         cred.sign()
736
737         return cred.save_to_string(save_parents=True)
738
739     ##
740     # GENI API: Get_credential
741     #
742     # Retrieve a credential for an object.
743     #
744     # If cred==None, then the behavior reverts to get_self_credential()
745     #
746     # @param cred credential object specifying rights of the caller
747     # @param type type of object (user | slice | sa | ma | node)
748     # @param name human readable name of object
749     #
750     # @return the string representation of a credental object
751
752     def get_credential(self, cred, type, name):
753         if not cred:
754             return get_self_credential(self, type, name)
755
756         self.decode_authentication(cred, "getcredential")
757
758         self.verify_object_belongs_to_me(name)
759
760         auth_hrn = get_authority(name)
761         auth_info = self.get_auth_info(auth_hrn)
762
763         records = self.resolve_raw(type, name, must_exist=True)
764         record = records[0]
765
766         # TODO: Check permission that self.client_cred can access the object
767
768         object_gid = record.get_gid_object()
769         new_cred = Credential(subject = object_gid.get_subject())
770         new_cred.set_gid_caller(self.client_gid)
771         new_cred.set_gid_object(object_gid)
772         new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
773         new_cred.set_pubkey(object_gid.get_pubkey())
774
775         rl = self.determine_rights(type, name)
776         new_cred.set_privileges(rl)
777
778         new_cred.set_parent(AuthHierarchy.get_auth_cred(auth_hrn))
779
780         new_cred.encode()
781         new_cred.sign()
782
783         return new_cred.save_to_string(save_parents=True)
784
785     ##
786     # GENI_API: Create_gid
787     #
788     # Create a new GID. For MAs and SAs that are physically located on the
789     # registry, this allows a owner/operator/PI to create a new GID and have it
790     # signed by his respective authority.
791     #
792     # @param cred credential of caller
793     # @param name hrn for new GID
794     # @param uuid unique identifier for new GID
795     # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
796     #
797     # @return the string representation of a GID object
798
799     def create_gid(self, cred, name, uuid, pubkey_str):
800         self.decode_authentication(cred, "getcredential")
801
802         self.verify_object_belongs_to_me(name)
803
804         self.verify_object_permission(name)
805
806         if uuid == None:
807             uuid = create_uuid()
808
809         pkey = Keypair()
810         pkey.load_pubkey_from_string(pubkey_str)
811         gid = AuthHierarchy.create_gid(name, uuid, pkey)
812
813         return gid.save_to_string(save_parents=True)
814