Merge commit 'origin/master'
[plcapi.git] / PLC / Accessors / Factory.py
index 2dd2a73..fec0d63 100644 (file)
@@ -1,7 +1,6 @@
+#
 # Thierry Parmentelat - INRIA
-# $Id$
-# $URL$
-
+#
 from types import NoneType
 
 from PLC.Faults import *
@@ -22,55 +21,70 @@ from PLC.SiteTags import SiteTags, SiteTag
 from PLC.Persons import Persons, Person
 from PLC.PersonTags import PersonTags, PersonTag
 
-# this is another story..
-#from PLC.Ilinks import Ilink
+# need to import so the core classes get decorated with caller_may_write_tag
+from PLC.AuthorizeHelpers import AuthorizeHelpers
 
-from PLC.TagTypes import TagTypes, TagType
-
-# known classes : { class -> secondary_key }
-taggable_classes = { Node : {'table_class' : Nodes, 
+# known classes : { class -> details }
+taggable_classes = { Node : {'table_class' : Nodes,
                              'joins_class' : NodeTags, 'join_class' : NodeTag,
                              'secondary_key': 'hostname'},
-                     Interface : {'table_class' : Interfaces, 
+                     Interface : {'table_class' : Interfaces,
                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
                                   'secondary_key' : 'ip'},
-                     Slice: {'table_class' : Slices, 
+                     Slice: {'table_class' : Slices,
                              'joins_class': SliceTags, 'join_class': SliceTag,
                              'secondary_key':'name'},
-                     Site: {'table_class' : Sites, 
+                     Site: {'table_class' : Sites,
                              'joins_class': SiteTags, 'join_class': SiteTag,
                              'secondary_key':'login_base'},
-                     Person: {'table_class' : Persons, 
+                     Person: {'table_class' : Persons,
                              'joins_class': PersonTags, 'join_class': PersonTag,
                              'secondary_key':'email'},
-#                     Ilink : xxx
                      }
 
 # xxx probably defined someplace else
+admin_roles = ['admin']
+person_roles = [ 'admin', 'pi', 'tech', 'user' ]
 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
 tech_roles = [ 'admin', 'pi', 'tech' ]
 
 #
 # generates 2 method classes:
 # Get<classname><methodsuffix> (auth, id_or_name) -> value or None
-# Set<classname><methodsuffix> (auth, id_or_name, value) -> None
+# Set<classname><methodsuffix> (auth, id_or_name, value) -> value
 # value is always a string, no cast nor typecheck for now
 #
-# The expose_in_api flag tells whether this tag may be handled 
+# The expose_in_api flag tells whether this tag may be handled
 #   through the Add/Get/Update methods as a native field
 #
-# note: tag_min_role_id gets attached to the tagtype instance, 
-# while get_roles and set_roles get attached to the created methods
-# this might need a cleanup
-# 
-# in addition a convenience method like e.g. LocateNodeArch is defined 
+# note: set_roles get attached as 'roles' to the tagtype instance,
+# also get_roles and set_roles get attached to the created methods
+#
+# in addition a convenience method like e.g. LocateNodeArch is defined
 # in the Accessor class; its purpose is to retrieve the tag, or to create it if needed
+# 
+# Legacy NOTE:
+# prior to plcapi-5.0-19, this used to accept an additional argument
+# named min_role_id; this was redundant and confusing, it has been
+# removed, we now use set_roles to restrict write access on the corresponding tag
+
+# the convention here is that methodsuffix should be mixed case, e.g. MyStuff
+# while tagname is expected to be lowercase
+# you then end up with e.g. GetPersonMyStuff
+
+# the entry point accepts a single class or a list of classes
+def define_accessors (module, objclasses, *args, **kwds):
+    if not isinstance(objclasses,list):
+        objclasses=[objclasses]
+    for objclass in objclasses:
+        define_accessors_ (module, objclass, *args, **kwds)
+
+# this is for one class
+def define_accessors_ (module, objclass, methodsuffix, tagname,
+                       category, description,
+                       get_roles=all_roles, set_roles=admin_roles, 
+                       expose_in_api = False):
 
-def define_accessors (module, objclass, methodsuffix, tagname, 
-                      category, description, 
-                      get_roles=['admin'], set_roles=['admin'], 
-                      tag_min_role_id=10, expose_in_api = False):
-    
     if objclass not in taggable_classes:
         try:
             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
@@ -94,7 +108,7 @@ def define_accessors (module, objclass, methodsuffix, tagname,
                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
                            (classname,tagname)})
 
-    # accepts 
+    # accepts
     get_accepts = [ Auth () ]
     primary_key=objclass.primary_key
     secondary_key = taggable_classes[objclass]['secondary_key']
@@ -110,33 +124,37 @@ def define_accessors (module, objclass, methodsuffix, tagname,
     setattr(get_class,'roles',get_roles)
     setattr(get_class,'accepts',get_accepts)
     setattr(get_class,'returns', get_returns)
-    setattr(get_class,'skip_typecheck',True)
+# that was useful for legacy method only, but we now need type_checking
+#    setattr(get_class,'skip_type_check',True)
 
     setattr(set_class,'roles',set_roles)
     setattr(set_class,'accepts',set_accepts)
     setattr(set_class,'returns', set_returns)
-    setattr(set_class,'skip_typecheck',True)
-    
+# that was useful for legacy method only, but we now need type_checking
+#    setattr(set_class,'skip_type_check',True)
+
     table_class = taggable_classes[objclass]['table_class']
     joins_class = taggable_classes[objclass]['joins_class']
     join_class = taggable_classes[objclass]['join_class']
 
     # locate the tag and create it if needed
     # this method is attached to the Accessor class
-    def locate_or_create_tag (self):
+    def tag_locator (self, enforce=False):
         return self.locate_or_create_tag (tagname=tagname,
                                           category=category,
                                           description=description,
-                                          min_role_id=tag_min_role_id)
+                                          roles=set_roles,
+                                          enforce=enforce)
 
     # attach it to the Accessor class
-    setattr(Accessor,locator_name,locate_or_create_tag)
+    Accessor.register_tag_locator(locator_name,tag_locator)
 
     # body of the get method
     def get_call (self, auth, id_or_name):
         # locate the tag, see above
-        locator = getattr(Accessor,locator_name)
-        tag_type_id = locator(AccessorSingleton(self.api))
+        tag_locator = Accessor.retrieve_tag_locator(locator_name)
+        tag_type = tag_locator(AccessorSingleton(self.api))
+        tag_type_id=tag_type['tag_type_id']
 
         filter = {'tag_type_id':tag_type_id}
         if isinstance (id_or_name,int):
@@ -145,7 +163,7 @@ def define_accessors (module, objclass, methodsuffix, tagname,
             filter[secondary_key]=id_or_name
         joins = joins_class (self.api,filter,['value'])
         if not joins:
-            # xxx - we return None even if id_or_name is not valid 
+            # xxx - we return None even if id_or_name is not valid
             return None
         else:
             return joins[0]['value']
@@ -153,23 +171,33 @@ def define_accessors (module, objclass, methodsuffix, tagname,
     # attach it
     setattr (get_class,"call",get_call)
 
-    # body of the set method 
+    # body of the set method
     def set_call (self, auth, id_or_name, value):
         # locate the object
         if isinstance (id_or_name, int):
             filter={primary_key:id_or_name}
         else:
             filter={secondary_key:id_or_name}
-        objs = table_class(self.api, filter,[primary_key,secondary_key])
+# we need the full monty b/c of the permission system
+#        objs = table_class(self.api, filter,[primary_key,secondary_key])
+        objs = table_class(self.api, filter)
         if not objs:
             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
-        primary_id = objs[0][primary_key]
-                           
+        # the object being tagged
+        obj=objs[0]
+        primary_id = obj[primary_key]
+
         # locate the tag, see above
-        locator = getattr(Accessor,locator_name)
-        tag_type_id = locator(AccessorSingleton(self.api))
+        tag_locator = Accessor.retrieve_tag_locator(locator_name)
+        tag_type = tag_locator(AccessorSingleton(self.api))
+        tag_type_id = tag_type['tag_type_id']
 
-        # locate the join object (e.g. NodeTag, SliceTag or InterfaceTag)
+        # check authorization
+        if not hasattr(objclass,'caller_may_write_tag'):
+            raise PLCAuthenticationFailure, "class %s misses method caller_may_write_tag"%objclass.__name__
+        obj.caller_may_write_tag (self.api,self.caller,tag_type)
+
+        # locate the join object (e.g. NodeTag or similar)
         filter = {'tag_type_id':tag_type_id}
         if isinstance (id_or_name,int):
             filter[primary_key]=id_or_name
@@ -200,6 +228,7 @@ def define_accessors (module, objclass, methodsuffix, tagname,
         else:
             self.message += " %d "%objs[0][primary_key]
         self.message += "updated"
+        return value
 
     # attach it
     setattr (set_class,"call",set_call)
@@ -214,4 +243,3 @@ def define_accessors (module, objclass, methodsuffix, tagname,
         methods=[]
     methods += [get_name,set_name]
     setattr(module,'methods',methods)
-