Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC2140: Terms of Service for ISes and IMs #2140

Merged
merged 62 commits into from Aug 26, 2019
Merged
Changes from 3 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
23af87e
Proposal for IS & IM TOS API
dbkr Jun 20, 2019
32c7fc6
you have a number now
dbkr Jun 20, 2019
cf48030
One more tradeoff
dbkr Jun 20, 2019
276e2b6
Typo
dbkr Jun 21, 2019
d4ca0c2
Specify ID grammar and add comma
dbkr Jun 21, 2019
9ca3ccc
Add requirments section for de-duping between services.
dbkr Jun 21, 2019
a63e442
Linkify
dbkr Jun 24, 2019
4ba9b2a
perfix
dbkr Jun 24, 2019
2555801
m.third_party_terms -> m.accepted_terms
dbkr Jun 24, 2019
8ae4755
s/Third Party/Accepted/
dbkr Jun 24, 2019
abb4071
HS docs must be added too
dbkr Jun 24, 2019
2c09580
line wrap
dbkr Jun 25, 2019
6f374dc
Re-write for OpenID auth
dbkr Jun 25, 2019
0dae2d5
GET terms must be unauthed.
dbkr Jun 25, 2019
9e0d8b9
Use M_CONSENT_NOT_GIVEN
dbkr Jun 25, 2019
5709427
Typing hard is
dbkr Jun 26, 2019
af691b5
Clarify this applies to 2134
dbkr Jun 26, 2019
1d75828
Clarify what to do if no (new) docs
dbkr Jun 26, 2019
ba7047c
Clarify we must be accepting HS auth
dbkr Jun 26, 2019
4edf826
Capitalise on our identifiers
dbkr Jun 26, 2019
6273868
Clarify v1 API deprecation
dbkr Jun 26, 2019
58cf083
backwards compat
dbkr Jun 26, 2019
2694bb1
Add really horrible custom HTTP header
dbkr Jun 26, 2019
21b9eaf
No custom HTTP headers
dbkr Jun 26, 2019
b5326de
Exclude requestToken endpoints from auth requirement
dbkr Jun 27, 2019
10a6a59
Deprecate `bind_email` / `bind_msisdn`
dbkr Jun 27, 2019
f95197b
make the many-anded sentence a list
dbkr Jun 27, 2019
4be283c
Typing
dbkr Jun 27, 2019
83bb386
line wrap
dbkr Jun 28, 2019
45d6309
back to M_TERMS_NOT_SIGNED
dbkr Jun 28, 2019
786d5bc
rewrite UI auth tradeoffs
dbkr Jun 28, 2019
fe14d3c
Spec terms response
dbkr Jun 28, 2019
8af35be
Typo
dbkr Jul 2, 2019
2d11217
Typo
dbkr Jul 2, 2019
5374030
Drop application/x-form-www-urlencoded in v2
dbkr Jul 2, 2019
f02e4c2
both registers are excluded from auth
dbkr Jul 2, 2019
d00dfb7
exclude submittoken too
dbkr Jul 2, 2019
03e6ab0
re-word double openid
dbkr Jul 2, 2019
7f65364
Typo
dbkr Jul 2, 2019
ac6b9bd
s/deprecate/remove/
dbkr Jul 2, 2019
79dbad2
remove acceptance token mention
dbkr Jul 2, 2019
10858bf
set account data after registration
dbkr Jul 2, 2019
4c72c37
slash
dbkr Jul 2, 2019
e28f7aa
slash
dbkr Jul 2, 2019
d15c9df
fullstop
dbkr Jul 2, 2019
1a66934
http status code
dbkr Jul 2, 2019
30dcc28
try & clarify that HS signature isn't the only acceptable auth for un…
dbkr Jul 4, 2019
bf8a1e5
Add way to get the HS to bind/unbind existing 3pids
dbkr Jul 5, 2019
701d340
Remove exception for request/submitToken
dbkr Jul 5, 2019
9bb6ad8
typo
dbkr Jul 10, 2019
f474b31
typo
dbkr Jul 12, 2019
6e061b1
unnecessary capital
dbkr Jul 12, 2019
25a47af
unnecessary capital mk. 2
dbkr Jul 12, 2019
a1de6ff
Hopefully clarify some bits
dbkr Jul 15, 2019
d9269b0
Exclude pubkey endpoints from auth
dbkr Jul 15, 2019
e4bdc28
Apply suggestions from code review
dbkr Aug 19, 2019
12377fb
/account/logout not /logout
dbkr Aug 19, 2019
6d00673
clarify error proxying
dbkr Aug 19, 2019
4073d94
Typo
dbkr Aug 21, 2019
6931541
Typo
dbkr Aug 21, 2019
8bd9d7c
Add full stop
dbkr Aug 21, 2019
4ea8f64
is_token -> id_access_token and add invite to proxy list
dbkr Aug 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
211 changes: 211 additions & 0 deletions proposals/2140-terms-of-service-2.md
@@ -0,0 +1,211 @@
# MSC2140: Terms of Service API for Identity Servers and Integration Managers

MSC1692 introduces a method for homeservers to require that users read and
dbkr marked this conversation as resolved.
Show resolved Hide resolved
agree to certain documents before being permitted to use the service. This
proposal introduces a corresponding method that can be used with Identity
Servers and Integration Managers.

The challenge here is that Identity Servers do not require any kind of user
login to access the service and so are unable to track what users have agreed
to what terms in the way that Homeservers do. We thereforce cannot re-use the
same method for Identity Servers without fundamentally changing the Identity
Service API.

Requirements for this proposal are:
* ISs and IMs should be able to give multiple documents a user must agree to
dbkr marked this conversation as resolved.
Show resolved Hide resolved
abide by
* Each document shoud be versioned
* ISs and IMs must be able to prevent users from using the service if they
dbkr marked this conversation as resolved.
Show resolved Hide resolved
have not provided agreement.
* A user should only have to agree to each version of each document once for
their Matrix ID, ie. having agreed to a set of terms in one client, they
should not have to agree to them again when using a different client.

## Proposal

Throuhgout this proposal, $prefix will be used to refer to the prefix of the
API in question, ie. `/_matrix/identity/api/v1` for the IS API and
`/_matrix/integrations/v1` for the IM API.

This proposal introduces:
* The `$prefix/terms` endpoint
* The `m.third_party_terms` section in account data
* The `X-TERMS-TOKEN` HTTP header

### Terms API

New API endpoints will be introduced:

#### `GET $prefix/terms`:
This returns a set of documents that the user must agree to abide by in order
to use the service. Its response is similar to the structure used in the
`m.terms` UI auth flow of the Client/Server API:

```json
{
"policies": {
"terms_of_service": {
dbkr marked this conversation as resolved.
Show resolved Hide resolved
"version": "2.0",
"en": {
"name": "Terms of Service",
"url": "https://example.org/somewhere/terms-2.0-en.html"
},
"fr": {
"name": "Conditions d'utilisation",
"url": "https://example.org/somewhere/terms-2.0-fr.html"
}
}
}
}
```

Each document (ie. key/value pair in the 'policies' object) MUST be
turt2live marked this conversation as resolved.
Show resolved Hide resolved
uniquely identified by its URL. It is therefore strongly recommended
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to uniquely identify by tuple of (<type>, <version>, <url>)? That would give de-duplication while still allowing just https://matrix.org/legal.html, as well as not having to do any crazy lookups?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly - otoh I sort of like forcing the version to be in the URL for general URL transparency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm just a little nervous that if we end up having three or more different services, each with multiple policies, with some churn of versions of the years, plus an increasing number of languages, then we'll end up with quite a lot of different stuff to check.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Travis points out that we discussed this kind of thing in #2140 (comment) as eventually the conclusion was that de-duping by URL is probably the lesser evil.

that the URL contains the version number of the document. The name
and version keys, however, are used only to provide a human-readable
description of the document to the user.

In the IM API, the client should provide authentication for this endpoint.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For readers: This would be done through https://github.com/turt2live/matrix-dimension/blob/master/docs/reference/scalar_auth.md at the moment, but in future could be #1961


#### `POST $prefix/terms`:
Requests to this endpoint have a single key, `user_accepts` whose value is
a list of URLs (given by the `url` field in the GET response) of documents that
the user has agreed to:

```json
{
"user_accepts": ["https://example.org/somewhere/terms-2.0-en.html"]
turt2live marked this conversation as resolved.
Show resolved Hide resolved
}
```

In the IM API, the client should provide authentication for this endpoint.

The clients MUST include the correct URL for the language of the document that
turt2live marked this conversation as resolved.
Show resolved Hide resolved
was presented to the user and they agreed to. How servers store or serialise
acceptance into the `acceptance_token` is not defined, eg. they may internally
dbkr marked this conversation as resolved.
Show resolved Hide resolved
transform all URLs to the URL of the English-language version of each document
if the server deems it appropriate to do so. Servers should accept agreement of
any one language of each document as sufficient, regardless of what language a
client is operating in: users should not have to re-consent to documents if
they change their client to a different language.

The response MAY contain a `acceptance_token` which, if given, is an
opaque string that the client must store for user in subsequent requests
dbkr marked this conversation as resolved.
Show resolved Hide resolved
to any endpoint to the same server.

If the server has stored the fact that the user has agreed to these terms,
(which implies the user is authenticated) it can supply no `acceptance_token`.
The server may instead choose to supply an `acceptance_token`, for example if,
as in the IS API, the user is unauthenticated and therefore the server is
unable to store the fact a user has agreed to a set of terms.

The `acceptance_token` is opaque and it is up to the server how it computes it,
dbkr marked this conversation as resolved.
Show resolved Hide resolved
but the server must be able to given an `acceptance_token`, compute whether it
constitutes agreement to a given set of terms. For example, the simplest (but
most verbose) implemenation would be to make the `acceptance_token` the JSON
array of documents as provided in the request. A smarter implementation may be
a simple hash, or even cryptograhic hash if desired.

### Third-Party Terms Account Data

This proposal also defines the `m.third_party_terms` section in User Account
Data in the client/server API that clients SHOULD use to track what sets of
terms the user has consented to. This has an array of URLs under the 'accepted'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we have a dictionary policy_id -> policy_url instead of an array of URLs?
That will help to know which types of document the user has already signed. Matrix clients could then detect an update of one policy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might also be useful to know the version of the document the user saw too, so could have:

{
    "accepted": [
        {
            "url": "https://example.org/somewhere/terms-1.2-en.html",
            "version": "1.2",
            "id": "terms_of_service",
        },
        {
            "url": "https://example.org/somewhere/privacy-1.2-en.html",
            "version": "1.2",
            "id": "privacy_policy",
        }
    ]
}

...maybe?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this MSC, the policy ID feels like it is not meant to be trusted and could change at any time, so I am wary of making any assumptions about it. Maybe the MSC should explicitly state something like: "The terms response contains these policy IDs largely to match similar behaviour from the homeserver, but don't assume anything about them, they may change at any time, etc. The URL is the thing that matters."

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the homeserver version being MSC1692 where the policy ID actually means something. The homeserver version supports optional and non-optional changes to policies whereas this MSC does not (for valid reasons on both fronts, I believe).

key to which the user has agreed to.

An `m.third_party_terms` section therefore resembles the following:

```json
{
"accepted": [
"https://example.org/somewhere/terms-1.2-en.html",
"https://example.org/somewhere/privacy-1.2-en.html"
]
}
```

Whenever a client submits a `POST $prefix/terms` request to an IS or IM, it
SHOULD update this account data section adding any the URLs of any additional
documents that the user agreed to to this list.

### Terms Acceptance in the API

Any request to any endpoint in the IS and IM APIs, with the exception of
`/_matrix/identity/api/v1` may return a `M_TERMS_NOT_SIGNED` errcode. This
indicates that the user must agree to (new) terms in order to use or continue
to use the service.

The client uses the `GET $prefix/terms` endpoint to get the latest set of terms
that must be agreed to. It then cross-references this set of documents against
the `m.third_party_terms` account data and presents to the user any documents
that they have not already agreed to, along with UI for them to indicate their
agreement. Once the user has indicated their agreement, then, and only then,
must the client use the `POST $prefix/terms` API to signal to the server the
set of documents that the user has agreed to.

If the server returns an `acceptance_token`, the client should include this
token in the `X-TERMS-TOKEN` HTTP header in all subsequent requests to an
endpoint on the API with the exception of `/_matrix/identity/api/v1`.

Both making the `POST $prefix/terms` request and providing an `X-TERMS-TOKEN`
header signal that the user consents to the terms contained within the
corresponding documents. That is to say, if a client or user obtains an
acceptance token via means other than a response to the `POST $perfix/terms`
dbkr marked this conversation as resolved.
Show resolved Hide resolved
API, inclusion of the acceptance token in an `X-TERMS-TOKEN` header in a
request still constitutes agreement to the terms in the corresponding
documents.

## Tradeoffs

This introduces a different way of accepting terms from the client/server API
which uses User-Interactive Authentication. In the client/server API, the use
of UI auth allows terms acceptance to be integrated into the registration flow
in a simple and backwards-compatible way. Indtroducing the UI Auth mechanism
into these other APIs would add significant complexity, so this functionality
turt2live marked this conversation as resolved.
Show resolved Hide resolved
has been provided with simpler, dedicated endpoints.

The `m.third_party_terms` section contains only URLs of the documents that
have been agreed to. This loses information like the name and version of
the document, but:
* It would be up to the clients to copy this information correctly into
account data.
* Having just the URLs makes it much easier for clients to make a list
turt2live marked this conversation as resolved.
Show resolved Hide resolved
of URLs and find documents not already agreed to.

## Potential issues
turt2live marked this conversation as resolved.
Show resolved Hide resolved

If the server does not authentcate users, some mechanism is required to track
users agreement to terms. The introduction of an extra HTTP header on all
requests adds overhead to every request and complexity to the client to add a
custom header.


## Security considerations

The `acceptance_token` is, in effect, a cookie and could be used to identify
users of the service. Users of the Integration manager must be authenticated
anyway, so this is irrelevant for the IM API. It could allow an Identity Server
to identify users where it may otherwise not be able to do so (if a client was
careful to mask other identifying HTTP headers). Given most requests to the IS
API, by their nature, include 3pids which, even if hashed, will make users
easily identifiable, this probably does not add any significant concern.

It is assumed that once servers publish a given version of a document at a
given URL, the contents of that URL will not change. This could be mitigated by
identifying documents based on a hash of their contents rather than their URLs.
dbkr marked this conversation as resolved.
Show resolved Hide resolved
Agreement to terms in the client/server API makes this assumption, so this
proposal aims to be consistent.


## Conclusion

This proposal adds an error response to all endpoints on the API and a custom
HTTP header on all requests that is used to signal agreement to a set of terms
and conditions. The use of the header is only necessary if the server has no
other means of tracking acceptance of terms per-user. The IS API is not
authenticated so ISes will have no choice but to use the header. The IM API is
authenticated so IMs may either use the header or store acceptance per-user.

A separate endpoint is specified with a GET request for retrieving the set
of terms required and a POST to indicate that the user consents to those
terms.