Merge ~bloodearnest/snapstore-client:api-docs into snapstore-client:master

Proposed by Simon Davy
Status: Merged
Approved by: Simon Davy
Approved revision: edf1bf9a89d8702bd8abc63f578dc73d260468dc
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~bloodearnest/snapstore-client:api-docs
Merge into: snapstore-client:master
Diff against target: 350 lines (+296/-2)
6 files modified
.gitignore (+1/-0)
Makefile (+9/-2)
docs/en/authentication.md (+161/-0)
docs/en/index.md (+12/-0)
docs/en/overrides.md (+105/-0)
docs/metadata.yaml (+8/-0)
Reviewer Review Type Date Requested Status
Daniel Manrique (community) Approve
Colin Watson (community) Approve
Review via email: mp+333038@code.launchpad.net

Commit message

initial api docs for overrides

Description of the change

initial api docs for overrides

To post a comment you must log in.
f33c99a... by Simon Davy

add missing links

Revision history for this message
Daniel Manrique (roadmr) wrote :

There's some pseudoformat in this document which suggests the intent is markdown rather than rst, in which case I'd suggest checking it with e.g. python-markdown, because right now it doesn't render very nicely. Even if it's not intended for rendering, it'd make sense to still adhere to markdown so it *can* be rendered :)

I was initially super confused and was about to suggest this document was too unfriendly, but then went back and realized the audience are developers who'll interact with the API, so it should be OK to be a bit rough on them.

Also caught some typos and other minor things below.

review: Needs Fixing
e2369aa... by Simon Davy

Move documentation to subdir and make proper markdown

Builds using the documentation-builder tool

bc2dffe... by Simon Davy

add make docs command

Revision history for this message
Simon Davy (bloodearnest) wrote :

Thanks, have addressed formatting and typo issues. The goal of this MP is a first draft to tick the box and have something to iterate on. As such, full docs for macaroons is out of scope for this MP

6f26b12... by Simon Davy

add index page and titles

Revision history for this message
Colin Watson (cjwatson) :
review: Approve
b54a5a4... by Simon Davy

fix typos and minor rewording/reordering

Revision history for this message
Daniel Manrique (roadmr) wrote :

+1 from me, thanks!

review: Approve
edf1bf9... by Simon Davy

s/snapstore/snap store/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2index 2c97cd0..5d2c033 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -3,3 +3,4 @@ __pycache__/
6
7 /.coverage
8 /htmlcov
9+docs/build
10diff --git a/Makefile b/Makefile
11index d13c088..a3b3b78 100644
12--- a/Makefile
13+++ b/Makefile
14@@ -33,6 +33,13 @@ test: $(ENV)/dev
15 $(PYTHON3) -m unittest $(TESTS) 2>&1
16 $(MAKE) --silent lint
17
18+/snap/bin/documentation-builder:
19+ sudo snap install documentation-builder
20+
21+docs: /snap/bin/documentation-builder
22+ documentation-builder --base-dir docs --output-path docs/build
23+
24+
25 lint:
26 $(FLAKE8) $(SERVICE_PACKAGE)
27
28@@ -43,10 +50,10 @@ coverage: $(ENV)/dev
29
30 clean:
31 rm -rf $(TMPDIR)
32- rm -rf dist
33+ rm -rf dist docs/build
34 rm -rf .coverage htmlcov
35 find -name '__pycache__' -print0 | xargs -0 rm -rf
36 find -name '*.~*' -delete
37
38
39-.PHONY: bootstrap test lint coverage clean snap
40+.PHONY: bootstrap test lint coverage clean snap docs
41diff --git a/docs/en/authentication.md b/docs/en/authentication.md
42new file mode 100644
43index 0000000..a6ce31e
44--- /dev/null
45+++ b/docs/en/authentication.md
46@@ -0,0 +1,161 @@
47+---
48+title: Authentication
49+---
50+
51+Authentication
52+==============
53+
54+You will require a valid Ubuntu SSO user to authenticate, and that user
55+must have been configured as an admin in the snapstore.admin tool, e.g.
56+
57+ snapstore.admin add-admin user@example.com
58+
59+The snap store uses macaroons for authentication, which are a kind of bearer
60+token that can be constrained and that can be authorized by third-party
61+services. We strongly recommend using
62+[pymacaroons](https://github.com/ecordell/pymacaroons) or
63+[libmacaroons](https://github.com/rescrv/libmacaroons) to work with these
64+tokens.
65+
66+If you want to understand more about how macaroons work, refer to the [original
67+paper](https://research.google.com/pubs/pub41892.html).
68+
69+
70+To login, you must first get a root macaroon from the snap store proxy,
71+then discharge (verify) that macaroon with Ubuntu SSO
72+(https://login.ubuntu.com/).
73+
74+
75+Root Macaroon
76+-------------
77+
78+To get a root macaroon:
79+
80+```
81+POST https://<store_url>/v2/auth/issue-store-admin
82+```
83+
84+The response is simple and matches this JSON Schema:
85+
86+```json
87+{
88+ 'type': 'object',
89+ 'properties': {
90+ 'macaroon': {'type': 'string'},
91+ },
92+ 'required': ['macaroon'],
93+ 'additionalProperties': False,
94+}
95+```
96+
97+This is your main authentication token, and should be stored persistently.
98+
99+
100+Discharge Ubuntu SSO Caveat
101+---------------------------
102+
103+The root macaroon contains a caveat that the user must have a valid
104+Ubuntu SSO account. To prove that is the case, we need to discharge that
105+caveat with Ubuntu SSO.
106+
107+You need to deserialize this root macaroon, and extract the caveat id
108+with the location 'login.ubuntu.com'. For example, using pymacaroons:
109+
110+```python
111+macaroon = pymacaroons.Macaroon.deserialize(root_macaroon)
112+for caveat in macaroon.caveats:
113+ if caveat.location == 'login.ubuntu.com':
114+ return caveat.caveat_id
115+```
116+
117+Then we need to discharge the caveat with Ubuntu SSO.
118+
119+ POST https://login.ubuntu.com/api/v2/tokens/discharge
120+
121+With request body populated with the user details and the caveat id.
122+Ubuntu SSO doesn't have JSON Schema, but the request body should have
123+the form as follows:
124+
125+```json
126+{
127+ "email": ...,
128+ "password": ...,
129+ "otp": ..., # if user account has 2FA enabled
130+ "caveat_id": ...,
131+}
132+```
133+
134+Response:
135+
136+```json
137+{
138+ "discharge_macaroon": "<serialized discharge>",
139+}
140+```
141+
142+Note: for more detailed responses from Ubuntu SSO, particularly handling
143+invalid credentials and 2FA, see the general
144+[Ubuntu SSO documentation for OAuth tokens](
145+http://canonical-identity-provider.readthedocs.io/en/latest/resources/token.html),
146+which is also used by the macaroon discharge endpoint.
147+
148+You will need to persist the raw root macaroon and the raw discharge
149+macaroon. Together, these are your authentication.
150+
151+
152+Request Authentication
153+----------------------
154+
155+To authenticate a request, you must bind the discharge macaroon to the
156+root macaroon, and send that as your value in an 'Authorization' HTTP
157+header.
158+
159+For example, with pymacaroons:
160+
161+```python
162+root = pymacaroon.Macaroon.deserialize(root_raw)
163+discharge = pymacaroons.Macaroon.deserialize(discharge_raw)
164+bound = root.prepare_for_request(discharge)
165+header = 'Macaroon root="{}", discharge="{}"'.format(root_raw, bound.serialize())
166+```
167+
168+Note: if your discharge macaroon has expired, it will be indicated by
169+indicated by a 401 status code, and the header:
170+
171+```
172+WWW-Authenticate: Macaroon needs_refresh=1
173+```
174+
175+In this case you will need to refresh your discharge macaroon, described below,
176+and retry the request.
177+
178+
179+Refreshing the Discharge Macaroon
180+---------------------------------
181+
182+Your discharge macaroon has an expiry, and needs refreshing with Ubuntu
183+SSO periodically.
184+
185+To do so, simply:
186+
187+ POST https://login.ubuntu.com/api/v2/tokens/refresh
188+
189+With request body:
190+
191+```json
192+{
193+ "discharge_macaroon": "<discharge>"
194+}
195+```
196+
197+The response simply contains the new discharge macaroon.
198+
199+```json
200+{
201+ "discharge_macaroon": "<new discharge>",
202+}
203+```
204+
205+Update and persist this new discharge macaroon for later use.
206+
207+
208diff --git a/docs/en/index.md b/docs/en/index.md
209new file mode 100644
210index 0000000..fe54667
211--- /dev/null
212+++ b/docs/en/index.md
213@@ -0,0 +1,12 @@
214+---
215+title: Index
216+---
217+
218+Snap Store Proxy API Documentation
219+=================================
220+
221+This is the documentation for the snap store proxy, which allows you to override
222+upstream revisions for specific snaps on your snap store proxy.
223+
224+* [Authentication](authentication.html)
225+* [Overrides](overrides.html)
226diff --git a/docs/en/overrides.md b/docs/en/overrides.md
227new file mode 100644
228index 0000000..cfbd628
229--- /dev/null
230+++ b/docs/en/overrides.md
231@@ -0,0 +1,105 @@
232+---
233+title: Overrides
234+---
235+
236+Overrides
237+=========
238+
239+
240+The Overrides API supports two operations: list overrides, and set overrides.
241+
242+All operations require an 'Authorization' header, as described in [Authentication](authentication.html).
243+
244+
245+List Overrides
246+--------------
247+
248+To list overrides for a snap name:
249+
250+```
251+GET https://<store url>/v2/metadata/overrides/{snap_name}
252+X-Ubuntu-Series: 16
253+```
254+
255+Note the header.
256+
257+The response includes a list of any overrides for that snap. The format matches
258+this JSON Schema:
259+
260+```json
261+{
262+ 'type': 'object',
263+ 'properties': {
264+ 'overrides': {
265+ 'type': 'array',
266+ 'items': {
267+ 'type': 'object',
268+ 'properties': {
269+ 'snap_id': {'type': 'string'},
270+ 'snap_name': {'type': 'string'},
271+ 'revision': {
272+ 'type': 'integer',
273+ 'minimum': 1,
274+ },
275+ 'upstream_revision': {
276+ 'type': ['integer', 'null'],
277+ 'minimum': 1,
278+ },
279+ 'channel': {'type': 'string'},
280+ 'architecture': {'type': 'string'},
281+ 'series': {'type': 'string'},
282+ },
283+ 'required': [
284+ 'snap_id', 'snap_name', 'revision', 'upstream_revision',
285+ 'channel', 'architecture', 'series',
286+ ],
287+ 'additionalProperties': False,
288+ },
289+ },
290+ },
291+ 'required': ['overrides'],
292+ 'additionalProperties': False,
293+}
294+```
295+
296+
297+Set Overrides
298+-------------
299+
300+To set overrides:
301+
302+```
303+POST https://<store url>/v2/metadata/overrides
304+```
305+
306+with a request body listing the overrides for each snap/channel.
307+
308+The request format should match the following JSON Schema.
309+
310+```json
311+{
312+ 'type': 'array',
313+ 'items': {
314+ 'type': 'object',
315+ 'properties': {
316+ 'snap_name': {'type': 'string'},
317+ 'revision': {
318+ 'type': ['integer', 'null'],
319+ 'minimum': 1,
320+ },
321+ 'channel': {'type': 'string'},
322+ 'series': {'type': 'string'},
323+ },
324+ 'required': ['snap_name', 'revision', 'channel', 'series'],
325+ 'additionalProperties': False,
326+ },
327+ 'minItems': 1,
328+}
329+```
330+
331+To delete an override, simply set its revision field to null. The "series" field
332+must be set to "16" currently.
333+
334+The response includes the results of the operation. The response format is the
335+same as for listing overrides, except that "revision" can be null if an
336+override was deleted.
337diff --git a/docs/metadata.yaml b/docs/metadata.yaml
338new file mode 100644
339index 0000000..0116991
340--- /dev/null
341+++ b/docs/metadata.yaml
342@@ -0,0 +1,8 @@
343+site_title: Snapstore Proxy API Documentation
344+navigation:
345+ - title: Index
346+ location: en/index.md
347+ - title: Authentication
348+ location: en/authentication.md
349+ - title: Overrides
350+ location: en/overrides.md

Subscribers

People subscribed via source and target branches

to all changes: