aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'mastodon/push.py')
-rw-r--r--mastodon/push.py201
1 files changed, 201 insertions, 0 deletions
diff --git a/mastodon/push.py b/mastodon/push.py
new file mode 100644
index 0000000..def348a
--- /dev/null
+++ b/mastodon/push.py
@@ -0,0 +1,201 @@
1# push.py - webpush endpoints and tooling
2
3import base64
4import os
5import json
6
7from .versions import _DICT_VERSION_PUSH, _DICT_VERSION_PUSH_NOTIF
8from .errors import MastodonIllegalArgumentError
9from .utility import api_version
10from .compat import IMPL_HAS_CRYPTO, ec, serialization, default_backend
11from .compat import IMPL_HAS_ECE, http_ece
12
13from .internals import Mastodon as Internals
14
15class Mastodon(Internals):
16 ###
17 # Reading data: Webpush subscriptions
18 ###
19 @api_version("2.4.0", "2.4.0", _DICT_VERSION_PUSH)
20 def push_subscription(self):
21 """
22 Fetch the current push subscription the logged-in user has for this app.
23
24 Returns a :ref:`push subscription dict <push subscription dict>`.
25 """
26 return self.__api_request('GET', '/api/v1/push/subscription')
27
28 ###
29 # Writing data: Push subscriptions
30 ###
31 @api_version("2.4.0", "2.4.0", _DICT_VERSION_PUSH)
32 def push_subscription_set(self, endpoint, encrypt_params, follow_events=None,
33 favourite_events=None, reblog_events=None,
34 mention_events=None, poll_events=None,
35 follow_request_events=None, status_events=None, policy='all'):
36 """
37 Sets up or modifies the push subscription the logged-in user has for this app.
38
39 `endpoint` is the endpoint URL mastodon should call for pushes. Note that mastodon
40 requires https for this URL. `encrypt_params` is a dict with key parameters that allow
41 the server to encrypt data for you: A public key `pubkey` and a shared secret `auth`.
42 You can generate this as well as the corresponding private key using the
43 :ref:`push_subscription_generate_keys() <push_subscription_generate_keys()>` function.
44
45 `policy` controls what sources will generate webpush events. Valid values are
46 `all`, `none`, `follower` and `followed`.
47
48 The rest of the parameters controls what kind of events you wish to subscribe to.
49
50 Returns a :ref:`push subscription dict <push subscription dict>`.
51 """
52 if not policy in ['all', 'none', 'follower', 'followed']:
53 raise MastodonIllegalArgumentError("Valid values for policy are 'all', 'none', 'follower' or 'followed'.")
54
55 endpoint = Mastodon.__protocolize(endpoint)
56
57 push_pubkey_b64 = base64.b64encode(encrypt_params['pubkey'])
58 push_auth_b64 = base64.b64encode(encrypt_params['auth'])
59
60 params = {
61 'subscription[endpoint]': endpoint,
62 'subscription[keys][p256dh]': push_pubkey_b64,
63 'subscription[keys][auth]': push_auth_b64,
64 'policy': policy
65 }
66
67 if follow_events is not None:
68 params['data[alerts][follow]'] = follow_events
69
70 if favourite_events is not None:
71 params['data[alerts][favourite]'] = favourite_events
72
73 if reblog_events is not None:
74 params['data[alerts][reblog]'] = reblog_events
75
76 if mention_events is not None:
77 params['data[alerts][mention]'] = mention_events
78
79 if poll_events is not None:
80 params['data[alerts][poll]'] = poll_events
81
82 if follow_request_events is not None:
83 params['data[alerts][follow_request]'] = follow_request_events
84
85 if follow_request_events is not None:
86 params['data[alerts][status]'] = status_events
87
88 # Canonicalize booleans
89 params = self.__generate_params(params)
90
91 return self.__api_request('POST', '/api/v1/push/subscription', params)
92
93 @api_version("2.4.0", "2.4.0", _DICT_VERSION_PUSH)
94 def push_subscription_update(self, follow_events=None,
95 favourite_events=None, reblog_events=None,
96 mention_events=None, poll_events=None,
97 follow_request_events=None):
98 """
99 Modifies what kind of events the app wishes to subscribe to.
100
101 Returns the updated :ref:`push subscription dict <push subscription dict>`.
102 """
103 params = {}
104
105 if follow_events is not None:
106 params['data[alerts][follow]'] = follow_events
107
108 if favourite_events is not None:
109 params['data[alerts][favourite]'] = favourite_events
110
111 if reblog_events is not None:
112 params['data[alerts][reblog]'] = reblog_events
113
114 if mention_events is not None:
115 params['data[alerts][mention]'] = mention_events
116
117 if poll_events is not None:
118 params['data[alerts][poll]'] = poll_events
119
120 if follow_request_events is not None:
121 params['data[alerts][follow_request]'] = follow_request_events
122
123 # Canonicalize booleans
124 params = self.__generate_params(params)
125
126 return self.__api_request('PUT', '/api/v1/push/subscription', params)
127
128 @api_version("2.4.0", "2.4.0", "2.4.0")
129 def push_subscription_delete(self):
130 """
131 Remove the current push subscription the logged-in user has for this app.
132 """
133 self.__api_request('DELETE', '/api/v1/push/subscription')
134
135 ###
136 # Push subscription crypto utilities
137 ###
138 def push_subscription_generate_keys(self):
139 """
140 Generates a private key, public key and shared secret for use in webpush subscriptions.
141
142 Returns two dicts: One with the private key and shared secret and another with the
143 public key and shared secret.
144 """
145 if not IMPL_HAS_CRYPTO:
146 raise NotImplementedError(
147 'To use the crypto tools, please install the webpush feature dependencies.')
148
149 push_key_pair = ec.generate_private_key(ec.SECP256R1(), default_backend())
150 push_key_priv = push_key_pair.private_numbers().private_value
151 try:
152 push_key_pub = push_key_pair.public_key().public_bytes(
153 serialization.Encoding.X962,
154 serialization.PublicFormat.UncompressedPoint,
155 )
156 except:
157 push_key_pub = push_key_pair.public_key().public_numbers().encode_point()
158
159 push_shared_secret = os.urandom(16)
160
161 priv_dict = {
162 'privkey': push_key_priv,
163 'auth': push_shared_secret
164 }
165
166 pub_dict = {
167 'pubkey': push_key_pub,
168 'auth': push_shared_secret
169 }
170
171 return priv_dict, pub_dict
172
173 @api_version("2.4.0", "2.4.0", _DICT_VERSION_PUSH_NOTIF)
174 def push_subscription_decrypt_push(self, data, decrypt_params, encryption_header, crypto_key_header):
175 """
176 Decrypts `data` received in a webpush request. Requires the private key dict
177 from :ref:`push_subscription_generate_keys() <push_subscription_generate_keys()>` (`decrypt_params`) as well as the
178 Encryption and server Crypto-Key headers from the received webpush
179
180 Returns the decoded webpush as a :ref:`push notification dict <push notification dict>`.
181 """
182 if (not IMPL_HAS_ECE) or (not IMPL_HAS_CRYPTO):
183 raise NotImplementedError(
184 'To use the crypto tools, please install the webpush feature dependencies.')
185
186 salt = self.__decode_webpush_b64(encryption_header.split("salt=")[1].strip())
187 dhparams = self.__decode_webpush_b64(crypto_key_header.split("dh=")[1].split(";")[0].strip())
188 p256ecdsa = self.__decode_webpush_b64(crypto_key_header.split("p256ecdsa=")[1].strip())
189 dec_key = ec.derive_private_key(decrypt_params['privkey'], ec.SECP256R1(), default_backend())
190 decrypted = http_ece.decrypt(
191 data,
192 salt=salt,
193 key=p256ecdsa,
194 private_key=dec_key,
195 dh=dhparams,
196 auth_secret=decrypt_params['auth'],
197 keylabel="P-256",
198 version="aesgcm"
199 )
200
201 return json.loads(decrypted.decode('utf-8'), object_hook=Mastodon.__json_hooks)
Powered by cgit v1.2.3 (git 2.41.0)