def sync_trigger_partial_sync_callback(req, service): svc = Service.FromID(service) if req.method == "POST": # if whe're using decathlon services, force resync # Get users ids list, depending of services response = svc.ExternalIDsForPartialSyncTrigger(req) _sync = Sync() # Get users _id list from external ID users_to_sync = _sync.getUsersIDFromExternalId(response, service) if not users_to_sync: return HttpResponse(status=401) else: for user in users_to_sync: # For each users, if we can sync now if "LastSynchronization" in user and user["LastSynchronization"] is not None and datetime.utcnow() - \ user["LastSynchronization"] < _sync.MinimumSyncInterval: return HttpResponse(status=403) exhaustive = None if "LastSynchronization" in user and user["LastSynchronization"] is not None and datetime.utcnow() - \ user["LastSynchronization"] > _sync.MaximumIntervalBeforeExhaustiveSync: exhaustive = True # Force immadiate sync _sync.ScheduleImmediateSync(user, exhaustive) return HttpResponse(status=204) elif req.method == "GET": return svc.PartialSyncTriggerGET(req) else: return HttpResponse(status=400)
def account_setconfig(req): if not req.user: return HttpResponse(status=403) data = json.loads(req.body.decode("utf-8")) if data["sync_skip_before"] and len(data["sync_skip_before"]): data["sync_skip_before"] = dateutil.parser.parse( data["sync_skip_before"]) User.SetConfiguration(req.user, data) _sync = Sync() _sync.SetNextSyncIsExhaustive(req.user, True) return HttpResponse()
def sync_clear_errorgroup(req, service, group): _sync = Sync() if not req.user: return HttpResponse(status=401) rec = User.GetConnectionRecord(req.user, service) if not rec: return HttpResponse(status=404) # Prevent this becoming a vehicle for rapid synchronization to_clear_count = 0 for x in rec.SyncErrors: if "UserException" in x and "ClearGroup" in x["UserException"] and x[ "UserException"]["ClearGroup"] == group: to_clear_count += 1 _sync = Sync() if to_clear_count > 0: db.connections.update_one( {"_id": rec._id}, {"$pull": { "SyncErrors": { "UserException_ClearGroup": group } }}) db.users.update_one( {"_id": req.user["_id"]}, {'$inc': { "BlockingSyncErrorCount": -to_clear_count }} ) # In the interests of data integrity, update the summary counts immediately as opposed to waiting for a sync to complete. _sync.ScheduleImmediateSync( req.user, True ) # And schedule them for an immediate full resynchronization, so the now-unblocked services can be brought up to speed. return HttpResponse() return HttpResponse() return HttpResponse(status=404)
def sync_schedule_immediate(req): _sync = Sync() if not req.user: return HttpResponse(status=401) if "LastSynchronization" in req.user and req.user[ "LastSynchronization"] is not None and datetime.utcnow( ) - req.user["LastSynchronization"] < _sync.MinimumSyncInterval: return HttpResponse(status=403) exhaustive = None if "LastSynchronization" in req.user and req.user[ "LastSynchronization"] is not None and datetime.utcnow() - req.user[ "LastSynchronization"] > _sync.MaximumIntervalBeforeExhaustiveSync: exhaustive = True _sync.ScheduleImmediateSync(req.user, exhaustive) return HttpResponse()
def config(req): in_diagnostics = "diagnostics" in req.path _sync = Sync() return { "config": { "minimumSyncInterval": _sync.MinimumSyncInterval.seconds, "siteVer": SITE_VER, "pp": { "url": PP_WEBSCR, "buttonId": PP_BUTTON_ID }, "connection_services": CONNECTION_SERVICES, "soft_launch": SOFT_LAUNCH_SERVICES, "disabled_services": DISABLED_SERVICES, "withdrawn_services": WITHDRAWN_SERVICES, "in_diagnostics": in_diagnostics }, "hidden_infotips": req.COOKIES.get("infotip_hide", None) }
def _assocPaymentLikeObject(user, collection, payment_like_object, schedule_now, skip_deassoc=False): # Since I seem to have taken this duck-typing quite far # First, deassociate payment ids from other accounts that may be using them if "_id" in payment_like_object and not skip_deassoc: db.users.update_many( {}, {"$pull": { collection: { "_id": payment_like_object["_id"] } }}) # Then, attach to us db.users.update_one({"_id": ObjectId(user["_id"])}, {"$addToSet": { collection: payment_like_object }}) if schedule_now: _sync = Sync() _sync.ScheduleImmediateSync(user)
}, upsert=True, return_document=ReturnDocument.AFTER) heartbeat_rec_id = heartbeat_rec["_id"] patch_requests_with_default_timeout(timeout=60) if isinstance(settings.HTTP_SOURCE_ADDR, list): settings.HTTP_SOURCE_ADDR = settings.HTTP_SOURCE_ADDR[ settings.WORKER_INDEX % len(settings.HTTP_SOURCE_ADDR)] patch_requests_source_address((settings.HTTP_SOURCE_ADDR, 0)) # We defer including the main body of the application till here so the settings aren't captured before we've set them up. # The better way would be to defer initializing services until they're requested, but it's 10:30 and this will work just as well. from tapiriik.sync import Sync sync_heartbeat("ready") worker_message("ready") Sync = Sync() Sync.PerformGlobalSync(heartbeat_callback=sync_heartbeat, version=WorkerVersion) worker_message("shutting down cleanly") db.sync_workers.delete_one({"_id": heartbeat_rec_id}) close_connections() worker_message("shut down") logging.info("-----[ ENDING SYNC_WORKER ]-----") sys.stdout.flush()
def ConnectService(user, serviceRecord): from tapiriik.services import Service, UserExceptionType existingUser = db.users.find_one({ "_id": { '$ne': ObjectId(user["_id"]) }, "ConnectedServices.ID": ObjectId(serviceRecord._id) }) if "ConnectedServices" not in user: user["ConnectedServices"] = [] delta = False if existingUser is not None: # merge merge merge # Don't let the user end up with two services of the same type, ever # It's not fully supported, plus it's caused all sorts of trauma in the past. # Note that this will discard the new serviceRecord connection if an existing one exists on the other account # ...which isn't the end of the world, compared to screwing around asking the user which they wanted to keep. for to_merge_service in existingUser["ConnectedServices"]: if len([ x for x in user["ConnectedServices"] if x["Service"] == to_merge_service["Service"] ]) == 0: user["ConnectedServices"].append(to_merge_service) # There's got to be some 1-liner to do this merge if "Payments" in existingUser: if "Payments" not in user: user["Payments"] = [] user["Payments"] += existingUser["Payments"] if "Promos" in existingUser: if "Promos" not in user: user["Promos"] = [] user["Promos"] += existingUser["Promos"] if "ExternalPayments" in existingUser: if "ExternalPayments" not in user: user["ExternalPayments"] = [] user["ExternalPayments"] += existingUser["ExternalPayments"] if "FlowExceptions" in existingUser: if "FlowExceptions" not in user: user["FlowExceptions"] = [] user["FlowExceptions"] += existingUser["FlowExceptions"] user["Email"] = user["Email"] if "Email" in user and user[ "Email"] is not None else ( existingUser["Email"] if "Email" in existingUser else None) user["NonblockingSyncErrorCount"] = ( user["NonblockingSyncErrorCount"] if "NonblockingSyncErrorCount" in user and user["NonblockingSyncErrorCount"] is not None else 0 ) + (existingUser["NonblockingSyncErrorCount"] if "NonblockingSyncErrorCount" in existingUser and existingUser["NonblockingSyncErrorCount"] is not None else 0) user["BlockingSyncErrorCount"] = ( user["BlockingSyncErrorCount"] if "BlockingSyncErrorCount" in user and user["BlockingSyncErrorCount"] is not None else 0 ) + (existingUser["BlockingSyncErrorCount"] if "BlockingSyncErrorCount" in existingUser and existingUser["BlockingSyncErrorCount"] is not None else 0) user["SyncExclusionCount"] = ( user["SyncExclusionCount"] if "SyncExclusionCount" in user and user["SyncExclusionCount"] is not None else 0) + ( existingUser["SyncExclusionCount"] if "SyncExclusionCount" in existingUser and existingUser["SyncExclusionCount"] is not None else 0) user[ "Created"] = user["Created"] if user["Created"] < existingUser[ "Created"] else existingUser["Created"] if "AncestorAccounts" not in user: user["AncestorAccounts"] = [] user["AncestorAccounts"] += existingUser["AncestorAccounts"] if "AncestorAccounts" in existingUser else [] user["AncestorAccounts"] += [existingUser["_id"]] user["Timezone"] = user["Timezone"] if "Timezone" in user and user[ "Timezone"] else (existingUser["Timezone"] if "Timezone" in existingUser else None) user["CreationIP"] = user[ "CreationIP"] if "CreationIP" in user and user[ "CreationIP"] else (existingUser["CreationIP"] if "CreationIP" in existingUser else None) existing_config = existingUser[ "Config"] if "Config" in existingUser else {} existing_config.update(user["Config"] if "Config" in user else {}) user["Config"] = existing_config delta = True db.users.delete_one({"_id": existingUser["_id"]}) else: if serviceRecord._id not in [ x["ID"] for x in user["ConnectedServices"] ]: # we might be connecting a second account for the same service for duplicateConn in [ x for x in user["ConnectedServices"] if x["Service"] == serviceRecord.Service.ID ]: dupeRecord = User.GetConnectionRecord( user, serviceRecord.Service.ID ) # this'll just pick the first connection of type, but we repeat the right # of times anyways Service.DeleteServiceRecord(dupeRecord) # We used to call DisconnectService() here, but the results of that call were getting overwritten, which was unfortunate. user["ConnectedServices"] = [ x for x in user["ConnectedServices"] if x["Service"] != serviceRecord.Service.ID ] user["ConnectedServices"].append({ "Service": serviceRecord.Service.ID, "ID": serviceRecord._id }) delta = True _sync = Sync() db.users.update_one({"_id": user["_id"]}, {'$set': user}) if delta or ( hasattr(serviceRecord, "SyncErrors") and len(serviceRecord.SyncErrors) > 0 ): # also schedule an immediate sync if there is an outstanding error (i.e. user reconnected) db.connections.update_one( {"_id": serviceRecord._id}, { "$pull": { "SyncErrors": { "UserException_Type": UserExceptionType.Authorization } } } ) # Pull all auth-related errors from the service so they don't continue to see them while the sync completes. db.connections.update_one( {"_id": serviceRecord._id}, { "$pull": { "SyncErrors": { "UserException_Type": UserExceptionType.RenewPassword } } } ) # Pull all auth-related errors from the service so they don't continue to see them while the sync completes. _sync.SetNextSyncIsExhaustive( user, True ) # exhaustive, so it'll pick up activities from newly added services / ones lost during an error if hasattr(serviceRecord, "SyncErrors") and len(serviceRecord.SyncErrors) > 0: _sync.ScheduleImmediateSync(user)
def diag_user(req, user): try: userRec = db.users.find_one({"_id": ObjectId(user)}) except: userRec = None if not userRec: searchOpts = [{"Payments.Txn": user}, {"Payments.Email": user}] try: searchOpts.append({"AncestorAccounts": ObjectId(user)}) searchOpts.append({"ConnectedServices.ID": ObjectId(user)}) except: pass # Invalid format for ObjectId userRec = db.users.find_one({"$or": searchOpts}) if not userRec: searchOpts = [{"ExternalID": user}] try: searchOpts.append({"ExternalID": int(user)}) except: pass # Not an int svcRec = db.connections.find_one({"$or": searchOpts}) if svcRec: userRec = db.users.find_one( {"ConnectedServices.ID": svcRec["_id"]}) if userRec: return redirect("diagnostics_user", user=userRec["_id"]) if not userRec: return render(req, "diag/error_user_not_found.html") delta = True # Easier to set this to false in the one no-change case. if "sync" in req.POST: _sync = Sync() _sync.ScheduleImmediateSync(userRec, req.POST["sync"] == "Full") elif "unlock" in req.POST: db.users.update_one({"_id": ObjectId(user)}, {"$unset": { "SynchronizationWorker": None }}) elif "lock" in req.POST: db.users.update_one({"_id": ObjectId(user)}, {"$set": { "SynchronizationWorker": 1 }}) elif "requeue" in req.POST: db.users.update_one({"_id": ObjectId(user)}, {"$unset": { "QueuedAt": None }}) elif "hostrestrict" in req.POST: host = req.POST["host"] if host: db.users.update_one( {"_id": ObjectId(user)}, {"$set": { "SynchronizationHostRestriction": host }}) else: db.users.update_one( {"_id": ObjectId(user)}, {"$unset": { "SynchronizationHostRestriction": None }}) elif "substitute" in req.POST: req.session["substituteUserid"] = user return redirect("dashboard") elif "svc_setauth" in req.POST and len(req.POST["authdetails"]): db.connections.update_one( {"_id": ObjectId(req.POST["id"])}, {"$set": { "Authorization": json.loads(req.POST["authdetails"]) }}) elif "svc_setconfig" in req.POST and len(req.POST["config"]): db.connections.update_one( {"_id": ObjectId(req.POST["id"])}, {"$set": { "Config": json.loads(req.POST["config"]) }}) elif "svc_unlink" in req.POST: from tapiriik.services import Service from tapiriik.auth import User svcRec = Service.GetServiceRecordByID(req.POST["id"]) try: Service.DeleteServiceRecord(svcRec) except: pass try: User.DisconnectService(svcRec) except: pass elif "svc_marksync" in req.POST: db.connections.update_one( {"_id": ObjectId(req.POST["id"])}, {"$addToSet": { "SynchronizedActivities": req.POST["uid"] }}) elif "svc_clearexc" in req.POST: db.connections.update_one({"_id": ObjectId(req.POST["id"])}, {"$unset": { "ExcludedActivities": 1 }}) elif "svc_clearacts" in req.POST: db.connections.update_one({"_id": ObjectId(req.POST["id"])}, {"$unset": { "SynchronizedActivities": 1 }}) _sync = Sync() _sync.SetNextSyncIsExhaustive(userRec, True) elif "svc_toggle_poll_sub" in req.POST: from tapiriik.services import Service svcRec = Service.GetServiceRecordByID(req.POST["id"]) svcRec.SetPartialSyncTriggerSubscriptionState( not svcRec.PartialSyncTriggerSubscribed) elif "svc_toggle_poll_trigger" in req.POST: from tapiriik.services import Service svcRec = Service.GetServiceRecordByID(req.POST["id"]) db.connections.update_one( {"_id": ObjectId(req.POST["id"])}, {"$set": { "TriggerPartialSync": not svcRec.TriggerPartialSync }}) elif "svc_tryagain" in req.POST: from tapiriik.services import Service svcRec = Service.GetServiceRecordByID(req.POST["id"]) db.connections.update_one( {"_id": ObjectId(req.POST["id"])}, {"$pull": { "SyncErrors": { "Scope": "activity" } }}) act_recs = db.activity_records.find_one({"UserID": ObjectId(user)}) for act in act_recs["Activities"]: if "FailureCounts" in act and svcRec.Service.ID in act[ "FailureCounts"]: del act["FailureCounts"][svcRec.Service.ID] db.activity_records.save(act_recs) else: delta = False if delta: return redirect("diagnostics_user", user=user) return render(req, "diag/user.html", {"diag_user": userRec})
class User: ConfigurationDefaults = { "suppress_auto_sync": False, "sync_upload_delay": 0, "sync_skip_before": None, "historical_sync": False } Sync = Sync() def Get(id): return db.users.find_one({"_id": ObjectId(id)}) def GetByConnection(svcRec): return db.users.find_one({"ConnectedServices.ID": svcRec._id}) def Ensure(req): from ipware.ip import get_real_ip if req.user == None: req.user = User.Create(creationIP=get_real_ip(req)) User.Login(req.user, req) return req.user def Login(user, req): req.session["userid"] = str(user["_id"]) req.user = user def Logout(req): del req.session["userid"] del req.user def Create(creationIP=None): uid = db.users.insert({"Created": datetime.utcnow(), "CreationIP": creationIP}) # will mongodb insert an almost empty doc, i.e. _id? return db.users.with_options(read_preference=ReadPreference.PRIMARY).find_one({"_id": uid}) def GetConnectionRecordsByUser(user): return [ServiceRecord(x) for x in db.connections.find({"_id": {"$in": [x["ID"] for x in user["ConnectedServices"]]}})] def GetConnectionRecord(user, svcId): rec = db.connections.find_one({"_id": {"$in": [x["ID"] for x in user["ConnectedServices"] if x["Service"] == svcId]}}) return ServiceRecord(rec) if rec else None def SetEmail(user, email): db.users.update({"_id": ObjectId(user["_id"])}, {"$set": {"Email": email}}) def SetTimezone(user, tz): db.users.update({"_id": ObjectId(user["_id"])}, {"$set": {"Timezone": tz}}) def _assocPaymentLikeObject(user, collection, payment_like_object, schedule_now, skip_deassoc=False): # Since I seem to have taken this duck-typing quite far # First, deassociate payment ids from other accounts that may be using them if "_id" in payment_like_object and not skip_deassoc: db.users.update({}, {"$pull": {collection: {"_id": payment_like_object["_id"]}}}, multi=True) # Then, attach to us db.users.update({"_id": ObjectId(user["_id"])}, {"$addToSet": {collection: payment_like_object}}) if schedule_now: Sync.ScheduleImmediateSync(user) def AssociatePayment(user, payment, schedule_now=True): User._assocPaymentLikeObject(user, "Payments", payment, schedule_now) def AssociateExternalPayment(user, external_payment, schedule_now=False, skip_deassoc=False): User._assocPaymentLikeObject(user, "ExternalPayments", external_payment, schedule_now, skip_deassoc) def AssociatePromo(user, promo, schedule_now=True): User._assocPaymentLikeObject(user, "Promos", promo, schedule_now) def HasActivePayment(user): # Payments and Promos share the essential data field - Expiry # We don't really care if the payment has yet to take place yet - why would it be in the system then? # (Timestamp too, but the fact we rely on it here is only for backwards compatability with some old payment records) payment_like_objects = (user["Payments"] if "Payments" in user else []) + (user["Promos"] if "Promos" in user else []) + (user["ExternalPayments"] if "ExternalPayments" in user else []) for payment in payment_like_objects: if "Expiry" in payment: if payment["Expiry"] == None or payment["Expiry"] > datetime.utcnow(): return True else: if payment["Timestamp"] > (datetime.utcnow() - timedelta(days=365.25)): return True return False def PaidUserMongoQuery(): # Don't need the no-expiry case here, those payments have all expired by now return { "$or": [ {"Payments.Expiry": {"$gt": datetime.utcnow()}}, {"Promos.Expiry": {"$gt": datetime.utcnow()}}, {"Promos.Expiry": {"$type": 10, "$exists": True}} # === null ] } def IsServiceConnected(user, service_id): return service_id in [x["Service"] for x in user["ConnectedServices"]] def ConnectService(user, serviceRecord): from tapiriik.services import Service, UserExceptionType existingUser = db.users.find_one({"_id": {'$ne': ObjectId(user["_id"])}, "ConnectedServices.ID": ObjectId(serviceRecord._id)}) if "ConnectedServices" not in user: user["ConnectedServices"] = [] delta = False if existingUser is not None: # merge merge merge # Don't let the user end up with two services of the same type, ever # It's not fully supported, plus it's caused all sorts of trauma in the past. # Note that this will discard the new serviceRecord connection if an existing one exists on the other account # ...which isn't the end of the world, compared to screwing around asking the user which they wanted to keep. for to_merge_service in existingUser["ConnectedServices"]: if len([x for x in user["ConnectedServices"] if x["Service"] == to_merge_service["Service"]]) == 0: user["ConnectedServices"].append(to_merge_service) # There's got to be some 1-liner to do this merge if "Payments" in existingUser: if "Payments" not in user: user["Payments"] = [] user["Payments"] += existingUser["Payments"] if "Promos" in existingUser: if "Promos" not in user: user["Promos"] = [] user["Promos"] += existingUser["Promos"] if "ExternalPayments" in existingUser: if "ExternalPayments" not in user: user["ExternalPayments"] = [] user["ExternalPayments"] += existingUser["ExternalPayments"] if "FlowExceptions" in existingUser: if "FlowExceptions" not in user: user["FlowExceptions"] = [] user["FlowExceptions"] += existingUser["FlowExceptions"] user["Email"] = user["Email"] if "Email" in user and user["Email"] is not None else (existingUser["Email"] if "Email" in existingUser else None) user["NonblockingSyncErrorCount"] = (user["NonblockingSyncErrorCount"] if "NonblockingSyncErrorCount" in user and user["NonblockingSyncErrorCount"] is not None else 0) + (existingUser["NonblockingSyncErrorCount"] if "NonblockingSyncErrorCount" in existingUser and existingUser["NonblockingSyncErrorCount"] is not None else 0) user["BlockingSyncErrorCount"] = (user["BlockingSyncErrorCount"] if "BlockingSyncErrorCount" in user and user["BlockingSyncErrorCount"] is not None else 0) + (existingUser["BlockingSyncErrorCount"] if "BlockingSyncErrorCount" in existingUser and existingUser["BlockingSyncErrorCount"] is not None else 0) user["SyncExclusionCount"] = (user["SyncExclusionCount"] if "SyncExclusionCount" in user and user["SyncExclusionCount"] is not None else 0) + (existingUser["SyncExclusionCount"] if "SyncExclusionCount" in existingUser and existingUser["SyncExclusionCount"] is not None else 0) user["Created"] = user["Created"] if user["Created"] < existingUser["Created"] else existingUser["Created"] if "AncestorAccounts" not in user: user["AncestorAccounts"] = [] user["AncestorAccounts"] += existingUser["AncestorAccounts"] if "AncestorAccounts" in existingUser else [] user["AncestorAccounts"] += [existingUser["_id"]] user["Timezone"] = user["Timezone"] if "Timezone" in user and user["Timezone"] else (existingUser["Timezone"] if "Timezone" in existingUser else None) user["CreationIP"] = user["CreationIP"] if "CreationIP" in user and user["CreationIP"] else (existingUser["CreationIP"] if "CreationIP" in existingUser else None) existing_config = existingUser["Config"] if "Config" in existingUser else {} existing_config.update(user["Config"] if "Config" in user else {}) user["Config"] = existing_config delta = True db.users.remove({"_id": existingUser["_id"]}) else: if serviceRecord._id not in [x["ID"] for x in user["ConnectedServices"]]: # we might be connecting a second account for the same service for duplicateConn in [x for x in user["ConnectedServices"] if x["Service"] == serviceRecord.Service.ID]: dupeRecord = User.GetConnectionRecord(user, serviceRecord.Service.ID) # this'll just pick the first connection of type, but we repeat the right # of times anyways Service.DeleteServiceRecord(dupeRecord) # We used to call DisconnectService() here, but the results of that call were getting overwritten, which was unfortunate. user["ConnectedServices"] = [x for x in user["ConnectedServices"] if x["Service"] != serviceRecord.Service.ID] user["ConnectedServices"].append({"Service": serviceRecord.Service.ID, "ID": serviceRecord._id}) delta = True db.users.update({"_id": user["_id"]}, user) if delta or (hasattr(serviceRecord, "SyncErrors") and len(serviceRecord.SyncErrors) > 0): # also schedule an immediate sync if there is an outstanding error (i.e. user reconnected) db.connections.update({"_id": serviceRecord._id}, {"$pull": {"SyncErrors": {"UserException.Type": UserExceptionType.Authorization}}}) # Pull all auth-related errors from the service so they don't continue to see them while the sync completes. db.connections.update({"_id": serviceRecord._id}, {"$pull": {"SyncErrors": {"UserException.Type": UserExceptionType.RenewPassword}}}) # Pull all auth-related errors from the service so they don't continue to see them while the sync completes. Sync.SetNextSyncIsExhaustive(user, True) # exhaustive, so it'll pick up activities from newly added services / ones lost during an error if hasattr(serviceRecord, "SyncErrors") and len(serviceRecord.SyncErrors) > 0: Sync.ScheduleImmediateSync(user) def DisconnectService(serviceRecord, preserveUser=False): # not that >1 user should have this connection activeUsers = list(db.users.find({"ConnectedServices.ID": serviceRecord._id})) if len(activeUsers) == 0: raise Exception("No users found with service " + serviceRecord._id) db.users.update({}, {"$pull": {"ConnectedServices": {"ID": serviceRecord._id}}}, multi=True) if not preserveUser: for user in activeUsers: if len(user["ConnectedServices"]) - 1 == 0: # I guess we're done here? db.activity_records.remove({"UserID": user["_id"]}) db.users.remove({"_id": user["_id"]}) def AuthByService(serviceRecord): return db.users.find_one({"ConnectedServices.ID": serviceRecord._id}) def SetFlowException(user, sourceServiceRecord, targetServiceRecord, flowToTarget=True, flowToSource=True): if "FlowExceptions" not in user: user["FlowExceptions"] = [] # flow exceptions are stored in "forward" direction - service-account X will not send activities to service-account Y forwardException = {"Target": {"Service": targetServiceRecord.Service.ID, "ExternalID": targetServiceRecord.ExternalID}, "Source": {"Service": sourceServiceRecord.Service.ID, "ExternalID": sourceServiceRecord.ExternalID}} backwardsException = {"Target": forwardException["Source"], "Source": forwardException["Target"]} if flowToTarget is not None: if flowToTarget: user["FlowExceptions"][:] = [x for x in user["FlowExceptions"] if x != forwardException] elif not flowToTarget and forwardException not in user["FlowExceptions"]: user["FlowExceptions"].append(forwardException) if flowToSource is not None: if flowToSource: user["FlowExceptions"][:] = [x for x in user["FlowExceptions"] if x != backwardsException] elif not flowToSource and backwardsException not in user["FlowExceptions"]: user["FlowExceptions"].append(backwardsException) db.users.update({"_id": user["_id"]}, {"$set": {"FlowExceptions": user["FlowExceptions"]}}) def GetFlowExceptions(user): if "FlowExceptions" not in user: return {} return user["FlowExceptions"] def CheckFlowException(user, sourceServiceRecord, targetServiceRecord): ''' returns true if there is a flow exception blocking activities moving from source to destination ''' forwardException = {"Target": {"Service": targetServiceRecord.Service.ID, "ExternalID": targetServiceRecord.ExternalID}, "Source": {"Service": sourceServiceRecord.Service.ID, "ExternalID": sourceServiceRecord.ExternalID}} return "FlowExceptions" in user and forwardException in user["FlowExceptions"] # You may recognize that these functions are shamelessly copy-pasted from service_base.py def GetConfiguration(user): config = copy.deepcopy(User.ConfigurationDefaults) config.update(user["Config"] if "Config" in user else {}) return config def SetConfiguration(user, config, no_save=False, drop_existing=False): sparseConfig = {} if not drop_existing: sparseConfig = copy.deepcopy(User.GetConfiguration(user)) sparseConfig.update(config) keys_to_delete = [] for k, v in sparseConfig.items(): if (k in User.ConfigurationDefaults and User.ConfigurationDefaults[k] == v): keys_to_delete.append(k) # it's the default, we can not store it for k in keys_to_delete: del sparseConfig[k] user["Config"] = sparseConfig if not no_save: db.users.update({"_id": user["_id"]}, {"$set": {"Config": sparseConfig}})