Project update
This is an update on the implementation details of two new APIs announced in RFC-14 App Access Rule.
In our previous RFC, we outlined how the app access rule would block app access to specific Confluence spaces or Jira projects. At that time we planned to implement the block at the space and project level.
We have since decided that we can better optimize for customer flexibility in future iterations of this feature if we instead block apps from accessing data at one layer lower in granularity, meaning apps will not be able to access content on pages, issues, in comments, or in attachments. Customers will still set rules at the space and project level and will be notified about what data is covered or excluded via documentation and in the UI directly.
While this will leave apps with access to a few pieces of user-generated content like space names, the majority of user-generated content in these spaces and projects will still be protected, and we will be able to enable more granular controls for customers in the future to better align with other rules under data security policies.
Please respect our community guidelines : keep it welcoming and safe by commenting on the ideas not the people; keep it tidy by keeping on topic; empower the community by keeping comments constructive. Thanks!
- Publish: 18 Sep 2023
- Resolve: 02 Oct 2023
The new APIs
An apps access to data within a workspace may be affected whenever an Administrator adds or updates an app access rule within a data security policy. Should an app lose or gain access to objects within a workspace, an event will be published allowing the app to react to the change accordingly.
Apps are provided an API to query by container, if this is known, whether objects in that container are still accessible. There is also an API to query by object whether the object is still accessible. Note that ‘access to an object’ also implies access to the object’s comments, attachments, etc.
Apps are also provided with an API to query rules that are active in a given workspace, so they can display a warning to end-users.
(See the appendix for more information about workspaces, containers and objects.)
New event/webhook
New product trigger events for Forge apps and webhooks for Atlassian Connect apps to be notified when a policy change causes a space or project to become inaccessible or accessible (e.g.
SPACE_RESTRICTED
andSPACE_UNRESTRICTED
).
After evaluating technical implementation options, we propose a simplified application flow and event schema.
Application flow
App components are in blue, new Atlassian components are in green.
An app that needs to know when it has been blocked/unblocked from accessing objects will subscribe to this event. As a policy may specify containers from multiple workspaces for protection, one event will be published for each workspace covered by the policy. The app should retrieve a policy result (i.e. is the object blocked or not blocked) for each object it knows about within the workspace (specified by an event), and for each policy result respond appropriately, such as deleting data for blocked objects, or including an object in a list once it is unblocked.
(See the appendix for more information about workspaces, containers and objects.)
Event schema
The proposed event schema is defined using the AsyncAPI specification. The nicest way to read this schema is to go to AsyncAPI Studio and replace the sample schema with (copy/paste) the following schema:
asyncapi: '2.6.0'
tags:
- name: ecosystem
id: 'urn:com.atlassian.ecosystem.app.webhooks'
info:
title: Ecosystem App Webhooks Service
version: 0.1.0
description: Ecosystem service which publishes events to App webhooks.
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0
contact:
url: https://community.developer.atlassian.com/t/rfc-25
channels:
atlassian/webhooks:
subscribe:
bindings:
http:
type: request
method: POST
summary: Receive notifications of changes to App policy constraints.
message:
oneOf:
- $ref: '#/components/messages/appAccessToWorkspaceUpdatedEventBinary'
- $ref: '#/components/messages/appAccessToWorkspaceUpdatedEventStructured'
components:
messages:
appAccessToWorkspaceUpdatedEventBinary:
title: Event - App Access to Workspace Changed (binary mode)
description: |-
Published when an App's access to a Workspace has changed in some way.
It could be that the App has been added or removed from a Policy, or the Workspaces covered by a Policy associated with that App have changed.
traits:
- $ref: '#/components/messageTraits/eventHeadersBinary'
payload:
$ref: '#/components/schemas/appAccessToWorkspaceUpdatedData'
examples:
- name: AppAccessToWorkspaceUpdated binary mode
headers:
ce_specversion: "1.0"
ce_type: "avi:ecosystem.app_policy:updated:app_access_to_workspace.v1"
ce_source: "atlassian.com/ecosystem"
ce_id: "7a6796c0-746d-4504-92cd-819eca234306"
ce_time: "{{now(yyyy-MM-dd'T'HH:mm:SS'Z')}}"
content-type: application/json; charset=utf-8
payload:
workspaceAri: "ari:cloud:confluence:46e2390a-101e-4b83-b311-362a16083618:workspace/b5595212-55bb-49e4-8d7f-86d46f4500c1"
appAccessToWorkspaceUpdatedEventStructured:
title: Event - App Access to Workspace Changed (structured mode)
description: |-
Published when an App's access to a Workspace has changed in some way.
It could be that the App has been added or removed from a Policy, or the Workspaces covered by a Policy associated with that App have changed.
headers:
content-type:
type: string
enum:
- 'application/cloudevents+json; charset=UTF-8'
payload:
allOf:
- $ref: '#/components/schemas/eventHeadersStructured'
properties:
data:
$ref: '#/components/schemas/appAccessToWorkspaceUpdatedData'
examples:
- name: AppAccessToWorkspaceUpdated structured mode
headers:
content-type: application/cloudevents+json; charset=utf-8
payload:
specversion: "1.0"
type: "avi:ecosystem.app_policy:updated:app_access_to_workspace.v1"
source: "atlassian.com/ecosystem"
id: "7a6796c0-746d-4504-92cd-819eca234306"
time: "{{now(yyyy-MM-dd'T'HH:mm:SS'Z')}}"
data:
workspaceAri: "ari:cloud:confluence:46e2390a-101e-4b83-b311-362a16083618:workspace/b5595212-55bb-49e4-8d7f-86d46f4500c1"
schemas:
appAccessToWorkspaceUpdatedData:
type: object
properties:
workspaceAri:
$ref: "#/components/schemas/workspaceAri"
eventHeadersStructured:
summary: Message headers in structured content mode.
description: |-
Cloudevent spec mandatory headers, plus a few headers deemed mandatory when publishing cloudevents from Atlassian.
type: object
required:
- specversion
- id
- source
- type
- time
properties:
specversion:
type: string
description: The version of the CloudEvents specification which the event uses.
enum:
- "1.0"
id:
type: string
minLength: 1
description: Identifies the event.
source:
type: string
format: uri-reference
minLength: 1
description: Identifies the context in which an event happened.
type:
type: string
minLength: 1
description: Describes the type of event related to the originating occurrence.
examples:
- "avi:ecosystem.app_policy:updated:app_access_to_workspace.v1"
time:
type: string
#format: date-time
description: Timestamp of when the occurrence happened. Must adhere to RFC 3339.
subject:
type: string
description: Describes the subject of the event in the context of the event producer (identified by source).
dataschema:
type: string
description: |-
Identifies the schema for the payload.
This should dereference to an actual schema document.
examples:
- "https://developer.atlassian.com/platform/ecosystem/events-reference/app_policy/app_access_to_workspace/updated/v1"
workspaceAri:
type: string
description: Workspace ARI
examples:
# https://developer.atlassian.com/platform/atlassian-resource-identifier/resource-owners/registry/#ati%3Acloud%3Aconfluence%3Aworkspace
- "ari:cloud:confluence:46e2390a-101e-4b83-b311-362a16083618:workspace/b5595212-55bb-49e4-8d7f-86d46f4500c1"
# https://developer.atlassian.com/platform/atlassian-resource-identifier/resource-owners/registry/#ati%3Acloud%3Ajira%3Aworkspace
- "ari:cloud:jira:b2eef0a4-755d-4ac6-b117-0c3d9201ff8d:workspace/b2eef0a4-755d-4ac6-b117-0c3d9201ff8d"
messageTraits:
eventHeadersBinary:
headers:
type: object
required:
- ce-specversion
- ce-id
- ce-source
- ce-type
- ce-time
properties:
content-type:
type: string
enum:
- 'application/json; charset=UTF-8'
ce-specversion:
type: string
description: The version of the CloudEvents specification which the event uses.
enum:
- "1.0"
ce-id:
type: string
minLength: 1
description: Identifies the event.
ce-source:
type: string
format: uri-reference
minLength: 1
description: Identifies the context in which an event happened.
ce-type:
type: string
minLength: 1
description: Describes the type of event related to the originating occurrence.
examples:
- "avi:ecosystem.app_policy:updated:app_access_to_workspace.v1"
ce-time:
type: string
format: date-time
description: Timestamp of when the occurrence happened. Must adhere to RFC 3339.
ce-subject:
type: string
description: Describes the subject of the event in the context of the event producer (identified by source).
ce-dataschema:
type: string
description: |-
Identifies the schema for the payload.
This should dereference to an actual schema document.
examples:
- "https://developer.atlassian.com/platform/ecosystem/events-reference/app_policy/app_access_to_workspace/updated/v1"
Or, in short the event payload will be similar to this:
{
"specversion": "1.0",
"type": "avi:ecosystem.app_policy:updated:app_access_to_workspace.v1",
"source": "atlassian.com/ecosystem",
"id": "{{uuid()}}",
"time": "{{now(yyyy-MM-dd'T'HH:mm:SS'Z')}}",
"data": {
"workspaceAri": "ari:cloud:confluence:73dfba81-fec7-4a75-b59b-e495641293de:workspace/f0dede24-2545-4a2c-899f-8f349c710ba8"
}
}
(See the appendix for more information about ARIs.)
Event AVI
The exact value for the event AVI is still subject to confirmation but it may be similar to avi:ecosystem.app_policy:updated:app_access_to_workspace
, regardless of the App type.
Cloudevents details
The exact values for the type
and source
attributes are still subject to confirmation.
Events will be published via the HTTP protocol binding, per specification. The content mode will be either binary or structured, determined by the webhook publisher. Subscribers to these events can distinguish between these modes by inspecting the Content-Type
header value and parsing accordingly. Or (recommended) use a client SDK which provides a clean abstraction.
Changes to proposal
A summary of changes to what was previously specified:
1. Event semantics
The event will not include an attribute indicating whether access was restricted or unrestricted. The event will only indicate that ‘some change’ in restrictions occurred.
Motivation:
- DEVELOPER EXPERIENCE: As event processing may result in side effects (such as deleting data associated with a now-inaccessible container) and event delivery order is not guaranteed, to avoid race conditions apps are expected to call the API (detailed below) and fetch the current status, instead of relying on data from the event.
2. Event granularity
There will be one event published per app per workspace. Apps are expected to iterate over containers or objects saved in their datastore and check their current status using the new App Policy API.
Motivation:
- DEVELOPER EXPERIENCE: Org-wide policy changes can lead to a large number of events, and apps may face problems handling a surge of traffic if an event per app per container is published.
- TRUST EXPERIENCE: The less data about restricted spaces or projects is shared with apps - the better. By relying on apps to iterate over identifiers they already know about we do not disclose information about the total number of blocked containers or objects in a workspace.
3. Use of Cloudevents specification
As Ecosystem Partners become increasingly sophisticated in their handling of events published by Atlassian this is an opportune time to adopt an industry standard for describing event metadata. Cloudevents.io is a mature standard that will allow Partners to use standard SDKs to inspect events and create standardized eventing middleware, amongst other benefits.
New App Policy API
Previously specified.-,New%20REST%20APIs,-accessible%20to%20Forge):
New REST APIs accessible to Forge, Atlassian Connect, and OAuth 2.0 (3LO) apps to retrieve a set of opaque identifiers (e.g. numeric identifiers or UUIDs) for inaccessible spaces.
As previously proposed, a new API will be created to allow apps to query project\space restrictions.
Changes to proposal
Changes here fall into three categories:
1. Require identifiers to be passed as input
Instead of providing an API to enumerate restricted containers or objects, we will be relying on apps to pass batches of identifiers they already know about as input.
Motivation:
- DEVELOPER EXPERIENCE: In cases when the app knows about only a few identifiers, iterating over them will be faster compared to going over the full list of restricted projects or spaces. The downside is that apps will have to structure their databases to be able to iterate over data keyed by identifiers.
- TRUST EXPERIENCE: Similarly to “New event\webhook”, we want to avoid exposing the total number of spaces or projects the app does not have access to.
2. Provide API to determine if app restrictions are active
There will be a way for apps to determine if the execution context (site) has any active rules that may affect app access to data.
Motivation:
- DEVELOPER EXPERIENCE: When the app will be using APIs to fetch a list of objects, some of the items in the list may be hidden from the output due to active policies. Users of such apps may not be aware of policies and their effect on apps, since policies are configured by the Org admin. Using this API, apps can determine if there are any rules that can affect app operation, and if so – display a warning to the end user.
3. Separate API for Connect apps
We are shifting towards GraphQL API. This new set of APIs will be available in GraphQL form. Forge and OAuth 2.0 apps are already able to access those GraphQL APIs since they support auth mechanisms. While we expect Connect apps to gain the same level of access through upcoming compatibility features planned for the Forge and Connect platforms, in the meantime we have settled on providing a REST API proxy for them.
REST API for Connect apps
Request formats are slightly different for Jira and Confluence Connect apps
The proposed API schema is defined using the OpenAPI specification. The nicest way to read this schema is to go to Swagger Editor and replace the sample schema with (copy/paste) the following schema:
API schema:
openapi: 3.0.3
info:
title: App Policies API
version: "0.1.0"
description: Ecosystem service which publishes events to App webhooks.
contact:
name: Justin Thirkell
email: jthirkell@atlassian.com
url: https://dacrfc.atlassian.com/rfc/1234
servers:
- url: "https://your-site.atlassian.net/rest/atlassian-connect/latest/app-policies/data-classifications"
description: Jira
- url: "https://your-site.atlassian.net/wiki/rest/atlassian-connect/latest/app-policies/data-classifications"
description: Confluence
paths:
/containers:
get:
tags:
- containers
summary: Get the status of provided containers
operationId: getContainerDecisionStatus
parameters:
- name: projects
in: query
description: Project ids. Accepted only if app is installed into Jira.
explode: false
schema:
type: array
maxItems: 20
minItems: 1
items:
type: integer
minimum: 1
- name: spaces
in: query
description: Spaces ids. Accepted only if app is installed into Confluence.
explode: false
schema:
type: array
maxItems: 20
minItems: 1
items:
type: integer
minimum: 1
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
properties:
containers:
type: array
maxItems: 20
items:
$ref: '#/components/schemas/Result'
'400':
description: Invalid ID supplied
security:
- atlassian_connect_jwt:
- READ
/objects:
get:
tags:
- objects
summary: Get the status of provided objects
operationId: getObjectDecisionStatus
parameters:
- name: issues
in: query
description: Issues ids. Accepted only if app is installed into Jira.
explode: false
schema:
type: array
maxItems: 20
minItems: 1
items:
type: integer
minimum: 1
- name: pages
in: query
description: Pages ids. Accepted only if app is installed into Confluence.
explode: false
schema:
type: array
maxItems: 20
minItems: 1
items:
type: integer
minimum: 1
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
properties:
objects:
type: array
maxItems: 20
items:
$ref: '#/components/schemas/Result'
'400':
description: Invalid ID supplied
security:
- atlassian_connect_jwt:
- READ
/constraints:
get:
tags:
- constraints
summary: Check if the app is subject to policy restrictions in a given installation context
operationId: checkActiveConstraints
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
properties:
constraints:
$ref: '#/components/schemas/Constraints'
security:
- atlassian_connect_jwt:
- READ
components:
schemas:
Result:
required:
- id
- decision
type: object
properties:
id:
type: integer
format: int64
example: 14800
decision:
$ref: '#/components/schemas/Decision'
Decision:
required:
- status
type: object
properties:
status:
type: string
description: Policy decision status
enum:
- ALLOWED
- BLOCKED
Constraints:
type: object
properties:
hasConstraints:
type: boolean
securitySchemes:
atlassian_connect_jwt:
type: apiKey
name: authorisation
in: header
Alternatively, here is a short overview of new API methods:
Get the status of provided containers
Request (Jira Connect App):
GET /rest/atlassian-connect/latest/app-policies/data-classifications/containers ?projects=id1,id2
Request (Confluence Connect App):
GET /wiki/rest/atlassian-connect/latest/app-policies/data-classifications/containers ?spaces=id1,id2
Response:
{
"containers": [
{
"id": "id1",
"decision": {
"status": "ALLOWED"
}
},
{
"id": "id2",
"decision": {
"status": "BLOCKED"
}
}
]
}
Get the status of provided objects
Request (Jira Connect App):
GET /rest/atlassian-connect/latest/app-policies/data-classifications/objects ?issues=id1,id2
Request (Confluence Connect App):
GET /wiki/rest/atlassian-connect/latest/app-policies/data-classifications/objects ?pages=id1,id2
Response:
{
"objects": [
{
"id": "id1",
"decision": {
"status": "ALLOWED"
}
},
{
"id": "id2",
"decision": {
"status": "BLOCKED"
}
}
]
}
Check if the app is subject to policy restrictions in a given installation context
Request (Jira Connect App):
GET /rest/atlassian-connect/latest/app-policies/data-classifications/constraints
Request (Confluence Connect App):
GET /wiki/rest/atlassian-connect/latest/app-policies/data-classifications/constraints
Response:
{
"constraints": {
"active": true
}
}
GraphQL API for Forge and OAuth 2.0 apps
Forge and OAuth 2.0 apps will not be able to access the REST API described above, it is reserved for Connect apps only. GraphQL API will be provided instead. Additionally, we are going to provide a new SDK wrapper as part @forge/api
package, similar to Storage API, to simplify integration for Forge apps.
API contract
Type definitions
type Query {
ecosystem: EcosystemQuery
}
type EcosystemQuery {
appPolicies: EcosystemAppPolicies
}
type EcosystemAppPolicies {
dataClassifications(id: ID!): EcosystemDataClassificationsContext
}
type EcosystemDataClassificationsContext {
id: ID!
containers(ids: [ID!]!): [EcosystemDataClassificationPolicyResult]
objects(ids: [ID!]!): [EcosystemDataClassificationPolicyResult]
hasConstraints: Boolean
}
type EcosystemDataClassificationPolicyResult {
id: ID!
decision: EcosystemDataClassificationPolicyDecision!
}
type EcosystemDataClassificationPolicyDecision {
status: EcosystemDataClassificationPolicyDecisionStatus!
}
enum EcosystemDataClassificationPolicyDecisionStatus {
ALLOWED
BLOCKED
}
Get the status of provided containers
Query:
query getContainersDecisions($installationContext: ID!, $containerIds: [ID!]!) {
ecosystem {
appPolicies {
dataClassifications(id: $installationContext) {
containers(ids: $containerIds) {
id
decision {
status
}
}
}
}
}
}
Input:
This example demonstrates usage of confluence space ARI to uniquely identify scopes
{
"installationContext": "ari:cloud:confluence::site/{siteId}",
"containerIds": [
"ari:cloud:confluence:{siteId}:space/{spaceId}"
]
}
Output:
{
"data": {
"ecosystem": {
"appPolicies": {
"dataClassifications": {
"containers": [
{
"id": "ari:cloud:confluence:{siteId}:space/{spaceId}",
"decision": {
"status": "ALLOWED"
}
}
]
}
}
}
}
}
Get the status of provided objects
Query:
query getObjectsDecisions($installationContext: ID!, $objectIds: [ID!]!) {
ecosystem {
appPolicies {
dataClassifications(id: $installationContext) {
objects(ids: $objectIds) {
id
decision {
status
}
}
}
}
}
}
Input:
This example demonstrates usage of confluence space ARI to uniquely identify scapes
{
"installationContext": "ari:cloud:confluence::site/{siteId}",
"objectIds": [
"ari:cloud:confluence:{siteId}:page/{pageId}"
]
}
Output:
{
"data": {
"ecosystem": {
"appPolicies": {
"dataClassifications": {
"objects": [
{
"id": "ari:cloud:confluence:{siteId}:page/{pageId}",
"decision": {
"status": "ALLOWED"
}
}
]
}
}
}
}
}
Get if there are any constraints affecting the app in a given context
Query:
query getAppConstraints($installationContext: ID!) {
ecosystem {
appPolicies {
dataClassifications(id: $installationContext) {
hasConstraints
}
}
}
}
Input:
This example demonstrates usage of workspace ARI to uniquely identify app installation context.
{
"installationContext": "ari:cloud:confluence::site/{siteId}"
}
Output:
{
"data": {
"ecosystem": {
"appPolicies": {
"dataClassifications": {
"hasConstraints": true
}
}
}
}
}
Scopes
For Connect apps, the App must have the relevant Connect READ
scope. We do not expect this to be a change for any affected Apps.
For Forge and OAuth 2.0 apps, read:confluence-space.summary
or read:project:jira
scopes will be required to access container-level information (depending on the type of container). No additional scopes will be required to check if the app is affected by policy constraints in a workspace.
Appendix
ARIs
This RFC mentions ARIs and these may be unfamiliar to readers. An ARI (Atlassian resource identifier) is a globally unique identifier. ARIs can identify any entity, for example:
- Jira issues
- Jira projects
- Confluence pages
- Trello boards
- Atlassian users, (global entities)
Workspace ARI examples:
ari:cloud:jira:ad95fada:workspace/114dfb50
ari:cloud:confluence:1d52f2ee:workspace/cfdef2de
ARIs are particularly important when providing APIs that span multiple products, such as in this RFC which specifies an API that will be used for both Confluence and Jira.
An ARI is an opaque identifier, meaning that although it is human-readable, it should only ever be handled atomically - i.e. not parsed into its constituent parts. The structure of ARIs may change over time and so parsing of ARIs is strongly discouraged.
Containers
Both Confluence and Jira define a model that involves grouping objects into containers. For Jira a container of issues is called a project, for Confluence a container of pages is a space.
When defining APIs that span multiple products such as in this RFC, it is useful to define a term that may be used for any and all products. In this case, it is a container, and so the APIs specify the use of containerAri
in various places.
A containerAri
is an abstraction that in practice maps to concrete ARIs such as a spaceAri
or a projectAri
. So whenever you see container in API definitions you can think of this as a space or a project, whatever makes sense in your particular context.
Objects
The corollary of using the term container as an abstraction for projects and spaces is the term objects as an abstraction for Jira issues and Confluence pages.
So whenever you see object in API definitions you can think of this as an issue or a page, whatever makes sense in your particular context.
Asks
- Is this new API sufficient to allow graceful handling of access rules restrictions for your app?
- What is your estimated timeframe to implement this flow for your app?
- Your thoughts on the usage of standards - GraphQL and Cloudevents?