all tag types use permission helpers in AuthorizeHelpers
[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 hasattr(objclass,'caller_may_write_tag'):
185             obj.caller_may_write_tag (self.api,self.caller,tag_type)
186
187         # locate the join object (e.g. NodeTag or similar)
188         filter = {'tag_type_id':tag_type_id}
189         if isinstance (id_or_name,int):
190             filter[primary_key]=id_or_name
191         else:
192             filter[secondary_key]=id_or_name
193         joins = joins_class (self.api,filter)
194         # setting to something non void
195         if value is not None:
196             if not joins:
197                 join = join_class (self.api)
198                 join['tag_type_id']=tag_type_id
199                 join[primary_key]=primary_id
200                 join['value']=value
201                 join.sync()
202             else:
203                 joins[0]['value']=value
204                 joins[0].sync()
205         # providing an empty value means clean up
206         else:
207             if joins:
208                 join=joins[0]
209                 join.delete()
210         # log it
211         self.event_objects= { objclass.__name__ : [primary_id] }
212         self.message=objclass.__name__
213         if secondary_key in objs[0]:
214             self.message += " %s "%objs[0][secondary_key]
215         else:
216             self.message += " %d "%objs[0][primary_key]
217         self.message += "updated"
218         return value
219
220     # attach it
221     setattr (set_class,"call",set_call)
222
223     # define in module
224     setattr(module,get_name,get_class)
225     setattr(module,set_name,set_class)
226     # add in <module>.methods
227     try:
228         methods=getattr(module,'methods')
229     except:
230         methods=[]
231     methods += [get_name,set_name]
232     setattr(module,'methods',methods)