-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgadgetHandler.py
389 lines (340 loc) · 14.6 KB
/
gadgetHandler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
'''
Copyright 2011 Acknack Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''
'''
Methods that handle the interaction between gadgets and robot
'''
import base64
import logging
import config
from dbtools import sessionTools
from dbtools import settingsTools
from dbtools import waveTools
import emailInterface
from permission import rawTypes as pt_raw
from security import sessionCreation
import utils
from waveapi import simplejson
from google.appengine.api import mail
from google.appengine.ext import deferred
def onUserChangesSettings(event, wavelet, add_gadget):
'''
Deals with version two of the add participants gadget changing. Changes
public settings etc
@param event: the event that triggered the gadget state change
@param wavelet: the wavelet the gadget lives in
@param add_gadget: the add participants gadget retrieved from the wave
'''
logging.info("Gadget state changed (v2)")
gadgetWrapper = addParticipantsGadgetWrapper(add_gadget)
#Process incoming requests
incomingRequests = gadgetWrapper.getIncomingRequests()
logging.info("Found " + str(len(incomingRequests)) + " requests")
for user, request in incomingRequests.items():
action = request.get("action", None)
if action == "addEmail":
_addEmailUser(event, wavelet, gadgetWrapper, user, request)
elif action == "deleteEmail":
_deleteEmailUser(wavelet, gadgetWrapper, user, request)
elif action == "changePublic":
_changePublicSettings(wavelet, gadgetWrapper, user, request)
#Process incoming participant profiles
_saveParticipantProfiles(wavelet, gadgetWrapper)
#Write changes back to gadget
gadgetWrapper.reconstructGadgetState()
def _saveParticipantProfiles(wavelet, gadgetWrapper):
'''
Saves the participant profiles state to the datastore
@param wavelet: the wavelet with the gadget
@param gadgetWrapper: the object wrapping the gadgets state
'''
if gadgetWrapper.getParticipantDetailsState() == "updated":
participantDetails = gadgetWrapper.getParticipantDetails()
waveMeta = waveTools.createOrUpdate( wavelet.wave_id,
wavelet.wavelet_id,
participant_profiles=participantDetails)
gadgetWrapper.setParticipantDetailsState(None)
def _addEmailUser(event, wavelet, gadgetWrapper, user, request):
'''
Adds an email particpant to the wave and updates the gadget to reflect
@param event: the event that triggered the changed gadget
@param wavelet: the wavelet where the request originated from
@param user: the user that made the request
@param gadgetWrapper: the object that wraps this gadgets state
@param request: the request to process
'''
logging.info("Add email user request")
email = request.get('params').get("email") or None
message = request.get('params').get('message') or ""
if email:
logging.info("Creating new session for " + email)
url = sessionCreation.generateNewUser( wavelet.wave_id,
wavelet.wavelet_id,
email,
pt_raw.RW['READ_WRITE'])
deferred.defer(
emailInterface.sendFirstNotificationEmail,
url,
wavelet.wave_id,
wavelet.wavelet_id,
email,
event.modified_by,
wavelet.title,
message=message)
wavelet.add_proxying_participant(utils.getProxyForFromEmail(email))
gadgetWrapper.addEmailParticipant(email)
gadgetWrapper.deleteIncomingRequest(user)
def _deleteEmailUser(wavelet, gadgetWrapper, user, request):
'''
Removes an email participant from the wave and updates the gadget to reflect
@param wavelet: the wavelet where the request originated from
@param user: the user that made the request
@param gadgetWrapper: the object that wraps this gadgets state
@param request: the request to process
'''
logging.info("Delete email user request")
email = request.get('params').get("email") or None
if email:
logging.info("Deleting " + email)
settingsTools.changeRWPermission( pt_raw.RW['DELETED'],
key={ 'wave_id' : wavelet.wave_id,
'wavelet_id': wavelet.wavelet_id,
'email' : email})
gadgetWrapper.removeEmailParticipant(email)
gadgetWrapper.deleteIncomingRequest(user)
def _changePublicSettings(wavelet, gadgetWrapper, user, request):
'''
Changes the public settings for the wave and updates the gadget to reflect
@param wavelet: the wavelet where the request originated from
@param user: the user that made the request
@param gadgetWrapper: the object that wraps this gadgets state
@param request: the request to process
'''
logging.info("Change public settings request")
#Extract values from the incoming json
isPublic = None
isReadOnly = None
params = request.get("params", None)
if params:
isPublic = params.get("isPublic", None)
isReadOnly = params.get("isReadOnly", None)
if isPublic == None:
isPublic = False
if isReadOnly == None:
isReadOnly = True
#Convert values into permissions
if isReadOnly:
rw_permission = pt_raw.RW['READ']
else:
rw_permission = pt_raw.RW['READ_WRITE']
if not isPublic:
rw_permission = pt_raw.RW['DELETED']
url = sessionCreation.generateNewUser( wavelet.wave_id,
wavelet.wavelet_id,
config.PUBLIC_EMAIL,
rw_permission)
gadgetWrapper.changePublicSettings(isPublic, isReadOnly, url)
gadgetWrapper.deleteIncomingRequest(user)
def constructInitialState(wavelet):
'''
Constructs the initial gadget state. This is used purely for migration from
the v1 gadget to v2 gadget. So it returns a list of email users in the
correct format for the gadget
@param wavelet: the wavelet where the gadget will live
@return a dictionary containing the key values of the initial state
'''
sessions = sessionTools.fetch(wavelet.wave_id, wavelet.wavelet_id)
#Form the email list
email_list = []
public_session = None
for session in sessions:
if sessionTools.isPublic(session):
public_session = session
else:
email_list.append(session.email)
#Form public settings
public = {}
isPublic = False
isReadOnly = True
try:
public_settings = settingsTools.get(public_session)
rw_permission = public_settings.rw_permission
if rw_permission == pt_raw.RW['READ']:
isPublic = True
isReadOnly = True
elif rw_permission == pt_raw.RW['READ_WRITE']:
isPublic = True
isReadOnly = False
except:
#Just means public settings could not be found. Defaults will be used
pass
public.update({'isPublic' : isPublic, 'isReadOnly' : isReadOnly});
output = base64.b64encode(simplejson.dumps({'emailParticipants' : email_list,
'public' : public}))
return {'state' : output, 'participantDetailsState' : 'fetch'}
def requestParticipantProfilesUpdate(add_gadget):
'''
Sends a request to the gadget to resend the participant profiles
@param add_gadget: the gadget found in this wave
'''
add_gadget.update_element({'participantDetailsState' : 'fetch'})
def onAddParticipantsChangedV1(event, wavelet, add_gadget):
'''
Deals with the add participants gadget changing, subscribes wave users etc
@param event: the event that triggered the gadget state change
@param wavelet: the wavelet the gadget lives in
@param add_gadget: the add participants gadget retrieved from the wave
'''
#Fetch the values from the gadget
participants_json = add_gadget.get("EMAIL-PARTICIPANTS", None)
if participants_json:
participants_json = base64.b64decode(participants_json)
else:
participants_json = "[]"
participants = simplejson.loads(participants_json)
new_participant = add_gadget.get("ADD-PARTICIPANT", None)
if new_participant:
logging.info("Subscribing new e-mail user: " + new_participant)
#Check the email address appears valid. Fail silently if not. The js
#should check and report errors before sending. This prevents hackers
if not mail.is_email_valid(new_participant):
logging.info(new_participant + " not subscribed. E-mail address not valid")
return
#Only update if the user is new
if not new_participant in participants:
deferred.defer(
emailInterface.sendFirstNotificationEmail,
sessionCreation.generateNewUser(wavelet.wave_id,
wavelet.wavelet_id,
new_participant,
pt_raw.RW['READ_WRITE']),
wavelet.wave_id,
wavelet.wavelet_id,
new_participant,
event.modified_by,
wavelet.title)
participants.append(new_participant)
wavelet.add_proxying_participant(utils.getProxyForFromEmail(new_participant))
#Update the gadget
participants_json = simplejson.dumps(participants)
add_gadget.update_element({ "ADD-PARTICIPANT": None,
"EMAIL-PARTICIPANTS": base64.b64encode(participants_json)})
class addParticipantsGadgetWrapper:
'''
Object that models the add participants gadget in a pythonic way
'''
def __init__(self, gadget):
'''
@param gadget: the gadget instance
'''
self._gadget = gadget
self._default_state = {"emailParticipants" : [], 'public':{'isPublic':False, 'isReadOnly':True}}
self._parseGadgetState()
def _loads(self, obj):
'''
Loads an object from the gadget state if it is b64/json
@param obj: the raw string to load
@return the python object
'''
if not obj:
return None
return simplejson.loads(base64.b64decode(obj))
def _dumps(self, obj):
'''
Writes an object to a string so it can be stored in the gadget state
@param obj: the object to write to string
@return a b64/json string representing the object
'''
if not obj:
return ""
return base64.b64encode(simplejson.dumps(obj))
def _parseGadgetState(self):
'''
Parses the state variable into a python data structure and stores in
the object
'''
self._participantDetails = self._loads(self._gadget.get("participantDetails", None)) or {}
self._state = self._loads(self._gadget.get("state", None)) or {}
self._participantDetailsState = self._gadget.get("participantDetailsState", None)
self._parseIncomingRequests()
def _parseIncomingRequests(self):
'''
Parses the incoming requests and stores in the object
'''
commands = {}
for command in self._gadget.keys():
if command == "participantDetails" or command == "participantDetailsState" or command == "state":
continue
commands.update({command : self._loads(self._gadget.get(command, None))})
self._requests = commands
def getParticipantDetails(self):
'''
@return partipantDetails object or {} if it is not present
'''
return self._participantDetails or {}
def getParticipantDetailsState(self):
'''
@return participantDetailsState variable or None if it is not present
'''
return self._participantDetailsState
def setParticipantDetailsState(self, state):
'''
@param state: the new participantDetailsState variable or None to delete
'''
self._participantDetailsState = state
def addEmailParticipant(self, email):
'''
@param email: the email address to add
'''
if not self._state:
self._state = self._default_state
self._state['emailParticipants'].append(email)
def removeEmailParticipant(self, email):
'''
@param email: the email address to remove
'''
if self._state:
if email in self._state['emailParticipants']:
self._state['emailParticipants'].remove(email)
else:
self._state = self._default_state
def changePublicSettings(self, isPublic, isReadOnly, url):
'''
@param isPublic: the public state T/F
@param isReadOnly: the readOnlyState T/F
@param url: the public url
'''
if self._state:
self._state['public']['isPublic'] = isPublic
self._state['public']['isReadOnly'] = isReadOnly
self._state['public']['url'] = url
else:
self._state = {"emailParticipants" : [], 'public':{'isPublic':isPublic, 'isReadOnly':isReadOnly}}
def getIncomingRequests(self):
'''
@return the incoming requests object or {} if not present
'''
return self._requests
def deleteIncomingRequest(self, key):
'''
@param key: the key to remove
'''
self._requests[key] = None
def reconstructGadgetState(self):
'''
Reconstructs the gadgets state so it can be re-written to the gadget
'''
update = { "state" : self._dumps(self._state),
"participantDetailsState" : self._participantDetailsState}
for key in self._requests:
update.update({key: self._requests[key]})
self._gadget.update_element(update)