Skip to content

Commit 6254bfe

Browse files
committed
Use refresh_token to refresh access_token
Requires client_id and client_secret. Signed-off-by: Justin Cinkelj <[email protected]>
1 parent f6a8245 commit 6254bfe

File tree

8 files changed

+120
-8
lines changed

8 files changed

+120
-8
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ python manage.py createsuperuser
5555
We need to create OAuth2 application and access token for integration with AAP.
5656
Follow [setup/README.md](setup/README.md#sso-authentication), section "SSO authentication".
5757

58-
File `clusters.yaml` needs to contain the access token.
58+
File `clusters.yaml` needs to contain the access token, refresh token, client id and client secret.
5959

6060
```bash
6161
cp -i clusters.example.yaml clusters.yaml

clusters.example.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ clusters:
44
address: 10.44.17.179
55
port: 8443
66
access_token: sampleToken
7+
refresh_token: sampleRefreshToken
8+
client_id: sampleClientId
9+
client_secret: sampleClientSecret
710
verify_ssl: false
811
sync_schedules:
912
- name: Every 5 minutes sync
@@ -14,6 +17,9 @@ clusters:
1417
address: 10.48.17.178
1518
port: 8443
1619
access_token: sampleToken1
20+
refresh_token: sampleRefreshToken1
21+
client_id: sampleClientId1
22+
client_secret: sampleClientSecret1
1723
verify_ssl: false
1824
sync_schedules:
1925
- name: Every 3 hours sync

setup/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ Next create a token at https://AAP_CONTROLLER_FQDN:8443/#/users/<id>/tokens:
9393
- OAuth application: automation-dashboard-sso
9494
- Scope: read
9595

96-
Store access token value.
97-
The access token is used in `clusters.yaml`.
96+
Store access token and refresh token values.
97+
They are used in `clusters.yaml`.
9898

9999
#### Run installer
100100

src/backend/apps/clusters/connector.py

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.db import transaction
99
import urllib3
1010

11-
from backend.apps.clusters.encryption import decrypt_value
11+
from backend.apps.clusters.encryption import decrypt_value, encrypt_value
1212
from backend.apps.clusters.models import (
1313
ClusterSyncData,
1414
ClusterSyncStatus,
@@ -84,8 +84,47 @@ def headers(self):
8484
'Accept': 'application/json',
8585
}
8686

87-
def execute_get_one(self, url, timeout=None):
88-
logger.info(f'Executing GET request to {url}')
87+
def _reauth(self, timeout=None):
88+
url = f'{self.cluster.base_url}/api/o/token/' # TODO AAP version
89+
refresh_token = decrypt_value(self.cluster.refresh_token)
90+
client_id = self.cluster.client_id
91+
client_secret = decrypt_value(self.cluster.client_secret)
92+
93+
data = {
94+
'grant_type': 'refresh_token',
95+
'refresh_token': refresh_token,
96+
}
97+
headers = {
98+
'Content-Type': 'application/x-www-form-urlencoded',
99+
'Accept': 'application/json',
100+
}
101+
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
102+
try:
103+
response = requests.post(
104+
url=url,
105+
data=data,
106+
auth=auth,
107+
verify=self.cluster.verify_ssl,
108+
timeout=timeout if timeout is not None else self.timeout,
109+
headers=headers)
110+
except requests.exceptions.RequestException as e:
111+
logger.error(f'Token refresh POST request failed with exception {e}')
112+
return False
113+
if not response.ok:
114+
logger.error(f'Token refresh POST request failed with status {response.status_code} text={response.json()}')
115+
return False
116+
logger.info(f'Token refresh POST request succedded with status {response.status_code}')
117+
resp = response.json()
118+
119+
self.cluster.access_token = encrypt_value(resp['access_token'])
120+
self.cluster.refresh_token = encrypt_value(resp["refresh_token"])
121+
self.access_token = resp['access_token']
122+
self.cluster.save()
123+
124+
return True
125+
126+
def _get_with_reauth(self, url, timeout=None):
127+
# try 1st time
89128
try:
90129
response = requests.get(
91130
url=url,
@@ -95,8 +134,32 @@ def execute_get_one(self, url, timeout=None):
95134
except requests.exceptions.RequestException as e:
96135
logger.error(f'GET request failed with exception {e}')
97136
return None
98-
if not response.ok:
99-
logger.error(f'GET request failed with status {response.status_code}')
137+
if response.ok:
138+
return response
139+
140+
# Try to re-auth only after 401 error
141+
logger.error(f'GET request failed with status {response.status_code}')
142+
if response.status_code != 401:
143+
return response
144+
self._reauth()
145+
146+
# try 2nd time
147+
try:
148+
response = requests.get(
149+
url=url,
150+
verify=self.cluster.verify_ssl,
151+
timeout=timeout if timeout is not None else self.timeout,
152+
headers=self.headers)
153+
except requests.exceptions.RequestException as e:
154+
logger.error( f'GET request failed with exception {e}')
155+
return None
156+
logger.error(f'GET after reauth response.status_code={response.status_code}')
157+
return response
158+
159+
def execute_get_one(self, url, timeout=None):
160+
logger.info(f'Executing GET request to {url}')
161+
response = self._get_with_reauth(url, timeout=timeout)
162+
if response is None or not response.ok:
100163
return None
101164
product_name = response.headers.get("X-Api-Product-Name", None)
102165
if product_name is None or product_name == "AWX":
@@ -228,13 +291,15 @@ def check_aap_version(self):
228291
if self.cluster.aap_version != ClusterVersionChoices.AAP25:
229292
self.cluster.aap_version = ClusterVersionChoices.AAP25
230293
self.cluster.save()
294+
logger.info(f'Detected AAP version 2.5 at {self.cluster.base_url}')
231295
return True
232296

233297
is_aap24_instance = self.is_aap24_instance
234298
if is_aap24_instance:
235299
if self.cluster.aap_version != ClusterVersionChoices.AAP24:
236300
self.cluster.aap_version = ClusterVersionChoices.AAP24
237301
self.cluster.save()
302+
logger.info(f'Detected AAP version 2.4 at {self.cluster.base_url}')
238303
return True
239304
raise Exception(f'Not valid version for cluster {self.cluster.base_url}.')
240305

src/backend/apps/clusters/management/commands/setclusters.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ def handle(self, *args, **options):
9595
with transaction.atomic():
9696
for cluster in yaml_clusters:
9797
cluster["access_token"] = encrypt_value(cluster["access_token"])
98+
cluster["refresh_token"] = encrypt_value(cluster["refresh_token"])
99+
cluster["client_secret"] = encrypt_value(cluster["client_secret"])
98100
self.stdout.write(self.style.NOTICE('Adding cluster: address={}'.format(cluster.get("address"))))
99101
try:
100102
new_cluster = ClusterSettings(**cluster)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Generated by Django 4.2.17 on 2025-09-11 11:30
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("clusters", "0014_alter_jobtemplate_time_taken_manually_execute_minutes"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="cluster",
15+
name="client_id",
16+
field=models.CharField(default="", max_length=255),
17+
),
18+
migrations.AddField(
19+
model_name="cluster",
20+
name="client_secret",
21+
field=models.BinaryField(
22+
default=b"gAAAAABowrLecNtBPqVaiwVCI_HgFVnV7d_UDSeZvGHhUSjSJELYcD03DoyLz6NUy1RGVY5dAP-SsAbBvOkPFzNH4oVbkmurEw=="
23+
),
24+
),
25+
migrations.AddField(
26+
model_name="cluster",
27+
name="refresh_token",
28+
field=models.BinaryField(
29+
default=b"gAAAAABowrLek-XxeqjYkiXMqHMrnKvHD4KGPTrANcdlUXDnTTbFNrXve3YG87iduL59_nevlZrYVPXueTMGk3UvA6nGJtrQYQ=="
30+
),
31+
),
32+
]

src/backend/apps/clusters/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from django.db.models import QuerySet
1515

1616
from backend.apps.clusters.schemas import DateRangeSchema, RelatedLinks
17+
from backend.apps.clusters.encryption import encrypt_value
1718

1819
manual_time = settings.DEFAULT_TIME_TAKEN_TO_MANUALLY_EXECUTE_MINUTES
1920
automation_time = settings.DEFAULT_TIME_TAKEN_TO_CREATE_AUTOMATION_MINUTES
@@ -42,6 +43,9 @@ class Cluster(CreatUpdateModel):
4243
address = models.CharField(max_length=255)
4344
port = models.IntegerField()
4445
access_token = models.BinaryField()
46+
refresh_token = models.BinaryField(default=encrypt_value(''))
47+
client_id = models.CharField(max_length=255, default='')
48+
client_secret = models.BinaryField(default=encrypt_value(''))
4549
verify_ssl = models.BooleanField(default=True)
4650
aap_version = models.CharField(max_length=15, choices=ClusterVersionChoices.choices, default=ClusterVersionChoices.AAP24)
4751

src/backend/apps/clusters/schemas.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class ClusterSettings(FrozenModel):
2020
address: str
2121
port: int
2222
access_token: bytes
23+
refresh_token: bytes
24+
client_id: str
25+
client_secret: bytes
2326
verify_ssl: bool = True
2427
sync_schedules: List[SyncSchedule] | None = []
2528

0 commit comments

Comments
 (0)