Metadata-Version: 2.1
Name: formsg-python-sdk
Version: 0.1.13
Summary: Python SDK for FormSG
License: MIT
Author: Chin Ying
Author-email: chinying@open.gov.sg
Requires-Python: >=3.6.2,<4.0.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Requires-Dist: PyNaCl (>=1.5.0,<2.0.0)
Requires-Dist: pytest (>=6.2.5,<7.0.0)
Requires-Dist: requests (>=2.27.1,<3.0.0)
Requires-Dist: typing_extensions (>=4.0.0,<5.0.0); python_version < "3.11"
Description-Content-Type: text/markdown

# formsg-python-sdk
This is the Python version of the [Form Javascript SDK](https://github.com/opengovsg/formsg-javascript-sdk/), which provides utility functions for verifying FormSG webhooks, as well as decrypting form submissions.

Note that this library is still in beta. 

## Requirements
Python 3.6+
## Quickstart
`pip install formsg-python-sdk`
```python
import formsg
# accepts STAGING or PRODUCTION, determines whether to use staging or production public signing keys
sdk = formsg.FormSdk("PRODUCTION")

# Your form's secret key downloaded from FormSG upon form creation
FORM_SECRET_KEY = "YOUR-SECRET-KEY"

# This is where your domain is hosted, and should match
# the URI supplied to FormSG in the form dashboard
YOUR_WEBHOOK_URI = "your-webhoook-uri"

# decryption without attachments
# if `verifiedContent` exists as a key in `encrypted_payload`, the return object will include a verified key
decrypted = sdk.crypto.decrypt(FORM_SECRET_KEY, encrypted_payload)

# webhook authentication
HEADER_RESP = "req.header.'x-formsg-signature'"
sdk.webhooks.authenticate(header=HEADER_RESP, uri=YOUR_WEBHOOK_URI)

# decryption with attachments
decrypted_with_attachments = sdk.crypto.decrypt_attachments(FORM_SECRET_KEY, encrypted_payload)
```

Refer to the [example app](https://github.com/opengovsg/formsg-python-sdk/blob/develop/example_app/flask.py) if you're running a flask server.

## End-to-end Encryption
### Format of Submission Response

| Key                    | Type                   | Description                                                                                              |
| ---------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------- |
| formId                 | str                 | Unique form identifier.                                                                                  |
| submissionId           | str                 | Unique response identifier, displayed as 'Response ID' to form respondents                               |
| encryptedContent       | str                 | The encrypted submission in base64.                                                                      |
| created                | str                 | Creation timestamp.                                                                                      |
| attachmentDownloadUrls | Mapping[str, str] | (Optional) Records containing field IDs and URLs where encrypted uploaded attachments can be downloaded. |

### Format of Decrypted Submissions

`crypto.decrypt(form_secret_key: str, decrypt_params: DecryptParams)`
takes in `decrypt_params` as the second argument, and returns an an object with
the shape

<pre>
{
  responses: FormField[]
  verified?: NotRequired[Mapping[str, Any]]
}
</pre>

The `decryptParams.encryptedContent` field decrypts into a list of `FormField` objects, which will be assigned to the `responses` key of the returned object.

Furthermore, if `decryptParams.verifiedContent` exists, the function will
decrypt and open the signed decrypted content with the package's own
`signingPublicKey` in
[`constants.py`](https://github.com/opengovsg/formsg-python-sdk/blob/develop/formsg/constants.py).
The resulting decrypted verifiedContent will be assigned to the `verified` key
of the returned object.

> **NOTE** <br>
> If any errors occur, either from the failure to decrypt either `encryptedContent` or `verifiedContent`, or the failure to authenticate the decrypted signed message in `verifiedContent`, `None` will be returned.

Note that due to end-to-end encryption, FormSG servers are unable to verify the data format.

However, the `decrypt` function exposed by this library [validates](https://github.com/opengovsg/formsg-python-sdk/blob/develop/formsg/util/validate.py) the decrypted content and will **return `None` if the
decrypted content does not fit the schema displayed below.**

| Key         | Type     | Description                                                                                              |
| ----------- | -------- | -------------------------------------------------------------------------------------------------------- |
| question    | str   | The question listed on the form                                                                          |
| answer      | str   | The submitter's answer to the question on form. Either this key or `answerArray` must exist.             |
| answerArray | List[str] | The submitter's answer to the question on form. Either this key or `answer` must exist.                  |
| fieldType   | str   | The type of field for the question.                                                                      |
| \_id        | str   | A unique identifier of the form field. WARNING: Changes when new fields are created/removed in the form. |

The full schema can be viewed in
[`validate.ts`](https://github.com/opengovsg/formsg-javascript-sdk/tree/master/src/util/validate.ts).

If the decrypted content is the correct shape, then:

1. the decrypted content (from `decryptParams.encryptedContent`) will be set as the value of the `responses` key.
2. if `decryptParams.verifiedContent` exists, then an attempt to
   decrypted the verified content will be called, and the result set as the
   value of `verified` key. There is no shape validation for the decrypted
   verified content. **If the verification fails, `None` is returned, even if
   `decryptParams.encryptedContent` was successfully decrypted.**
