13
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
# See the License for the specific language governing permissions and
15
15
# limitations under the License.
16
-
17
16
import abc
18
17
import logging
19
18
from typing import List , Tuple , Union
20
19
20
+ from synapse .api .errors import NotFoundError , StoreError
21
21
from synapse .push .baserules import list_with_base_rules
22
22
from synapse .replication .slave .storage ._slaved_id_tracker import SlavedIdTracker
23
23
from synapse .storage ._base import SQLBaseStore , db_to_json
27
27
from synapse .storage .databases .main .pusher import PusherWorkerStore
28
28
from synapse .storage .databases .main .receipts import ReceiptsWorkerStore
29
29
from synapse .storage .databases .main .roommember import RoomMemberWorkerStore
30
+ from synapse .storage .engines import PostgresEngine , Sqlite3Engine
30
31
from synapse .storage .push_rule import InconsistentRuleException , RuleNotFoundException
31
32
from synapse .storage .util .id_generators import StreamIdGenerator
32
33
from synapse .util import json_encoder
@@ -540,6 +541,25 @@ def _upsert_push_rule_txn(
540
541
},
541
542
)
542
543
544
+ # ensure we have a push_rules_enable row
545
+ # enabledness defaults to true
546
+ if isinstance (self .database_engine , PostgresEngine ):
547
+ sql = """
548
+ INSERT INTO push_rules_enable (id, user_name, rule_id, enabled)
549
+ VALUES (?, ?, ?, ?)
550
+ ON CONFLICT DO NOTHING
551
+ """
552
+ elif isinstance (self .database_engine , Sqlite3Engine ):
553
+ sql = """
554
+ INSERT OR IGNORE INTO push_rules_enable (id, user_name, rule_id, enabled)
555
+ VALUES (?, ?, ?, ?)
556
+ """
557
+ else :
558
+ raise RuntimeError ("Unknown database engine" )
559
+
560
+ new_enable_id = self ._push_rules_enable_id_gen .get_next ()
561
+ txn .execute (sql , (new_enable_id , user_id , rule_id , 1 ))
562
+
543
563
async def delete_push_rule (self , user_id : str , rule_id : str ) -> None :
544
564
"""
545
565
Delete a push rule. Args specify the row to be deleted and can be
@@ -552,6 +572,12 @@ async def delete_push_rule(self, user_id: str, rule_id: str) -> None:
552
572
"""
553
573
554
574
def delete_push_rule_txn (txn , stream_id , event_stream_ordering ):
575
+ # we don't use simple_delete_one_txn because that would fail if the
576
+ # user did not have a push_rule_enable row.
577
+ self .db_pool .simple_delete_txn (
578
+ txn , "push_rules_enable" , {"user_name" : user_id , "rule_id" : rule_id }
579
+ )
580
+
555
581
self .db_pool .simple_delete_one_txn (
556
582
txn , "push_rules" , {"user_name" : user_id , "rule_id" : rule_id }
557
583
)
@@ -570,10 +596,29 @@ def delete_push_rule_txn(txn, stream_id, event_stream_ordering):
570
596
event_stream_ordering ,
571
597
)
572
598
573
- async def set_push_rule_enabled (self , user_id , rule_id , enabled ) -> None :
599
+ async def set_push_rule_enabled (
600
+ self , user_id : str , rule_id : str , enabled : bool , is_default_rule : bool
601
+ ) -> None :
602
+ """
603
+ Sets the `enabled` state of a push rule.
604
+
605
+ Args:
606
+ user_id: the user ID of the user who wishes to enable/disable the rule
607
+ e.g. '@tina:example.org'
608
+ rule_id: the full rule ID of the rule to be enabled/disabled
609
+ e.g. 'global/override/.m.rule.roomnotif'
610
+ or 'global/override/myCustomRule'
611
+ enabled: True if the rule is to be enabled, False if it is to be
612
+ disabled
613
+ is_default_rule: True if and only if this is a server-default rule.
614
+ This skips the check for existence (as only user-created rules
615
+ are always stored in the database `push_rules` table).
616
+
617
+ Raises:
618
+ NotFoundError if the rule does not exist.
619
+ """
574
620
with await self ._push_rules_stream_id_gen .get_next () as stream_id :
575
621
event_stream_ordering = self ._stream_id_gen .get_current_token ()
576
-
577
622
await self .db_pool .runInteraction (
578
623
"_set_push_rule_enabled_txn" ,
579
624
self ._set_push_rule_enabled_txn ,
@@ -582,12 +627,47 @@ async def set_push_rule_enabled(self, user_id, rule_id, enabled) -> None:
582
627
user_id ,
583
628
rule_id ,
584
629
enabled ,
630
+ is_default_rule ,
585
631
)
586
632
587
633
def _set_push_rule_enabled_txn (
588
- self , txn , stream_id , event_stream_ordering , user_id , rule_id , enabled
634
+ self ,
635
+ txn ,
636
+ stream_id ,
637
+ event_stream_ordering ,
638
+ user_id ,
639
+ rule_id ,
640
+ enabled ,
641
+ is_default_rule ,
589
642
):
590
643
new_id = self ._push_rules_enable_id_gen .get_next ()
644
+
645
+ if not is_default_rule :
646
+ # first check it exists; we need to lock for key share so that a
647
+ # transaction that deletes the push rule will conflict with this one.
648
+ # We also need a push_rule_enable row to exist for every push_rules
649
+ # row, otherwise it is possible to simultaneously delete a push rule
650
+ # (that has no _enable row) and enable it, resulting in a dangling
651
+ # _enable row. To solve this: we either need to use SERIALISABLE or
652
+ # ensure we always have a push_rule_enable row for every push_rule
653
+ # row. We chose the latter.
654
+ for_key_share = "FOR KEY SHARE"
655
+ if not isinstance (self .database_engine , PostgresEngine ):
656
+ # For key share is not applicable/available on SQLite
657
+ for_key_share = ""
658
+ sql = (
659
+ """
660
+ SELECT 1 FROM push_rules
661
+ WHERE user_name = ? AND rule_id = ?
662
+ %s
663
+ """
664
+ % for_key_share
665
+ )
666
+ txn .execute (sql , (user_id , rule_id ))
667
+ if txn .fetchone () is None :
668
+ # needed to set NOT_FOUND code.
669
+ raise NotFoundError ("Push rule does not exist." )
670
+
591
671
self .db_pool .simple_upsert_txn (
592
672
txn ,
593
673
"push_rules_enable" ,
@@ -606,8 +686,30 @@ def _set_push_rule_enabled_txn(
606
686
)
607
687
608
688
async def set_push_rule_actions (
609
- self , user_id , rule_id , actions , is_default_rule
689
+ self ,
690
+ user_id : str ,
691
+ rule_id : str ,
692
+ actions : List [Union [dict , str ]],
693
+ is_default_rule : bool ,
610
694
) -> None :
695
+ """
696
+ Sets the `actions` state of a push rule.
697
+
698
+ Will throw NotFoundError if the rule does not exist; the Code for this
699
+ is NOT_FOUND.
700
+
701
+ Args:
702
+ user_id: the user ID of the user who wishes to enable/disable the rule
703
+ e.g. '@tina:example.org'
704
+ rule_id: the full rule ID of the rule to be enabled/disabled
705
+ e.g. 'global/override/.m.rule.roomnotif'
706
+ or 'global/override/myCustomRule'
707
+ actions: A list of actions (each action being a dict or string),
708
+ e.g. ["notify", {"set_tweak": "highlight", "value": false}]
709
+ is_default_rule: True if and only if this is a server-default rule.
710
+ This skips the check for existence (as only user-created rules
711
+ are always stored in the database `push_rules` table).
712
+ """
611
713
actions_json = json_encoder .encode (actions )
612
714
613
715
def set_push_rule_actions_txn (txn , stream_id , event_stream_ordering ):
@@ -629,12 +731,19 @@ def set_push_rule_actions_txn(txn, stream_id, event_stream_ordering):
629
731
update_stream = False ,
630
732
)
631
733
else :
632
- self .db_pool .simple_update_one_txn (
633
- txn ,
634
- "push_rules" ,
635
- {"user_name" : user_id , "rule_id" : rule_id },
636
- {"actions" : actions_json },
637
- )
734
+ try :
735
+ self .db_pool .simple_update_one_txn (
736
+ txn ,
737
+ "push_rules" ,
738
+ {"user_name" : user_id , "rule_id" : rule_id },
739
+ {"actions" : actions_json },
740
+ )
741
+ except StoreError as serr :
742
+ if serr .code == 404 :
743
+ # this sets the NOT_FOUND error Code
744
+ raise NotFoundError ("Push rule does not exist" )
745
+ else :
746
+ raise
638
747
639
748
self ._insert_push_rules_update_txn (
640
749
txn ,
0 commit comments