always invoke caller_my_access_tag
[plcapi.git] / PLC / Accessors / Factory.py
index cd526cc..b1ef299 100644 (file)
@@ -1,13 +1,14 @@
+#
 # Thierry Parmentelat - INRIA
 # Thierry Parmentelat - INRIA
-# $Id$
-
+#
 from types import NoneType
 
 from types import NoneType
 
-from PLC.Method import Method
+from PLC.Faults import *
+
 from PLC.Auth import Auth
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth
 from PLC.Parameter import Parameter, Mixed
-
-from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Accessor import Accessor, AccessorSingleton
 
 from PLC.Nodes import Nodes, Node
 from PLC.NodeTags import NodeTags, NodeTag
 
 from PLC.Nodes import Nodes, Node
 from PLC.NodeTags import NodeTags, NodeTag
@@ -15,48 +16,64 @@ from PLC.Interfaces import Interfaces, Interface
 from PLC.InterfaceTags import InterfaceTags, InterfaceTag
 from PLC.Slices import Slices, Slice
 from PLC.SliceTags import SliceTags, SliceTag
 from PLC.InterfaceTags import InterfaceTags, InterfaceTag
 from PLC.Slices import Slices, Slice
 from PLC.SliceTags import SliceTags, SliceTag
+from PLC.Sites import Sites, Site
+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
-
-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'},
                              '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'},
                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
                                   'secondary_key' : 'ip'},
-                     Slice: {'table_class' : Slices, 
+                     Slice: {'table_class' : Slices,
                              'joins_class': SliceTags, 'join_class': SliceTag,
                              'joins_class': SliceTags, 'join_class': SliceTag,
+                             'secondary_key':'name'},
+                     Site: {'table_class' : Sites,
+                             'joins_class': SiteTags, 'join_class': SiteTag,
                              'secondary_key':'login_base'},
                              'secondary_key':'login_base'},
-#                     Ilink : xxx
+                     Person: {'table_class' : Persons,
+                             'joins_class': PersonTags, 'join_class': PersonTag,
+                             'secondary_key':'email'},
                      }
 
 # xxx probably defined someplace else
                      }
 
 # 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
 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
 #
 # 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
 #
 #   through the Add/Get/Update methods as a native field
 #
-# note: tag_min_role_id gets attached to the tagtype instance, 
+# note: roles get attached to the tagtype instance,
 # while get_roles and set_roles get attached to the created methods
 # this might need a cleanup
 # 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
+# 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 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
+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__
     if objclass not in taggable_classes:
         try:
             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
@@ -70,23 +87,21 @@ def define_accessors (module, objclass, methodsuffix, tagname,
     classname=objclass.__name__
     get_name = "Get" + classname + methodsuffix
     set_name = "Set" + classname + methodsuffix
     classname=objclass.__name__
     get_name = "Get" + classname + methodsuffix
     set_name = "Set" + classname + methodsuffix
+    locator_name = "Locate" + classname + methodsuffix
 
 
-    # create method objects under PLC.Method.Method
+    # accessor method objects under PLC.Method.Method
     get_class = type (get_name, (Method,),
                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
                            (classname,tagname)})
     set_class = type (set_name, (Method,),
                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
                            (classname,tagname)})
     get_class = type (get_name, (Method,),
                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
                            (classname,tagname)})
     set_class = type (set_name, (Method,),
                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
                            (classname,tagname)})
-    # accepts 
+
+    # accepts
     get_accepts = [ Auth () ]
     primary_key=objclass.primary_key
     get_accepts = [ Auth () ]
     primary_key=objclass.primary_key
-    try:
-        secondary_key = taggable_classes[objclass]['secondary_key']
-        get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
-    except:
-        secondary_key = None
-        get_accepts += [ objclass.fields[primary_key] ]
+    secondary_key = taggable_classes[objclass]['secondary_key']
+    get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
     # for set, idem set of arguments + one additional arg, the new value
     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
 
     # for set, idem set of arguments + one additional arg, the new value
     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
 
@@ -98,24 +113,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,'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,'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']
 
     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):
+        return self.locate_or_create_tag (tagname=tagname,
+                                          category=category,
+                                          description=description,
+                                          roles=set_roles)
+
+    # attach it to the Accessor class
+    setattr(Accessor,locator_name,locate_or_create_tag)
+
     # body of the get method
     def get_call (self, auth, id_or_name):
     # body of the get method
     def get_call (self, auth, id_or_name):
-        # search the tagtype - xxx - might need a cache
-        tag_types = TagTypes (self.api, {'tagname': tagname})
-        if not tag_types:
-            return None
-        tag_type_id = tag_types[0]['tag_type_id']
+        # locate the tag, see above
+        locator = getattr(Accessor,locator_name)
+        tag_type = 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):
             filter[primary_key]=id_or_name
         filter = {'tag_type_id':tag_type_id}
         if isinstance (id_or_name,int):
             filter[primary_key]=id_or_name
@@ -123,7 +151,7 @@ def define_accessors (module, objclass, methodsuffix, tagname,
             filter[secondary_key]=id_or_name
         joins = joins_class (self.api,filter,['value'])
         if not joins:
             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']
             return None
         else:
             return joins[0]['value']
@@ -131,33 +159,33 @@ def define_accessors (module, objclass, methodsuffix, tagname,
     # attach it
     setattr (get_class,"call",get_call)
 
     # 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}
     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])
+# 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)
         if not objs:
             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
-        primary_id = objs[0][primary_key]
-                           
-        # search tag type & create if needed
-        tag_types = TagTypes (self.api, {'tagname':tagname})
-        if tag_types:
-            tag_type = tag_types[0]
-        else:
-            # not found: create it
-            tag_type_fields = {'tagname':tagname, 
-                               'category' :  category,
-                               'description' : description,
-                               'min_role_id': tag_min_role_id}
-            tag_type = TagType (self.api, tag_type_fields)
-            tag_type.sync()
+        # the object being tagged
+        obj=objs[0]
+        primary_id = obj[primary_key]
+
+        # locate the tag, see above
+        locator = getattr(Accessor,locator_name)
+        tag_type = locator(AccessorSingleton(self.api))
         tag_type_id = tag_type['tag_type_id']
 
         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
         filter = {'tag_type_id':tag_type_id}
         if isinstance (id_or_name,int):
             filter[primary_key]=id_or_name
@@ -180,6 +208,15 @@ def define_accessors (module, objclass, methodsuffix, tagname,
             if joins:
                 join=joins[0]
                 join.delete()
             if joins:
                 join=joins[0]
                 join.delete()
+        # log it
+        self.event_objects= { objclass.__name__ : [primary_id] }
+        self.message=objclass.__name__
+        if secondary_key in objs[0]:
+            self.message += " %s "%objs[0][secondary_key]
+        else:
+            self.message += " %d "%objs[0][primary_key]
+        self.message += "updated"
+        return value
 
     # attach it
     setattr (set_class,"call",set_call)
 
     # attach it
     setattr (set_class,"call",set_call)
@@ -194,4 +231,3 @@ def define_accessors (module, objclass, methodsuffix, tagname,
         methods=[]
     methods += [get_name,set_name]
     setattr(module,'methods',methods)
         methods=[]
     methods += [get_name,set_name]
     setattr(module,'methods',methods)
-