API
Integrates implements a GraphQL API that allows our products and customer-build external integrations to query data and perform mutations exposed in its schema.
All queries are directed to a single endpoint, which is exposed at /api.
Structure
The API is built using a schema-first approach enabled by the Ariadne library, structured as follows:
Directoryapi
Directoryenums
- __init__.py Python mappings
- schema.graphql Enum types
Directoryexplorer
- __init__.py Explorer config
Directoryinputs
- schema.graphql Input types
Directoryinterfaces
- schema.graphql Interface types
Directorymutations
- mutation_name.py Python implementation
- schema.py Python bindings
- schema.graphql Mutation type
Directorypayloads
- payload_name.py Python implementation
- schema.graphql Mutation return types
Directoryresolvers
- __init__.py Python bindings
Directoryobject_name
- field_name.py Python implementation
- schema.py Python bindings
- schema.graphql Object type
Directoryquery
- schema.graphql Query type
Directoryscalars
- __init__.py Python bindings
- scalar_name.py Python implementation
- scalars.graphql Scalar types
Directorysubscriptions
- subscription_name.py Subscription implementation
- schema.py Python bindings
- schema.graphql Subscription type
Directoryunions
- __init__.py Python bindings
- union_name.py Python implementation
- schema.graphql Union types
Directoryvalidations
- validation_name.py Validation implementation
Explorer
The API provides a web-based GUI, that allows performing queries and exploring schema definitions in a graphic and interactive way. You can access it on:
- https://app.fluidattacks.com/api, which is production.
https://<branch>.app.fluidattacks.com/api
, which are the ephemeral environments (where<branch>
is the name of your Git branch).- https://localhost:8081/api, which is local (useful for development).
Types
Integrates GraphQL types can be found on Documentation Explorer section at the Fluid Attacks API Playground or you can go to the source code.
There are two approaches
to defining a GraphQL
schema:
- Code-first
- Schema-first
We use the latter,
which implies defining the structure using GraphQL SDL
(Schema definition language)
and binding it to python functions.
e.g:
Enums
Integrates GraphQL enums can be found on Documentation Explorer section at the Fluid Attacks API Playground or you can go to the source code.
To map the value to something else, you can specify it in the enums binding index, e.g:
Scalars
Integrates GraphQL scalars can be found on Documentation Explorer section at the Fluid Attacks API Playground or you can go to the source code.
GraphQL provides some primitive scalars, such as String, Int and Boolean, but in some cases, it is required to define custom ones that aren’t included by default due to not (yet) being part of the spec, like Datetime, JSON and Upload.
Further reading:
Resolvers
Integrates GraphQL resolvers can be found on GraphiQL Explorer section at the Fluid Attacks API Playground or you can go to the source code.
A resolver is a function that receives two arguments:
- Parent: The value returned by the parent resolver, usually a dictionary. If it’s a root resolver this argument will be None
- Info: An object whose attributes provide details about the execution AST and the HTTP request.
It will also receive keyword arguments if the GraphQL field defines any.
The function must return a value whose structure matches the type defined in the GraphQL schema
Mutations
Integrates GraphQL mutations can be found on GraphiQL Explorer section at the Fluid Attacks API Playground or you can go to the source code.
Mutations are a kind of GraphQL operation explicitly meant to change data.
Most mutations only return {'success': bool}
also known as SimplePayload
,
but they aren’t limited to that.
If you need your mutation to return other data,
just look for it or define a new type in
api/mutations/payloads/schema.graphql
and use it.
Subscriptions
Integrates GraphQL subscriptions can be found on GraphiQL Explorer section at the Fluid Attacks API Playground or you can go to the source code.
Subscriptions are long-lasting operations designed to provide real-time data updates through bidirectional WebSocket communication. They are commonly used to query AI models and suggest solutions for identified risks.
They can maintain an active connection to your GraphQL server. Typical resolvers are used to start a connection.
Errors
All exceptions raised, will be reported in the “errors” field of the response.
Raising exceptions can be useful to enforce business rules and report back to the client in cases the operation could not be completed successfully.
Further reading:
Authentication
The Integrates API enforces authentication by checking for the presence and validity of a JWT in the request cookies or headers.
For resolvers or mutations
that require authenticated users,
decorate the function with the
@require_login
from decorators
.
Authorization
The Integrates API enforces authorization implementing an ABAC model with a simple grouping for defining roles. You can find the model here.
Levels and roles
An user can have one role for each one of the three levels of authorization:
- User
- Organization
- Group
Each role is associated with a set of permissions.
Also, Service level exists and it checks the covered features according to group plan like Advanced or Essential.
Enforcer
An enforcer is an authorization function that checks if the user can perform an action on the context.
We define enforcers for each authorization level. Read the description for understanding how to use them.
Boundary
The general methods for listing and getting the user permissions (and the permissions that user can grant) are in boundary.
The whole application must use this methods for implementing controls.
Policy
The general methods for get user role, grant permissions or revoke them, are in policy.
Decorators
For resolvers or mutations
that require authorized users,
decorate the function
with the appropriate decorator
from decorators
- @enforce_user_level_auth_async
- @enforce_organization_level_auth_async
- @enforce_group_level_auth_async
Guides
Adding new fields or mutations
- Declare the field or mutation in the schema using SDL
- Write the resolver or mutation function
- Bind the resolver or mutation function to the schema
Editing and removing fields or mutations
When dealing with fields or mutations that are already in use by clients, it’s crucial to ensure backward compatibility to prevent breaking changes. To achieve this, we implement a deprecation policy, providing users with advance notice of any planned edits or removals.
This involves informing API users about which fields or mutations will be edited/deleted in the future, granting them adequate time to adapt to this changes.
We use field and mutation deprecation for this. Our current policy mandates removal 6 months after marking fields and mutations as deprecated.
Deprecating fields
To mark fields or mutations as deprecated,
use the
@deprecated
directive,
e.g:
The reason should follow something similar to:
If it was replaced or there is an alternative, it should include:
Dates follow the AAAA/MM/DD
convention.
Additionally, we offer the option to assume the risk of using deprecated fields or mutations by including a flag in the commit message. This allows developers to make informed decision when incorporating changes that may affect their implementations.
Removing fields or mutations
When deprecating fields or mutations for removal, these are the common steps to follow:
- Mark the field or mutation as deprecated.
- Wait six months so clients have a considerable window to stop using the field or mutation.
- Delete the field or mutation.
e.g:
Let’s remove the color
field from type Car
:
-
Mark the
color
field as deprecated: -
Wait until one day after given deprecation date and Remove the field:
Editing fields or mutations
When renaming fields, mutations or already-existing types within the API, these are the common steps to follow:
- Mark the field or mutation you want to rename as deprecated.
- Add a new field or mutation using the new name you want.
- Wait until one day after given deprecation date.
- Remove the field or mutation that was marked as deprecated.
e.g:
Let’s make the color
field from type Car
to return a Color
instead of a String
:
-
create a
newColor
field that returns theColor
type: -
Mark the
color
field as deprecated and setnewColor
as the alternative: -
Wait until one day after given deprecation date and remove the
color
field: -
Add a new
color
field that uses theColor
type: -
Mark the
newColor
field as deprecated and setcolor
as the alternative: -
Wait until one day after given deprecation date and remove the
newColor
field: