JIRA-Atlassian-Connect-App-Django
Django
For Atlassian connect app, we would better use Django instead of Django rest framework.
Our first step is to build a local Django app server as a Atlassian connect app and install it.
Django app server as a Atlassian connect app
So please create a Django app (it was named as jira_app
for this example) first.
base_settings.py
```python ROOT_DIR = environ.Path(file) - 3
TEMPLATES = [ { ‘DIRS’: [os.path.join(ROOT_DIR, ‘jira_app’, ‘templates’)], } ]
ATLASSIAN_CONNECT_BASE_URL = env(‘ATLASSIAN_CONNECT_BASE_URL’)
* `mysite/urls.py`
```python
urlpatterns = [
path('jira/', include('jira_app.urls')),
]
jira_app/views.py
```python from django.conf import settings from django.http import JsonResponse from django.shortcuts import render from django.views.decorators.clickjacking import xframe_options_exempt
def atlassian_connect(request): base_url = settings.ATLASSIAN_CONNECT_BASE_URL name = f”my jira test {None if settings.ENVIRONMENT == ‘prod’ else ‘ ‘ + settings.ENVIRONMENT}”
return JsonResponse({
'baseUrl': base_url,
'key': f'my jira test-app-{settings.ENVIRONMENT}',
'authentication': {
'type': 'none'
},
'name': name,
'description': f'my jira test {settings.ENVIRONMENT}',
'links': {
'self': f'{base_url}/jira/atlassian-connect.json'
},
'scopes': [
'read',
'write'
],
'apiVersion': 1,
'modules': {
'generalPages': [
{
'url': '/jira/hello.html',
'key': 'hello',
'location': 'system.top.navigation.bar',
'name': {
'value': 'hello'
}
}
]
}
})
@xframe_options_exempt def hello(request): return render(request, ‘jira_app/hello.html’)
* `jira_app/urls.py`
```python
from django.urls import path
from . import views
urlpatterns = [
path('atlassian-connect.json', views.atlassian_connect, name='atlassian-connect'),
path('hello.html', views.hello, name='hello'),
]
jira_app/templates/jira_app/hello.html
```html <!DOCTYPE html>
Hello
## Install this app in Jira
Visit https://your-project.atlassian.net/secure/BrowseProjects.jspa
Then click `Apps -> Manage your apps`.
Then click `upload app` (should click `Settings -> Enable development mode` first).
Then input `From this URL` with the Ngrok url of your app like `https://544119900028.ngrok.io/jira/atlassian-connect.json`.
# JWT
## Get the install json and sharedSecret
```python
import json
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def installed(request):
print('======== request.body: ', request.body)
body = json.loads(request.body)
sharedSecret = body['sharedSecret']
print('======== sharedSecret: ', sharedSecret)
return HttpResponse("You posted to /jira/installed.")
This is what I get from request.body
when installing an app:
{
"key": "some-app",
"clientKey": "d388d673-a684-3f88-a3e1-7646d7a27058",
"publicKey": "MIGfMA0GCSq4SIb3DQEBAQUAA4GNADCBiQKBgQC4MfoLPnyiKXPeGR+PD+Qj1Puyzi/jRInJShzfu89mQddLpoIS8SjAmzETCfH86MtHUI4BmP7qgUEIXuHggSyd6Okpi1X4v/W+zYjCzrl5uWql88qbKksHhoovbjcZtVCE/4zUSJ7s8smyW7Xc80KhAFlhqbesbwOQhbvQdLk9uQIDAQAf",
"sharedSecret": "WkvYYm5PawgaOgt6hFacPOTFZrRlk/8pZWmZi8TgnW9irP4HRVuACocB6b7+rlKTemgyJlZP3sdCd5RTlSDrdg",
"serverVersion": "100150",
"pluginsVersion": "1001.0.0.SNAPSHOT",
"baseUrl": "https://some-domain.atlassian.net",
"productType": "jira",
"description": "Atlassian JIRA at https://team-1605254797493.atlassian.net ",
"eventType": "installed"
}
Other
- I guess an connect app can be installed in many clients (different from the
subdomain
name).
For example Like:
https://some-subdomain.atlassian.net/secure/BrowseProjects.jspa?selectedProjectType=software
https://other-people-subdomain.atlassian.net/secure/BrowseProjects.jspa?selectedProjectType=software
The install callback
will be invoked when a some-subdomain.atlassian.net
admin installs your app.
https://developer.atlassian.com/cloud/jira/platform/security-for-connect-apps/#signed-installation-callback-requests
So the sharedSecret
of all the users under the same some-subdomain.atlassian.net
will be always the same.
Use https://github.com/jpadilla/pyjwt to decode.
- Once user uninstalled an app and reinstall again, in the
install
callback, theAuthorization
will be in theheaders
and thesharedSecret
will still be the old one to ensure that our Python code could decode it.
But by body = json.loads(request.body); sharedSecret = body['sharedSecret']
, you can get a new sharedSecret
.
And the old sharedSecret
which you have just used will become stale in the future.
So you have to update the sharedSecret
for this client.
-
According to https://blog.developer.atlassian.com/understanding-jwt/ , the
clientKey
distinguished the client (e.g:your-subdomain.atlassian.net
has clientKeyd388d675-a684-3f88-a3e1-7646d8227059
his-subdomain.atlassian.net
has clientKey932fb277-c58c-3b4b-aae4-03634313c7bf
) -
At this moment, I think you may want to save the
clientKey
and thesharedSecret
into database.
Then, when a request arrives, you can decode it with sharedSecret
. But which sharedSecret
should you use?
The tip is that in every request, there is a parameter xdm_e': 'https://thegreatworld.atlassian.net'
which is the same as the baseUrl
in installation request json
we can use to determine which clientKey
is because we will save records in databases like:
your-subdomain.atlassian.net d388d675-a684-3f88-a3e1-7646d8227059 sharedSecret1345
his-subdomain.atlassian.net 932fb277-c58c-3b4b-aae4-03634313c7bf sharedSecret2824
...
https://pypi.org/project/atlassian-jwt/