1 | """ |
---|
2 | Ported to Python 3. |
---|
3 | """ |
---|
4 | |
---|
5 | import time |
---|
6 | import json |
---|
7 | |
---|
8 | from urllib.parse import ( |
---|
9 | quote, |
---|
10 | ) |
---|
11 | |
---|
12 | from bs4 import ( |
---|
13 | BeautifulSoup, |
---|
14 | ) |
---|
15 | |
---|
16 | from twisted.web.template import Tag |
---|
17 | from twisted.web.test.requesthelper import DummyRequest |
---|
18 | from twisted.application import service |
---|
19 | from testtools.twistedsupport import succeeded |
---|
20 | from twisted.internet.defer import ( |
---|
21 | inlineCallbacks, |
---|
22 | succeed, |
---|
23 | ) |
---|
24 | |
---|
25 | from ...storage_client import ( |
---|
26 | NativeStorageServer, |
---|
27 | StorageFarmBroker, |
---|
28 | ) |
---|
29 | from ...web.root import ( |
---|
30 | RootElement, |
---|
31 | Root, |
---|
32 | ) |
---|
33 | from ...util.connection_status import ConnectionStatus |
---|
34 | from ...crypto.ed25519 import ( |
---|
35 | create_signing_keypair, |
---|
36 | ) |
---|
37 | from allmydata.web.root import URIHandler |
---|
38 | from allmydata.client import _Client |
---|
39 | |
---|
40 | from .common import ( |
---|
41 | assert_soup_has_tag_with_attributes, |
---|
42 | ) |
---|
43 | from ..common_web import ( |
---|
44 | render, |
---|
45 | ) |
---|
46 | from ..common import ( |
---|
47 | EMPTY_CLIENT_CONFIG, |
---|
48 | ) |
---|
49 | |
---|
50 | from ..common import ( |
---|
51 | SyncTestCase, |
---|
52 | AsyncTestCase, |
---|
53 | ) |
---|
54 | |
---|
55 | from testtools.matchers import ( |
---|
56 | Equals, |
---|
57 | Contains, |
---|
58 | AfterPreprocessing, |
---|
59 | ) |
---|
60 | |
---|
61 | class RenderSlashUri(SyncTestCase): |
---|
62 | """ |
---|
63 | Ensure that URIs starting with /uri?uri= only accept valid |
---|
64 | capabilities |
---|
65 | """ |
---|
66 | |
---|
67 | def setUp(self): |
---|
68 | self.client = object() |
---|
69 | self.res = URIHandler(self.client) |
---|
70 | super(RenderSlashUri, self).setUp() |
---|
71 | |
---|
72 | @inlineCallbacks |
---|
73 | def test_valid_query_redirect(self): |
---|
74 | """ |
---|
75 | A syntactically valid capability given in the ``uri`` query argument |
---|
76 | results in a redirect. |
---|
77 | """ |
---|
78 | cap = ( |
---|
79 | b"URI:CHK:nt2xxmrccp7sursd6yh2thhcky:" |
---|
80 | b"mukesarwdjxiyqsjinbfiiro6q7kgmmekocxfjcngh23oxwyxtzq:2:5:5874882" |
---|
81 | ) |
---|
82 | query_args = {b"uri": [cap]} |
---|
83 | response_body = yield render(self.res, query_args) |
---|
84 | soup = BeautifulSoup(response_body, 'html5lib') |
---|
85 | tag = assert_soup_has_tag_with_attributes( |
---|
86 | self, |
---|
87 | soup, |
---|
88 | u"meta", |
---|
89 | {u"http-equiv": "refresh"}, |
---|
90 | ) |
---|
91 | self.assertThat( |
---|
92 | tag.attrs.get(u"content"), |
---|
93 | Contains(quote(cap, safe="")), |
---|
94 | ) |
---|
95 | |
---|
96 | def test_invalid(self): |
---|
97 | """ |
---|
98 | A syntactically invalid capbility results in an error. |
---|
99 | """ |
---|
100 | query_args = {b"uri": [b"not a capability"]} |
---|
101 | response_body = render(self.res, query_args) |
---|
102 | self.assertThat( |
---|
103 | response_body, |
---|
104 | succeeded(AfterPreprocessing(bytes, Equals(b"Invalid capability"))), |
---|
105 | ) |
---|
106 | |
---|
107 | |
---|
108 | class RenderServiceRow(SyncTestCase): |
---|
109 | def test_missing(self): |
---|
110 | """ |
---|
111 | minimally-defined static servers just need anonymous-storage-FURL |
---|
112 | and permutation-seed-base32. The WUI used to have problems |
---|
113 | rendering servers that lacked nickname and version. This tests that |
---|
114 | we can render such minimal servers. |
---|
115 | """ |
---|
116 | ann = {"anonymous-storage-FURL": "pb://w2hqnbaa25yw4qgcvghl5psa3srpfgw3@tcp:127.0.0.1:51309/vucto2z4fxment3vfxbqecblbf6zyp6x", |
---|
117 | "permutation-seed-base32": "w2hqnbaa25yw4qgcvghl5psa3srpfgw3", |
---|
118 | } |
---|
119 | srv = NativeStorageServer(b"server_id", ann, None, {}, EMPTY_CLIENT_CONFIG) |
---|
120 | srv.get_connection_status = lambda: ConnectionStatus(False, "summary", {}, 0, 0) |
---|
121 | |
---|
122 | class FakeClient(_Client): |
---|
123 | def __init__(self): |
---|
124 | service.MultiService.__init__(self) |
---|
125 | self.storage_broker = StorageFarmBroker( |
---|
126 | permute_peers=True, |
---|
127 | tub_maker=None, |
---|
128 | node_config=EMPTY_CLIENT_CONFIG, |
---|
129 | ) |
---|
130 | self.storage_broker.test_add_server(b"test-srv", srv) |
---|
131 | |
---|
132 | root = RootElement(FakeClient(), time.time) |
---|
133 | req = DummyRequest(b"") |
---|
134 | tag = Tag(b"") |
---|
135 | |
---|
136 | # Pick all items from services table. |
---|
137 | items = root.services_table(req, tag).item(req, tag) |
---|
138 | |
---|
139 | # Coerce `items` to list and pick the first item from it. |
---|
140 | item = list(items)[0] |
---|
141 | |
---|
142 | self.assertThat(item.slotData.get("version"), Equals("")) |
---|
143 | self.assertThat(item.slotData.get("nickname"), Equals("")) |
---|
144 | |
---|
145 | |
---|
146 | class RenderRoot(AsyncTestCase): |
---|
147 | |
---|
148 | @inlineCallbacks |
---|
149 | def test_root_json(self): |
---|
150 | """ |
---|
151 | The 'welcome' / root page renders properly with ?t=json when some |
---|
152 | servers show None for available_space while others show a |
---|
153 | valid int |
---|
154 | |
---|
155 | See also https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3852 |
---|
156 | """ |
---|
157 | ann = { |
---|
158 | "anonymous-storage-FURL": "pb://w2hqnbaa25yw4qgcvghl5psa3srpfgw3@tcp:127.0.0.1:51309/vucto2z4fxment3vfxbqecblbf6zyp6x", |
---|
159 | "permutation-seed-base32": "w2hqnbaa25yw4qgcvghl5psa3srpfgw3", |
---|
160 | } |
---|
161 | srv0 = NativeStorageServer(b"server_id0", ann, None, {}, EMPTY_CLIENT_CONFIG) |
---|
162 | srv0.get_connection_status = lambda: ConnectionStatus(False, "summary0", {}, 0, 0) |
---|
163 | |
---|
164 | srv1 = NativeStorageServer(b"server_id1", ann, None, {}, EMPTY_CLIENT_CONFIG) |
---|
165 | srv1.get_connection_status = lambda: ConnectionStatus(False, "summary1", {}, 0, 0) |
---|
166 | # arrange for this server to have some valid available space |
---|
167 | srv1.get_available_space = lambda: 12345 |
---|
168 | |
---|
169 | class FakeClient(_Client): |
---|
170 | history = [] |
---|
171 | stats_provider = object() |
---|
172 | nickname = "" |
---|
173 | nodeid = b"asdf" |
---|
174 | _node_public_key = create_signing_keypair()[1] |
---|
175 | introducer_clients = [] |
---|
176 | helper = None |
---|
177 | |
---|
178 | def __init__(self): |
---|
179 | service.MultiService.__init__(self) |
---|
180 | self.storage_broker = StorageFarmBroker( |
---|
181 | permute_peers=True, |
---|
182 | tub_maker=None, |
---|
183 | node_config=EMPTY_CLIENT_CONFIG, |
---|
184 | ) |
---|
185 | self.storage_broker.test_add_server(b"test-srv0", srv0) |
---|
186 | self.storage_broker.test_add_server(b"test-srv1", srv1) |
---|
187 | |
---|
188 | root = Root(FakeClient(), now_fn=time.time) |
---|
189 | |
---|
190 | lines = [] |
---|
191 | |
---|
192 | req = DummyRequest(b"") |
---|
193 | req.fields = {} |
---|
194 | req.args = { |
---|
195 | b"t": [b"json"], |
---|
196 | } |
---|
197 | |
---|
198 | # for some reason, DummyRequest is already finished when we |
---|
199 | # try to add a notifyFinish handler, so override that |
---|
200 | # behavior. |
---|
201 | |
---|
202 | def nop(): |
---|
203 | return succeed(None) |
---|
204 | req.notifyFinish = nop |
---|
205 | req.write = lines.append |
---|
206 | |
---|
207 | yield root.render(req) |
---|
208 | |
---|
209 | raw_js = b"".join(lines).decode("utf8") |
---|
210 | js = json.loads(raw_js) |
---|
211 | servers = js["servers"] |
---|
212 | self.assertEquals(len(servers), 2) |
---|
213 | self.assertIn( |
---|
214 | { |
---|
215 | "connection_status": "summary0", |
---|
216 | "nodeid": "server_id0", |
---|
217 | "last_received_data": 0, |
---|
218 | "version": None, |
---|
219 | "available_space": None, |
---|
220 | "nickname": "" |
---|
221 | }, |
---|
222 | servers |
---|
223 | ) |
---|
224 | self.assertIn( |
---|
225 | { |
---|
226 | "connection_status": "summary1", |
---|
227 | "nodeid": "server_id1", |
---|
228 | "last_received_data": 0, |
---|
229 | "version": None, |
---|
230 | "available_space": 12345, |
---|
231 | "nickname": "" |
---|
232 | }, |
---|
233 | servers |
---|
234 | ) |
---|