diff --git a/index.html b/index.html index 08d63b7..03991c6 100644 --- a/index.html +++ b/index.html @@ -240,6 +240,12 @@

  • [=Process the `scope_extensions` member=], passing |json| and |manifest|.
  • +
  • [=Process the `migrate_from` member=], passing |json|, |manifest| + and |manifest URL|. +
  • +
  • [=Process the `migrate_to` member=], passing |json|, |manifest| + and |manifest URL|. +
  • @@ -1780,22 +1786,6 @@

    different use cases.

    -
    -

    - The web-app-origin-association file -

    -

    - A web-app-origin-association file is a JSON file that can - be used to [=validate scope extensions=]. It confirm an association - between the origin it is in with one or more web applications. It - identifies apps uniquely by referencing manifest [=manifest/ids=]. -

    -

    - Given [=origin=] |origin:origin|, a [=web-app-origin-association=] - file is expected to be downloadable from - [origin]/.well-known/web-app-origin-association. -

    -

    Validating scope extensions @@ -1856,6 +1846,353 @@

    +
    +

    + The web-app-origin-association file +

    +

    + A web-app-origin-association file is a JSON file that can + be used to [=validate scope extensions=] or [=validate origin + migration=]. It confirms an association between the origin it is in + with one or more web applications. It identifies apps uniquely by + referencing manifest [=manifest/ids=]. +

    +

    + Given [=origin=] |origin:origin|, a [=web-app-origin-association=] + file is expected to be downloadable from + [origin]/.well-known/web-app-origin-association. +

    +
    +
    +

    + Web Application Origin Migration +

    +

    + Web Application Origin Migration allows a web application to indicate + to the user agent that it moved from one origin to another (within the + same site). This then enables the user agent to migrate an installed + web application while preserving the user's installation and potentially + their settings. +

    +
    +

    + Usage Example +

    +

    + The following example shows how a web application hosted at + https://old.example.com can be migrated to + https://new.example.com. Both origins must be + [=same site=] and must explicitly agree to the migration. +

    +

    + First, the old application's manifest can optionally include a + `migrate_to` member to signal to the user agent that it intends to + move (alternatively the old application can redirect to the new + application): +

    +
    +          {
    +            "name": "My App",
    +            "id": "/",
    +            "migrate_to": {
    +              "id": "https://new.example.com/",
    +              "install_url": "https://new.example.com/install"
    +            }
    +          }
    +        
    +

    + Next, the new application's manifest includes a `migrate_from` member + to claim the migration from the old application: +

    +
    +          {
    +            "name": "My New App",
    +            "id": "/",
    +            "migrate_from": [
    +              {
    +                "id": "https://old.example.com/",
    +                "install_url": "https://old.example.com/install",
    +                "behavior": "force"
    +              }
    +            ]
    +          }
    +        
    +

    + Finally, the old origin must host a `web-app-origin-association` + file to definitively validate that it permits the new application to + take over. Without this file, a malicious new application could + claim to migrate from an old application without permission. +

    +
    +          {
    +            "https://new.example.com/": {
    +              "allow_migration": true
    +            }
    +          }
    +        
    +
    +
    +

    + migrate_from member +

    +

    + The [=manifest/migrate_from=] member is a [=list=] of either + [=strings=] or [=ordered maps=] that identifies the old web + applications that are being migrated from. +

    +

    + If an entry is a [=string=], it is treated as the [=manifest/id=] of + the old application. +

    +

    + If an entry is an [=ordered map=], it can have the following members: +

    +
      +
    • + id: A + [=string=] representing the [=manifest/id=] of the old application. +
    • +
    • + install_url: A [=string=] + representing a URL of a page that links to the old application's + manifest. A user agent can use this field to fetch the manifest of + the old application in order to apply updates even if every other + URL that is part of the old application redirects to the new + application. +
    • +
    • + behavior: A + [=string=] that can be either `"suggest"` or `"force"`. This is a + hint to the user agent that might influence how forceful the UI is + that the user agent presents to the user to tell them about the + migration. +
    • +
    +
    + For the user agent to process the [=manifest/migrate_from=] member, + the manifest needs to also contain a valid explicit + id member in its JSON. +
    +
    +
    +

    + migrate_to member +

    +

    + The [=manifest/migrate_to=] member is an optional [=ordered map=] that + proactively signals a migration to a new application. It has the + following members: +

    +
      +
    • + id: A + [=string=] representing the [=manifest/id=] of the new application. +
    • +
    • + install_url: A [=string=] + representing a URL of a page that links to the new application's + manifest. The manifest on this page needs to include a + [=manifest/migrate_from=] field that points back at this application + for the migration to be processed. +
    • +
    +
    +
    +

    + Validating origin migration +

    +

    + To validate origin migration, given [=URL=] + |old_manifest_id:URL| and [=URL=] |new_manifest_id:URL|: +

    +
      +
    1. If |old_manifest_id|'s [=URL/origin=] and |new_manifest_id|'s + [=URL/origin=] are not [=same site=], return an error. +
    2. +
    3. If |old_manifest_id|'s [=URL/origin=] is [=same origin=] as + |new_manifest_id|'s [=URL/origin=], return success. +
    4. +
    5. Let [=URL=] |origin_url:URL| be the [=URL/origin=] of + |old_manifest_id|. +
    6. +
    7. Let [=URL=] |download_url:URL| be the result of [=URL + Parser|parsing=] "/.well-known/web-app-origin-association" using + |origin_url| as the base [=URL=]. +
    8. +
    9. Let [=ordered map=] |json:ordered map| be the result of a + [=fetch=] given |download_url|. +
    10. +
    11. If the [=fetch=] failed, return an error. +
    12. +
    13. If |json|[|new_manifest_id|] does not [=map/exist=] or is not an + [=ordered map=], return an error. +
    14. +
    15. If |json|[|new_manifest_id|]["allow_migration"] is not `true`, + return an error. +
    16. +
    17. Return success. +
    18. +
    +
    +
    +

    + Processing the `migrate_from` member +

    +

    + To process the `migrate_from` member, given [=ordered + map=] |json:ordered map|, [=ordered map=] |manifest:ordered map|, and + [=URL=] |manifest URL:URL|: +

    +
      +
    1. If |json|["id"] does not [=map/exist=] or is not a [=string=], + return. +
    2. +
    3. If |json|["id"] is the empty string, return. +
    4. +
    5. Let |base_origin:origin| be |manifest|["start_url"]'s + [=URL/origin=]. +
    6. +
    7. Let |id_url:URL| be the result of [=URL Parser|parsing=] + |json|["id"] with |base_origin| as the base URL. +
    8. +
    9. If |id_url| is failure, return. +
    10. +
    11. If |id_url| is not [=same origin=] as |manifest|["start_url"], + return. +
    12. +
    13. Let |processed_migrate_from:list| be a new [=list=]. +
    14. +
    15. Set |manifest|["migrate_from"] to |processed_migrate_from|. +
    16. +
    17. If |json|["migrate_from"] does not [=map/exist=] or is not a + [=list=], return. +
    18. +
    19. [=list/For each=] |entry| of |json|["migrate_from"]: +
        +
      1. Let |processed_entry:ordered map| be a new [=ordered map=]. +
      2. +
      3. If |entry| is a [=string=]: +
          +
        1. Let |id:URL| be the result of [=URL Parser|parsing=] + |entry| with |manifest URL| as the base URL. +
        2. +
        3. If |id| is failure, [=iteration/continue=]. +
        4. +
        5. Set |processed_entry|["id"] to |id|. +
        6. +
        +
      4. +
      5. Else if |entry| is an [=ordered map=]: +
          +
        1. If |entry|["id"] does not [=map/exist=] or is not a + [=string=], [=iteration/continue=]. +
        2. +
        3. Let |id:URL| be the result of [=URL Parser|parsing=] + |entry|["id"] with |manifest URL| as the base URL. +
        4. +
        5. If |id| is failure, [=iteration/continue=]. +
        6. +
        7. Set |processed_entry|["id"] to |id|. +
        8. +
        9. If |entry|["install_url"] [=map/exists=] and is a + [=string=]: +
            +
          1. Let |install_url:URL| be the result of [=URL + Parser|parsing=] |entry|["install_url"] with |manifest + URL| as the base URL. +
          2. +
          3. If |install_url| is not failure, set + |processed_entry|["install_url"] to |install_url|. +
          4. +
          +
        10. +
        11. If |entry|["behavior"] [=map/exists=] and is a + [=string=]: +
            +
          1. If |entry|["behavior"] is "suggest" or "force", set + |processed_entry|["behavior"] to |entry|["behavior"]. +
          2. +
          +
        12. +
        +
      6. +
      7. Else, [=iteration/continue=]. +
      8. +
      9. Validate |processed_entry|["id"] using the algorithm from + [=validate origin migration=], given [=URL=] + |processed_entry|["id"] and [=URL=] |id_url|. +
      10. +
      11. If the previous step returned an error, + [=iteration/continue=]. +
      12. +
      13. [=list/Append=] |processed_entry| to + |processed_migrate_from|. +
      14. +
      +
    20. +
    +
    +
    +

    + Processing the `migrate_to` member +

    +

    + To process the `migrate_to` member, given [=ordered + map=] |json:ordered map|, [=ordered map=] |manifest:ordered map|, and + [=URL=] |manifest URL:URL|: +

    +
      +
    1. If |json|["migrate_to"] does not [=map/exist=] or is not an + [=ordered map=], return. +
    2. +
    3. Let |entry:ordered map| be |json|["migrate_to"]. +
    4. +
    5. If |entry|["id"] does not [=map/exist=] or is not a [=string=], + return. +
    6. +
    7. Let |id:URL| be the result of [=URL Parser|parsing=] + |entry|["id"] with |manifest URL| as the base URL. +
    8. +
    9. If |id| is failure, return. +
    10. +
    11. Let |processed_migrate_to:ordered map| be a new [=ordered map=]. +
    12. +
    13. Set |processed_migrate_to|["id"] to |id|. +
    14. +
    15. If |entry|["install_url"] [=map/exists=] and is a [=string=]: +
        +
      1. Let |install_url:URL| be the result of [=URL + Parser|parsing=] |entry|["install_url"] with |manifest URL| as + the base URL. +
      2. +
      3. If |install_url| is not failure, set + |processed_migrate_to|["install_url"] to |install_url|. +
      4. +
      +
    16. +
    17. Set |manifest|["migrate_to"] to |processed_migrate_to|. +
    18. +
    +
    +
    +

    + Privacy and Security Considerations +

    +

    + To prevent malicious actors from silently taking over applications (e.g., a simple calculator updating itself to mimic a banking app), the [=validate origin migration=] algorithm enforces a two-way handshake. Both the new and the old application origins must explicitly agree to the migration via the [=web-app-origin-association=] file. +

    +

    + Furthermore, migrations are restricted to be [=same site=] to ensure they are used for legitimate rebranding and architecture changes within an organization's control, rather than transferring ownership to unverified third parties. +

    +

    + User agents should consider including an explicit user confirmation dialog as part of the migration flow, especially if an update to security-sensitive fields (such as the app name and icons) is included. +

    +
    +

    External application resource