always invoke caller_my_access_tag
[plcapi.git] / PLC / Accessors / Factory.py
1 #
2 # Thierry Parmentelat - INRIA
3 #
4 from types import NoneType
5
6 from PLC.Faults import *
7
8 from PLC.Auth import Auth
9 from PLC.Parameter import Parameter, Mixed
10 from PLC.Method import Method
11 from PLC.Accessor import Accessor, AccessorSingleton
12
13 from PLC.Nodes import Nodes, Node
14 from PLC.NodeTags import NodeTags, NodeTag
15 from PLC.Interfaces import Interfaces, Interface
16 from PLC.InterfaceTags import InterfaceTags, InterfaceTag
17 from PLC.Slices import Slices, Slice
18 from PLC.SliceTags import SliceTags, SliceTag
19 from PLC.Sites import Sites, Site
20 from PLC.SiteTags import SiteTags, SiteTag
21 from PLC.Persons import Persons, Person
22 from PLC.PersonTags import PersonTags, PersonTag
23
24 # known classes : { class -> details }
25 taggable_classes = { Node : {'table_class' : Nodes,
26                              'joins_class' : NodeTags, 'join_class' : NodeTag,
27                              'secondary_key': 'hostname'},
28                      Interface : {'table_class' : Interfaces,
29                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
30                                   'secondary_key' : 'ip'},
31                      Slice: {'table_class' : Slices,
32                              'joins_class': SliceTags, 'join_class': SliceTag,
33                              'secondary_key':'name'},
34                      Site: {'table_class' : Sites,
35                              'joins_class': SiteTags, 'join_class': SiteTag,
36                              'secondary_key':'login_base'},
37                      Person: {'table_class' : Persons,
38                              'joins_class': PersonTags, 'join_class': PersonTag,
39                              'secondary_key':'email'},
40                      }
41
42 # xxx probably defined someplace else
43 admin_roles = ['admin']
44 person_roles = [ 'admin', 'pi', 'tech', 'user' ]
45 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
46 tech_roles = [ 'admin', 'pi', 'tech' ]
47
48 #
49 # generates 2 method classes:
50 # Get<classname><methodsuffix> (auth, id_or_name) -> value or None
51 # Set<classname><methodsuffix> (auth, id_or_name, value) -> value
52 # value is always a string, no cast nor typecheck for now
53 #
54 # The expose_in_api flag tells whether this tag may be handled
55 #   through the Add/Get/Update methods as a native field
56 #
57 # note: roles get attached to the tagtype instance,
58 # while get_roles and set_roles get attached to the created methods
59 # this might need a cleanup
60 #
61 # in addition a convenience method like e.g. LocateNodeArch is defined
62 # in the Accessor class; its purpose is to retrieve the tag, or to create it if needed
63
64 # Legacy NOTE:
65 # prior to plcapi-5.0-19, this used to accept an additional argument
66 # named min_role_id; this was redundant and confusing, it has been
67 # removed, we now use set_roles to restrict access on the corresponding tag
68
69 # the convention here is that methodsuffix should be mixed case, e.g. MyStuff
70 # while tagname is expected to be lowercase
71 # you then end up with e.g. GetPersonMyStuff
72 def define_accessors (module, objclass, methodsuffix, tagname,
73                       category, description,
74                       get_roles=all_roles, set_roles=admin_roles, 
75                       expose_in_api = False):
76
77     if objclass not in taggable_classes:
78         try:
79             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
80         except:
81             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
82
83     # side-effect on, say, Node.tags, if required
84     if expose_in_api:
85         getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
86
87     classname=objclass.__name__
88     get_name = "Get" + classname + methodsuffix
89     set_name = "Set" + classname + methodsuffix
90     locator_name = "Locate" + classname + methodsuffix
91
92     # accessor method objects under PLC.Method.Method
93     get_class = type (get_name, (Method,),
94                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
95                            (classname,tagname)})
96     set_class = type (set_name, (Method,),
97                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
98                            (classname,tagname)})
99
100     # accepts
101     get_accepts = [ Auth () ]
102     primary_key=objclass.primary_key
103     secondary_key = taggable_classes[objclass]['secondary_key']
104     get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
105     # for set, idem set of arguments + one additional arg, the new value
106     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
107
108     # returns
109     get_returns = Mixed (Parameter (str), Parameter(NoneType))
110     set_returns = Parameter(NoneType)
111
112     # store in classes
113     setattr(get_class,'roles',get_roles)
114     setattr(get_class,'accepts',get_accepts)
115     setattr(get_class,'returns', get_returns)
116 # that was useful for legacy method only, but we now need type_checking
117 #    setattr(get_class,'skip_type_check',True)
118
119     setattr(set_class,'roles',set_roles)
120     setattr(set_class,'accepts',set_accepts)
121     setattr(set_class,'returns', set_returns)
122 # that was useful for legacy method only, but we now need type_checking
123 #    setattr(set_class,'skip_type_check',True)
124
125     table_class = taggable_classes[objclass]['table_class']
126     joins_class = taggable_classes[objclass]['joins_class']
127     join_class = taggable_classes[objclass]['join_class']
128
129     # locate the tag and create it if needed
130     # this method is attached to the Accessor class
131     def locate_or_create_tag (self):
132         return self.locate_or_create_tag (tagname=tagname,
133                                           category=category,
134                                           description=description,
135                                           roles=set_roles)
136
137     # attach it to the Accessor class
138     setattr(Accessor,locator_name,locate_or_create_tag)
139
140     # body of the get method
141     def get_call (self, auth, id_or_name):
142         # locate the tag, see above
143         locator = getattr(Accessor,locator_name)
144         tag_type = locator(AccessorSingleton(self.api))
145         tag_type_id=tag_type['tag_type_id']
146
147         filter = {'tag_type_id':tag_type_id}
148         if isinstance (id_or_name,int):
149             filter[primary_key]=id_or_name
150         else:
151             filter[secondary_key]=id_or_name
152         joins = joins_class (self.api,filter,['value'])
153         if not joins:
154             # xxx - we return None even if id_or_name is not valid
155             return None
156         else:
157             return joins[0]['value']
158
159     # attach it
160     setattr (get_class,"call",get_call)
161
162     # body of the set method
163     def set_call (self, auth, id_or_name, value):
164         # locate the object
165         if isinstance (id_or_name, int):
166             filter={primary_key:id_or_name}
167         else:
168             filter={secondary_key:id_or_name}
169 # we need the full monty b/c of the permission system
170 #        objs = table_class(self.api, filter,[primary_key,secondary_key])
171         objs = table_class(self.api, filter)
172         if not objs:
173             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
174         # the object being tagged
175         obj=objs[0]
176         primary_id = obj[primary_key]
177
178         # locate the tag, see above
179         locator = getattr(Accessor,locator_name)
180         tag_type = locator(AccessorSingleton(self.api))
181         tag_type_id = tag_type['tag_type_id']
182
183         # check authorization
184         if not hasattr(objclass,'caller_may_write_tag'):
185             raise PLCAuthenticationFailure, "class %s misses method caller_may_write_tag"%objclass.__name__
186         obj.caller_may_write_tag (self.api,self.caller,tag_type)
187
188         # locate the join object (e.g. NodeTag or similar)
189         filter = {'tag_type_id':tag_type_id}
190         if isinstance (id_or_name,int):
191             filter[primary_key]=id_or_name
192         else:
193             filter[secondary_key]=id_or_name
194         joins = joins_class (self.api,filter)
195         # setting to something non void
196         if value is not None:
197             if not joins:
198                 join = join_class (self.api)
199                 join['tag_type_id']=tag_type_id
200                 join[primary_key]=primary_id
201                 join['value']=value
202                 join.sync()
203             else:
204                 joins[0]['value']=value
205                 joins[0].sync()
206         # providing an empty value means clean up
207         else:
208             if joins:
209                 join=joins[0]
210                 join.delete()
211         # log it
212         self.event_objects= { objclass.__name__ : [primary_id] }
213         self.message=objclass.__name__
214         if secondary_key in objs[0]:
215             self.message += " %s "%objs[0][secondary_key]
216         else:
217             self.message += " %d "%objs[0][primary_key]
218         self.message += "updated"
219         return value
220
221     # attach it
222     setattr (set_class,"call",set_call)
223
224     # define in module
225     setattr(module,get_name,get_class)
226     setattr(module,set_name,set_class)
227     # add in <module>.methods
228     try:
229         methods=getattr(module,'methods')
230     except:
231         methods=[]
232     methods += [get_name,set_name]
233     setattr(module,'methods',methods)