Skip to content

Commit be589f2

Browse files
author
James Fuqian
authored
[BB2-1105] Generate New App Creds on each Creds Request fetch/download (#1012)
* changed fetch/download logic so that related application's client_id and client_secret re-generated. * fix CI alert. * generate only client_secret at creds request record creation time, keep client_id unchanged - per PR review. * fix edge case: deliver creds for app owned by 'root'
1 parent c26e8ba commit be589f2

File tree

3 files changed

+118
-74
lines changed

3 files changed

+118
-74
lines changed

apps/creds/admin.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.contrib import admin
22
from django.contrib.auth.models import User
3+
from oauth2_provider.generators import generate_client_secret
34
from oauth2_provider.models import get_application_model
45

56
from apps.accounts.models import UserProfile
@@ -18,14 +19,30 @@ class Meta:
1819

1920
@admin.register(MyCredentialingRequest)
2021
class MyCredentialingRequestAdmin(admin.ModelAdmin):
21-
readonly_fields = ('id', 'get_user', 'get_organization', 'updated_at', 'last_visit', 'visits_count', 'get_creds_req_url',)
22-
list_display = ("application", "id",
23-
"get_user", "get_organization", "get_creds_req_url",
24-
"created_at", "updated_at",
25-
"last_visit", "visits_count")
26-
list_filter = ('application__user__username',)
22+
readonly_fields = (
23+
"id",
24+
"get_user",
25+
"get_organization",
26+
"updated_at",
27+
"last_visit",
28+
"visits_count",
29+
"get_creds_req_url",
30+
)
31+
list_display = (
32+
"application",
33+
"id",
34+
"get_user",
35+
"get_organization",
36+
"get_creds_req_url",
37+
"created_at",
38+
"updated_at",
39+
"last_visit",
40+
"visits_count",
41+
)
2742

28-
search_fields = ('application__name', 'application__user__username', '=id')
43+
list_filter = ("application__user__username",)
44+
45+
search_fields = ("application__name", "application__user__username", "=id")
2946

3047
raw_id_fields = ("application",)
3148

@@ -59,3 +76,9 @@ def get_creds_req_url(self, obj):
5976

6077
get_creds_req_url.admin_order_field = "get_creds_req_url"
6178
get_creds_req_url.short_description = "URL for credentials request"
79+
80+
def save_model(self, request, obj, form, change):
81+
app = Application.objects.get(id=obj.application.id)
82+
app.client_secret = generate_client_secret()
83+
app.save()
84+
super().save_model(request, obj, form, change)

apps/creds/utils.py

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import string
2+
13
from django.conf import settings
24
from django.contrib.auth.models import User
35
from oauth2_provider.models import get_application_model
6+
from rest_framework import exceptions, status
47

58
from apps.accounts.models import UserProfile
6-
79
from .models import CredentialingReqest
810

911

@@ -14,39 +16,58 @@ def get_url(creds_request_id):
1416
return "{}/creds/{}".format(settings.HOSTNAME_URL, creds_request_id)
1517

1618

17-
def get_creds_by_id(creds_request_id: str):
18-
return get_creds_by_obj(CredentialingReqest.objects.get(id=creds_request_id))
19+
def get_app_creds(creds_request_id: string):
20+
creds_req = CredentialingReqest.objects.get(id=creds_request_id)
21+
22+
creds_dict = get_app_usr_info(creds_req)
23+
24+
if creds_req.updated_at is not None:
25+
raise exceptions.PermissionDenied(
26+
"Credentials already fetched (download), doing it again not allowed.",
27+
code=status.HTTP_403_FORBIDDEN,
28+
)
29+
30+
app = Application.objects.get(pk=creds_req.application_id)
1931

32+
creds_dict.update(
33+
{
34+
"client_id": app.client_id,
35+
"client_secret": app.client_secret,
36+
}
37+
)
2038

21-
def get_creds_by_obj(creds_req: CredentialingReqest):
22-
creds_dict = {
39+
return creds_dict
40+
41+
42+
def get_app_usr_info(creds_req: CredentialingReqest):
43+
44+
app_usr_info = {
2345
"user_name": None,
2446
"org_name": None,
2547
"app_id": None,
2648
"app_name": None,
27-
"client_id": None,
28-
"client_secret": None,
49+
"creds_req_id": creds_req.id,
2950
}
30-
if creds_req:
3151

32-
app = Application.objects.get(pk=creds_req.application_id)
52+
app = Application.objects.get(pk=creds_req.application_id)
3353

34-
if app:
35-
creds_dict.update(
36-
{
37-
"app_id": app.id,
38-
"app_name": app.name,
39-
"client_id": app.client_id,
40-
"client_secret": app.client_secret,
41-
}
42-
)
54+
if app:
55+
app_usr_info.update(
56+
{
57+
"app_id": app.id,
58+
"app_name": app.name,
59+
}
60+
)
4361

44-
user = User.objects.get(pk=app.user_id)
62+
user = User.objects.get(pk=app.user_id)
4563

46-
if user:
47-
creds_dict.update({"user_name": user.username})
64+
if user:
65+
app_usr_info.update({"user_name": user.username})
66+
try:
4867
usrprofile = UserProfile.objects.get(user=user)
4968
if usrprofile:
50-
creds_dict.update({"org_name": usrprofile.organization_name})
69+
app_usr_info.update({"org_name": usrprofile.organization_name})
70+
except UserProfile.DoesNotExist:
71+
pass
5172

52-
return creds_dict
73+
return app_usr_info

apps/creds/views.py

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from rest_framework import exceptions, status
99
from rest_framework.views import APIView
1010

11-
from apps.creds.utils import get_creds_by_obj
11+
from apps.creds.utils import get_app_usr_info, get_app_creds
1212
from .models import CredentialingReqest
1313

1414
Application = get_application_model()
@@ -20,69 +20,62 @@ def get(self, request, *args, **kwargs):
2020

2121
creds_req_id = kwargs.get("prod_cred_req_id")
2222
creds_req = self._get_creds_req(creds_req_id)
23+
action = request.query_params.get("action", "")
2324

24-
# check if expired
25-
if self._is_expired(creds_req):
26-
raise exceptions.PermissionDenied(
27-
"Generated credentialing request expired.",
28-
code=status.HTTP_403_FORBIDDEN,
29-
)
30-
31-
creds_dict = get_creds_by_obj(creds_req)
32-
# fetch creds request and update visits count and relevant timestamps
33-
creds_req.visits_count = creds_req.visits_count + 1
34-
creds_req.last_visit = datetime.datetime.now(datetime.timezone.utc)
35-
36-
ctx = creds_dict
37-
ctx.update({"creds_req_id": creds_req_id})
25+
self._validate_expiration(creds_req)
3826

3927
log_dict = {
4028
"type": "credentials request",
4129
"id": creds_req_id,
42-
"app_id": ctx.get("app_id", ""),
43-
"app_name": ctx.get("app_name", ""),
4430
}
4531

46-
action = request.query_params.get("action", "")
32+
response = None
33+
creds_dict = {}
34+
updated = False
4735

4836
if action == "fetch" or action == "download":
49-
if creds_req.updated_at is None:
50-
creds_req.updated_at = datetime.datetime.now(datetime.timezone.utc)
51-
ctx.update(fetch=action)
52-
log_dict.update(action=action)
53-
else:
54-
# already fetched, fetch again forbidden
55-
raise exceptions.PermissionDenied(
56-
"Credentials already fetched (download), doing it again not allowed.",
57-
code=status.HTTP_403_FORBIDDEN,
58-
)
59-
else:
60-
# do not give out creds yet if not a fetch request
61-
if "client_id" in ctx:
62-
ctx.pop("client_id")
63-
if "client_secret" in ctx:
64-
ctx.pop("client_secret")
65-
# update visits and fetch status etc.
66-
creds_req.save()
67-
68-
logger.info(log_dict)
37+
# generate new client_id, client_secret
38+
creds_dict = get_app_creds(creds_req_id)
39+
updated = True
40+
log_dict.update(
41+
{
42+
"action": action,
43+
"user_name": creds_dict["user_name"],
44+
"org_name": creds_dict["org_name"],
45+
"app_id": creds_dict["app_id"],
46+
"app_name": creds_dict["app_name"],
47+
}
48+
)
6949

7050
if action == "download":
7151
response = JsonResponse(creds_dict)
7252
response["Content-Disposition"] = 'attachment; filename="{}.json"'.format(
7353
creds_req_id
7454
)
75-
return response
7655
else:
77-
return render(request, "get_creds.html", ctx)
56+
# response creds request page and update visits count and relevant timestamps
57+
app_usr_info = get_app_usr_info(creds_req)
58+
log_dict.update(app_usr_info)
59+
ctx = {"fetch": action}
60+
ctx.update(creds_dict)
61+
ctx.update(app_usr_info)
62+
response = render(request, "get_creds.html", ctx)
63+
64+
self._update_creds_req_stats(creds_req, updated)
65+
66+
logger.info(log_dict)
7867

79-
def _is_expired(self, creds_req):
68+
return response
69+
70+
def _validate_expiration(self, creds_req):
8071
t_elapsed_since_created = (
8172
datetime.datetime.now(datetime.timezone.utc) - creds_req.created_at
8273
)
83-
return (
84-
t_elapsed_since_created.seconds > settings.CREDENTIALS_REQUEST_URL_TTL * 60
85-
)
74+
if t_elapsed_since_created.seconds > settings.CREDENTIALS_REQUEST_URL_TTL * 60:
75+
raise exceptions.PermissionDenied(
76+
"Generated credentialing request expired.",
77+
code=status.HTTP_403_FORBIDDEN,
78+
)
8679

8780
def _get_creds_req(self, id):
8881

@@ -101,3 +94,10 @@ def _get_creds_req(self, id):
10194
raise exceptions.NotFound("Credentialing request not found.")
10295

10396
return creds_req
97+
98+
def _update_creds_req_stats(self, creds_req: CredentialingReqest, updated: False):
99+
creds_req.visits_count = creds_req.visits_count + 1
100+
creds_req.last_visit = datetime.datetime.now(datetime.timezone.utc)
101+
if updated:
102+
creds_req.updated_at = datetime.datetime.now(datetime.timezone.utc)
103+
creds_req.save()

0 commit comments

Comments
 (0)