Skip to content

Admin page: export form definitions and data with attachments #779

Description

@ebruchez

Feature

The form definition, its dependencies (images, attachments), and/or form data and dependencies, are exported into a zip archive, which can be imported back into a Form Builder/Form Runner installation.

Use cases

  • Forms are developed on a instance of Form Builder distinct from the one used for staging and/or production and not reachable via direct internet connection.
  • Archival/backup of forms
  • Exchange of forms
  • Migrating from one persistence implementation to another (e.g. eXist to relational)
    • We won't support exporting from eXist at this point as that implementation has been removed.
  • Exporting from database to Orbeon Forms resources

Location of functionality

  • Form Runner Admin page
  • Form Runner Summary page
  • P2: Form Builder Summary/Detail pages for non-published form definitions

Steps

  • retrieve form definition, related dependencies, form data
  • use zip processor (or probably from Scala) to produce downloadable zip file
  • multiple forms can be selected for export when possible
  • specific data can be selected for export when possible (from Summary page only)

Service

It might be good to implement most of the functionality as a service first. This would also enable a scripted mode which would not require the Orbeon Forms user interface.

NOTE: This however would still require a running Form Builder instance.

  • This is implemented as a service that returns a zip archive or an error.
  • You can pass the service the id of a form, and/or search values as shown in the Form Builder Summary page. This selects 0 or more forms.
  • Each form selected is included in the archive.
  • If possible, calling the service from the command-line could be done with a simple wrapper around a tool like - wget/curl. If that is not possible or desirable, another solution can be found.

Activity

  1. ebruchez commented on Jun 6, 2013

    @ebruchez
    CollaboratorAuthor

    See also #1055.

  2. ebruchez commented on Feb 18, 2015

    @ebruchez
    CollaboratorAuthor

    Also exporting data with dependencies should also be an option.

  3. ebruchez commented on Feb 25, 2015

    @ebruchez
    CollaboratorAuthor

    Also requested: ability to export selected subset of data only. See customer thread.

  4. ebruchez commented on Oct 18, 2016

    @ebruchez
    CollaboratorAuthor
  5. ebruchez commented on May 2, 2018

    @ebruchez
    CollaboratorAuthor
  6. ebruchez commented on May 17, 2018

    @ebruchez
    CollaboratorAuthor

    For this customer , handling form definition only is enough and there is no requirement to handle data as well. This might simply at little bit a first implementation.

  7. ebruchez commented on May 18, 2018

    @ebruchez
    CollaboratorAuthor

    Versioning should be handled as well:

    • upon exporting, the user must be able to select the version(s) to export
    • version numbers must be included in the zip archive

    See also #1451 and #3448 about relevant improvements to versioning on the Home page.

  8. ebruchez commented on May 18, 2018

    @ebruchez
    CollaboratorAuthor

    The requirement also calls for:

    • doing this manually via a Form Runner UI
    • doing this via an API
  9. avernet commented on Jun 27, 2018

    @avernet
    Collaborator
  10. ebruchez commented on Jul 10, 2018

    @ebruchez
    CollaboratorAuthor

    +1 from customer as an option to migrate between providers (although another, explicit way of migrating between providers might be better).

  11. ebruchez commented on Jun 15, 2020

    @ebruchez
    CollaboratorAuthor
  12. ebruchez commented on Nov 9, 2021

    @ebruchez
    CollaboratorAuthor

    +1 from customer for archival of data.

    ZIP64 came out with the ZIP 4.5 specification in 2001 and Java 7 (2011) supports ZIP64. So file size for large exports should not be a problem.

  13. ebruchez commented on May 29, 2023

    @ebruchez
    CollaboratorAuthor

    First step: write "archive export API". Parameters:

    • date range

      • all
      • last-modified-time-gt/last-modified-time-ge
      • last-modified-time-lt/last-modified-time-le (optional)
    • data vs. form vs. both (vs. attachments?)

      • content=form-definition|form-data|attachments ?
    • app/form/form-version

      • app: part of the path, optional
      • form: part of the path, optional
      • form-version: URL parameter or header?
        • Search API uses Orbeon-Form-Definition-Version header for latest/specific/all
          • but that might not have been the best choice vs. a URL parameter
      • document-id: part of the path, optional (requires app/form)
        • scenario: export a single instance of form data with its attachments
    • P2: "dry run" to just return the count(s) of operations

    New entry point:

    /fr/service/persistence/export/$app/$form/$document-id?form-version=$form-version
    

    $app/$form/$document-id/form-version are all optional, but in constrained ways:

    • no parameter: export everything
      • we can imagine allowing form-version=latest|all, but specific doesn't make much sense here
    • $app only: export everything under the app
      • we can imagine allowing form-version=latest|all, but specific doesn't make much sense here
    • $app and $form
      • form-version=latest|specific|all
    • $app and $form and document-id
      • form-version: can be omitted or specific, but if so must match
  14. ebruchez commented on May 29, 2023

    @ebruchez
    CollaboratorAuthor

    We are implementing this mostly at the level of the persistence proxy, not directly in the providers.

    • a request that needs to search for form definitions and their versions, use the Form metadata API
    • a request that needs to search for data uses the Search API
    • GETting data and attachments use the CRUD API
  15. 32 remaining items

  16. ebruchez commented on Dec 8, 2023

    @ebruchez
    CollaboratorAuthor

    Permissions: I think we had decided that this was a admin function, which would export all data regardless of permissions.

    • check whether this is indeed the current behavior
    • document that aspect
  17. ebruchez commented on Dec 28, 2023

    @ebruchez
    CollaboratorAuthor

    To check the permissions behavior:

    • as user clerk, enter data that only user can read, with no permissions for user admin
    • as user admin, export form and data
  18. ebruchez commented on Dec 28, 2023

    @ebruchez
    CollaboratorAuthor

    Conclusion of the above:

    • as admin I am getting a zip with the form definition, but no data
    • as clerk (which also can access the Admin page), I am not getting any download (and no error), but there is a 403 in the logs
  19. ebruchez commented on Dec 28, 2023

    @ebruchez
    CollaboratorAuthor

    The 403 comes when:

    • calling /fr/service/mysql/history/test/export-permissions/2cadd6415e082486f6939a65c4aeaa4ea527783f?page-size=100&page-number=1
    • findAuthorizedOperationsOrThrow() doesn't find any operations

    However, this is wrong, because this takes the same code path as if for a GET of individual data. But this calls the /history API, and ends up with the "PUT and no data, so this is a 1" branch, and authorizedOperationsForNoData(), which returns no permissions.

    So even if we wanted to honor permissions, this would be wrong.

    But does this point to a bug with the /history API?

    • check and enter /history API bug if needed

    However, we do not want permissions to be honored, as per the decision above.

  20. ebruchez commented on Dec 28, 2023

    @ebruchez
    CollaboratorAuthor

    If we want the current permissions not to be honored, we need some new sort of setting to override the default behavior. This is not something we have right now. This would be the same as passing Permissions.Undefined, which would produce AnyOperation. That, or passing Permissions.Defined() granting Read to Anyone would work.

    However, of course, we'd need to be very careful and to enable this by passing an encrypted parameter for export, which would only be passed in this case.

    An external service call API would at this point not support this, and would honor permissions (but see possible /history but above).

  21. ebruchez commented on Dec 28, 2023

    @ebruchez
    CollaboratorAuthor

    In addition, the Search API also needs to support all operations so that all the data is returned.

  22. ebruchez commented on Dec 28, 2023

    @ebruchez
    CollaboratorAuthor

    So the idea is:

    • Admin page calls export/purge API with the encrypted admin param
    • export/purge API reads and then passes/forwards that parameter to the relevant persistence APIs
      • Search
      • CRUD
      • History (which currently ignores it?)
  23. ebruchez commented on Dec 29, 2023

    @ebruchez
    CollaboratorAuthor

    Instead of an encrypted param, now using a token with expiration.

  24. ebruchez commented on Dec 29, 2023

    @ebruchez
    CollaboratorAuthor

    Closing as all major items are done or new RFEs have been entered.

  25. ebruchez commented on Dec 29, 2023

    @ebruchez
    CollaboratorAuthor

    The idea of using a token is that we have some trusted source, in this case the Admin page, calling a service, in this case /fr/service/persistence/export. We'd like that source to tell the service that it has some special permissions, in this case "I can list and read everything" (and, in the case of /fr/service/persistence/purge, "I can list and read and delete everything"). This information has to be forwarded to the Search, CRUD, and History APIs down the line.

    My first idea was to say that we just add an internal URL parameter that says "I have these rights". But I didn't want that parameter to be usable by everyone, at least not until we figure out all the security implications. It is true that, for service calls, we currently allow any authorized service caller to then impersonate any user. However, after that, permissions still kick in. A regular, non-enrypted parameter could be ok, or not. Also, services can also be called internally, for example by a form service. Someone could, by design or mistakenly, design a form that calls the CRUD API with that parameter, and gain access to other users' data, bypassing permissions.

    So then I figured I'd just use an encrypted parameter, as we do for mode changes. However, in case of an error, the internal URL could be output to the logs, and someone obtaining the log could then know what encrypted parameter value to pass to gain more access, forever (or until the encryption password is changed).

    So instead, I used a token like we use for the access token, which has the benefit of having an expiration time. Even if someone has access to the token through logs or otherwise, the token will expire. In this case, it expires in a matter of seconds, enough for an internal request to go through, but hopefully not enough to give time to someone to gain access if by any chance a token value was obtained. At last, this covers the case of logs being shared.

    I cannot use the exact logic used by the access token, because that requires forms that enable token access. So currently, the token says "I am an admin and I can do anything with the data". We could change this to be more like the access token and list individual operations. This would allow /export to only request read and list permissions, for example.

    One last question is the creation of the token. If someone could have access to the function that creates the token, that could be a problem. This function is internal, but we should harden it. One idea would be, for example, to whitelist pages and services where the function can be successful. Specifically:

    • /fr/admin
    • /fr/service/persistence/export
    • /fr/service/persistence/purge

    Tasks:

    • whitelist allowed token creators
  26. ebruchez commented on Feb 23, 2024

    @ebruchez
    CollaboratorAuthor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions