1from __future__ import absolute_import, division, print_function
2__metaclass__ = type
3
4
5import pytest
6
7from mock import MagicMock
8
9
10from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import (
11    combine_identifier,
12    split_identifier,
13    Challenge,
14    Authorization,
15)
16
17from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
18    ACMEProtocolException,
19    ModuleFailException,
20)
21
22
23def test_combine_identifier():
24    assert combine_identifier('', '') == ':'
25    assert combine_identifier('a', 'b') == 'a:b'
26
27
28def test_split_identifier():
29    assert split_identifier(':') == ['', '']
30    assert split_identifier('a:b') == ['a', 'b']
31    assert split_identifier('a:b:c') == ['a', 'b:c']
32    with pytest.raises(ModuleFailException) as exc:
33        split_identifier('a')
34    assert exc.value.msg == 'Identifier "a" is not of the form <type>:<identifier>'
35
36
37def test_challenge_from_to_json():
38    client = MagicMock()
39
40    data = {
41        'url': 'xxx',
42        'type': 'type',
43        'status': 'valid',
44    }
45    client.version = 2
46    challenge = Challenge.from_json(client, data)
47    assert challenge.data == data
48    assert challenge.type == 'type'
49    assert challenge.url == 'xxx'
50    assert challenge.status == 'valid'
51    assert challenge.token is None
52    assert challenge.to_json() == data
53
54    data = {
55        'type': 'type',
56        'status': 'valid',
57        'token': 'foo',
58    }
59    challenge = Challenge.from_json(None, data, url='xxx')
60    assert challenge.data == data
61    assert challenge.type == 'type'
62    assert challenge.url == 'xxx'
63    assert challenge.status == 'valid'
64    assert challenge.token == 'foo'
65    assert challenge.to_json() == data
66
67    data = {
68        'uri': 'xxx',
69        'type': 'type',
70        'status': 'valid',
71    }
72    client.version = 1
73    challenge = Challenge.from_json(client, data)
74    assert challenge.data == data
75    assert challenge.type == 'type'
76    assert challenge.url == 'xxx'
77    assert challenge.status == 'valid'
78    assert challenge.token is None
79    assert challenge.to_json() == data
80
81
82def test_authorization_from_to_json():
83    client = MagicMock()
84    client.version = 2
85
86    data = {
87        'challenges': [],
88        'status': 'valid',
89        'identifier': {
90            'type': 'dns',
91            'value': 'example.com',
92        },
93    }
94    authz = Authorization.from_json(client, data, 'xxx')
95    assert authz.url == 'xxx'
96    assert authz.status == 'valid'
97    assert authz.identifier == 'example.com'
98    assert authz.identifier_type == 'dns'
99    assert authz.challenges == []
100    assert authz.to_json() == {
101        'uri': 'xxx',
102        'challenges': [],
103        'status': 'valid',
104        'identifier': {
105            'type': 'dns',
106            'value': 'example.com',
107        },
108    }
109
110    data = {
111        'challenges': [
112            {
113                'url': 'xxxyyy',
114                'type': 'type',
115                'status': 'valid',
116            }
117        ],
118        'status': 'valid',
119        'identifier': {
120            'type': 'dns',
121            'value': 'example.com',
122        },
123        'wildcard': True,
124    }
125    authz = Authorization.from_json(client, data, 'xxx')
126    assert authz.url == 'xxx'
127    assert authz.status == 'valid'
128    assert authz.identifier == '*.example.com'
129    assert authz.identifier_type == 'dns'
130    assert len(authz.challenges) == 1
131    assert authz.challenges[0].data == {
132        'url': 'xxxyyy',
133        'type': 'type',
134        'status': 'valid',
135    }
136    assert authz.to_json() == {
137        'uri': 'xxx',
138        'challenges': [
139            {
140                'url': 'xxxyyy',
141                'type': 'type',
142                'status': 'valid',
143            }
144        ],
145        'status': 'valid',
146        'identifier': {
147            'type': 'dns',
148            'value': 'example.com',
149        },
150        'wildcard': True,
151    }
152
153    client.version = 1
154
155    data = {
156        'challenges': [],
157        'identifier': {
158            'type': 'dns',
159            'value': 'example.com',
160        },
161    }
162    authz = Authorization.from_json(client, data, 'xxx')
163    assert authz.url == 'xxx'
164    assert authz.status == 'pending'
165    assert authz.identifier == 'example.com'
166    assert authz.identifier_type == 'dns'
167    assert authz.challenges == []
168    assert authz.to_json() == {
169        'uri': 'xxx',
170        'challenges': [],
171        'identifier': {
172            'type': 'dns',
173            'value': 'example.com',
174        },
175    }
176
177
178def test_authorization_create_error():
179    client = MagicMock()
180    client.version = 2
181    client.directory.directory = {}
182    with pytest.raises(ACMEProtocolException) as exc:
183        Authorization.create(client, 'dns', 'example.com')
184
185    assert exc.value.msg == 'ACME endpoint does not support pre-authorization.'
186
187
188def test_wait_for_validation_error():
189    client = MagicMock()
190    client.version = 2
191    data = {
192        'challenges': [
193            {
194                'url': 'xxxyyy1',
195                'type': 'dns-01',
196                'status': 'invalid',
197                'error': {
198                    'type': 'dns-failed',
199                    'subproblems': [
200                        {
201                            'type': 'subproblem',
202                            'detail': 'example.com DNS-01 validation failed',
203                        },
204                    ]
205                },
206            },
207            {
208                'url': 'xxxyyy2',
209                'type': 'http-01',
210                'status': 'invalid',
211                'error': {
212                    'type': 'http-failed',
213                    'subproblems': [
214                        {
215                            'type': 'subproblem',
216                            'detail': 'example.com HTTP-01 validation failed',
217                        },
218                    ]
219                },
220            },
221            {
222                'url': 'xxxyyy3',
223                'type': 'something-else',
224                'status': 'valid',
225            },
226        ],
227        'status': 'invalid',
228        'identifier': {
229            'type': 'dns',
230            'value': 'example.com',
231        },
232    }
233    client.get_request = MagicMock(return_value=(data, {}))
234    authz = Authorization.from_json(client, data, 'xxx')
235    with pytest.raises(ACMEProtocolException) as exc:
236        authz.wait_for_validation(client, 'dns')
237
238    assert exc.value.msg == (
239        'Failed to validate challenge for dns:example.com: Status is "invalid". Challenge dns-01: Error dns-failed Subproblems:\n'
240        '(dns-01.0) Error subproblem: "example.com DNS-01 validation failed"; Challenge http-01: Error http-failed Subproblems:\n'
241        '(http-01.0) Error subproblem: "example.com HTTP-01 validation failed".'
242    )
243    data = data.copy()
244    data['uri'] = 'xxx'
245    assert exc.value.module_fail_args == {
246        'identifier': 'dns:example.com',
247        'authorization': data,
248    }
249