-
Notifications
You must be signed in to change notification settings - Fork 375
feat: personal access tokens (PATs) #1924
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis update introduces comprehensive support for Personal Access Tokens (PATs) across both backend and frontend components. On the backend, a new Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Frontend
participant Backend
participant KnoxTokenModel
participant Database
User->>Frontend: Requests to create PAT
Frontend->>Backend: POST /iam/auth-tokens/ (name, expiry)
Backend->>KnoxTokenModel: Create AuthToken (with expiry)
KnoxTokenModel-->>Backend: Returns AuthToken and token string
Backend->>Database: Create PersonalAccessToken linked to AuthToken
Database-->>Backend: Confirms creation
Backend->>Frontend: Returns PAT info and token string
Frontend->>User: Displays token (once), updates PAT list
User->>Frontend: Requests to delete PAT
Frontend->>Backend: DELETE /iam/auth-tokens/{digest}/
Backend->>Database: Verify and delete PersonalAccessToken and linked AuthToken
Database-->>Backend: Confirms deletion
Backend->>Frontend: Returns success
Frontend->>User: Updates PAT list
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🔭 Outside diff range comments (1)
backend/iam/migrations/0012_personalaccesstoken.py (1)
1-25
:⚠️ Potential issuePipeline failing – run
ruff format
(and lint-driven clean-ups) before mergingCI is red because Ruff’s formatter would rewrite this file.
Runningruff format backend/iam/migrations/0012_personalaccesstoken.pylocally (or adding it to
pre-commit
) will fix whitespace/blank-line issues and unblock the build.🧰 Tools
🪛 GitHub Actions: Backend Linters
[error] 1-1: Ruff formatting check failed. File would be reformatted. Run 'ruff format' to fix code style issues.
🧹 Nitpick comments (14)
backend/iam/migrations/0012_personalaccesstoken.py (1)
19-22
: Optionally add a uniqueness safeguard for token namesRight now multiple rows can share the same
name
for the same user because the uniqueness is only guaranteed by theauth_token
FK.
If you want to avoid name clashes per user, you could add a DB/ORM-level constraint:+ migrations.AddConstraint( + model_name="personalaccesstoken", + constraint=models.UniqueConstraint( + fields=["auth_token", "name"], name="unique_pat_name_per_user" + ), + ),That would prevent accidental duplicates without extra app logic.
frontend/src/routes/(app)/(internal)/my-profile/settings/+page.svelte (2)
17-18
: Consistent casing for the imported component
CreatePatModal
(camel-case Pat) is imported fromCreatePATModal.svelte
(upper-case PAT).
While Svelte is case-sensitive at the file-system level, the mixed spelling can be confusing for grep/refactoring.-import CreatePatModal from './pat/components/CreatePATModal.svelte'; +import CreatePATModal from './pat/components/CreatePATModal.svelte';…and update the two
ref
usages accordingly.
94-104
: Rename deletion payload key fromid
→digest
for clarityYou’re passing the token digest, not the DB id:
- _form: defaults({ id }, zod(z.object({ id: z.string() }))), - schema: zod(z.object({ id: z.string() })), + _form: defaults({ digest: id }, zod(z.object({ digest: z.string() }))), + schema: zod(z.object({ digest: z.string() })),Renaming keeps the semantic intent crystal-clear and prevents accidental overlap with other objects that do use numeric IDs.
backend/iam/views.py (1)
117-120
: Use the token model helper for the quota check
request.user.auth_token_set
assumes the default Knox model.
For swappable models keep it generic:-from request.user.auth_token_set.filter(expiry__gt=now) +from get_token_model().objects.filter(user=request.user, expiry__gt=now)This aligns with
create_token()
and avoids subtle bugs whenKNOX_TOKEN_MODEL
is overridden.frontend/src/routes/(app)/(internal)/my-profile/settings/+page.server.ts (4)
50-52
: Consider moving schema definition to a shared location.The deletion schema is defined inline in the load function and again in the deletePAT action. Consider moving this to the schemas.ts file alongside AuthTokenCreateSchema for better reusability.
// In $lib/utils/schemas.ts + export const AuthTokenDeleteSchema = z.object({ id: z.string() }); // Then in this file - const personalAccessTokenDeleteForm = await superValidate(zod(z.object({ id: z.string() }))); + const personalAccessTokenDeleteForm = await superValidate(zod(AuthTokenDeleteSchema));
173-173
: Remove debug logging statement.This appears to be a development trace that should be removed before production.
- console.debug('deletePAT 1');
174-197
: PAT deletion implementation is secure and follows existing patterns.The implementation properly handles form validation, API requests, and user feedback. Consider using the same pattern as the createPAT action for consistency in the return statement.
For consistency with the createPAT action, consider:
- return message(form, { status: response.status }); + console.debug('Deleted PAT', { status: response.status }); + setFlash({ type: 'success', message: m.successfullyDeletedPersonalAccessToken() }, event); + return message(form, { status: response.status });
177-177
: Use shared schema for deletePAT validation.For consistency and maintainability, consider extracting this schema definition to your schemas.ts file as suggested earlier.
- const form = await superValidate(formData, zod(z.object({ id: z.string() }))); + const form = await superValidate(formData, zod(AuthTokenDeleteSchema));frontend/src/routes/(app)/(internal)/my-profile/settings/pat/components/CreatePATModal.svelte (6)
1-5
: Improve typing for parent prop.The parent prop is typed as
any
, which could lead to potential issues. Consider creating a proper interface for the expected parent properties.- export let parent: any; + interface ParentProps { + onConfirm: () => void; + buttonNeutral: string; + regionFooter: string; + } + export let parent: ParentProps;
49-49
: Remove development console.log.This appears to be a development debugging statement that should be removed before production.
- $: console.log('toto', $page.form?.form?.message?.data?.token);
79-81
: Update test ID to match PAT functionality.The test ID refers to TOTP (Time-based One-Time Password) instead of PAT (Personal Access Token).
- data-testid="activate-totp-confirm-button" + data-testid="generate-pat-button"
84-97
: Token display with copy functionality is well implemented.Good use of a warning message to inform the user that the token will only be displayed once. The copy button integration is implemented correctly.
Consider adding visual feedback when copying succeeds:
<button type="button" class="btn px-2 py-1 {parent.buttonNeutral} rounded-l-none" - use:copy={{ text: $page?.form?.form?.message?.data?.token }} + use:copy={{ + text: $page?.form?.form?.message?.data?.token, + onCopied: () => { + // Add toast notification or brief visual feedback + } + }} ><i class="fa-solid fa-copy mr-2"></i>{m.copy()}</button >
90-90
: Extract token path to a derived store for cleaner code.The nested path to access the token is repeated and could be simplified.
+ $: token = $page?.form?.form?.message?.data?.token || ''; - <pre>{$page?.form?.form?.message?.data?.token}</pre> + <pre>{token}</pre> // And update the copy directive: - use:copy={{ text: $page?.form?.form?.message?.data?.token }} + use:copy={{ text: token }}
98-106
: Update test ID in confirmation button.Similar to the previous suggestion, update the test ID to match PAT functionality.
- data-testid="activate-totp-confirm-button" + data-testid="pat-done-button"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
backend/ciso_assistant/settings.py
(1 hunks)backend/iam/migrations/0012_personalaccesstoken.py
(1 hunks)backend/iam/models.py
(2 hunks)backend/iam/serializers.py
(2 hunks)backend/iam/urls.py
(2 hunks)backend/iam/views.py
(4 hunks)dispatcher/README.md
(1 hunks)frontend/messages/en.json
(1 hunks)frontend/src/lib/components/Modals/ConfirmModal.svelte
(2 hunks)frontend/src/lib/utils/schemas.ts
(1 hunks)frontend/src/routes/(app)/(internal)/my-profile/settings/+page.server.ts
(3 hunks)frontend/src/routes/(app)/(internal)/my-profile/settings/+page.svelte
(3 hunks)frontend/src/routes/(app)/(internal)/my-profile/settings/pat/components/CreatePATModal.svelte
(1 hunks)frontend/src/routes/(app)/(third-party)/evidences/[id=uuid]/+page.svelte
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
backend/iam/serializers.py (2)
backend/iam/models.py (3)
PersonalAccessToken
(867-888)User
(389-595)expiry
(880-881)backend/core/serializer_fields.py (1)
FieldsRelatedField
(24-86)
frontend/src/routes/(app)/(internal)/my-profile/settings/+page.server.ts (2)
frontend/src/lib/utils/constants.ts (1)
BASE_API_URL
(3-7)frontend/src/lib/utils/schemas.ts (1)
AuthTokenCreateSchema
(687-690)
backend/iam/urls.py (1)
backend/iam/views.py (2)
AuthTokenDetailViewSet
(139-155)AuthTokenListViewSet
(68-136)
🪛 GitHub Actions: Backend Linters
backend/iam/migrations/0012_personalaccesstoken.py
[error] 1-1: Ruff formatting check failed. File would be reformatted. Run 'ruff format' to fix code style issues.
🪛 Ruff (0.8.2)
backend/iam/serializers.py
2-2: django.utils.timezone
imported but unused
Remove unused import: django.utils.timezone
(F401)
backend/iam/views.py
14-14: knox.views.user_logged_in
imported but unused
Remove unused import: knox.views.user_logged_in
(F401)
15-15: rest_framework.viewsets
imported but unused
Remove unused import: rest_framework.viewsets
(F401)
29-29: .serializers.PersonalAccessTokenCreateSerializer
imported but unused
Remove unused import: .serializers.PersonalAccessTokenCreateSerializer
(F401)
⏰ Context from checks skipped due to timeout of 90000ms (7)
- GitHub Check: startup-functional-test (3.12)
- GitHub Check: startup-docker-compose-test
- GitHub Check: functional-tests (3.12, chromium)
- GitHub Check: enterprise-functional-tests (3.12, chromium)
- GitHub Check: enterprise-startup-functional-test (3.12)
- GitHub Check: enterprise-startup-docker-compose-test
- GitHub Check: build (3.12)
🔇 Additional comments (22)
backend/ciso_assistant/settings.py (2)
275-275
: Token limit configuration added for PATs.Setting
TOKEN_LIMIT_PER_USER
to 5 enforces the requirement mentioned in the PR objectives to limit personal access tokens to a maximum of five per user.
282-282
: Knox token model explicitly defined.The new
KNOX_TOKEN_MODEL
setting specifies the model to be used for token authentication, which is necessary for the new Personal Access Token functionality.dispatcher/README.md (1)
140-140
: Documentation updated to reflect PAT feature availability.The "soon to be released" phrase has been removed from the token-based authentication section heading, correctly reflecting that the Personal Access Tokens feature is now implemented.
frontend/src/routes/(app)/(third-party)/evidences/[id=uuid]/+page.svelte (1)
36-36
: Validation schema added to ConfirmModal.A schema property was added to the ConfirmModal component props, using the same Zod validation schema already used in
_form
. This enhances form validation consistency.frontend/src/lib/components/Modals/ConfirmModal.svelte (2)
22-22
: Schema prop added to ConfirmModal component.A new exportable property
schema
has been added to accept validation schemas from parent components.
53-59
: SuperForm updated to use the schema for validation.The SuperForm component now includes a
validators
attribute that uses the provided schema, enabling proper form validation.frontend/src/lib/utils/schemas.ts (1)
687-690
: LGTM: Clean and concise schema for PAT creation.The schema correctly validates the required fields for creating a personal access token. The
name
field requires a non-empty string, and theexpiry
field is an optional positive number (presumably days until expiration).frontend/messages/en.json (1)
1474-1477
: LGTM: Comprehensive localization strings for PAT management.The localization strings provide clear and informative messages for all PAT operations, including helpful warnings and instructions about token security and one-time display.
Also applies to: 1479-1485
backend/iam/urls.py (2)
5-7
: LGTM: Clean imports for new view classes.The imports are correctly added for the new AuthToken view classes.
35-40
: LGTM: Properly defined REST API endpoints for PAT management.The URL patterns follow RESTful conventions with appropriate naming and structure:
/auth-tokens/
for listing and creating tokens/auth-tokens/<str:pk>/
for operations on specific tokensThese endpoints align with the implementation in the view classes.
backend/iam/models.py (2)
39-39
: LGTM: Better import practice for settings.Using
django.conf.settings
instead of a direct import is a better practice for Django applications.
867-889
: LGTM: Well-structured PersonalAccessToken model.The PersonalAccessToken model is well-designed:
- Links to the Knox token model with a ForeignKey
- Provides convenient property accessors for token metadata
- Includes a clear string representation
- Aligns with the PR objective of implementing personal access tokens
The model correctly delegates to the underlying auth_token for properties like creation time, expiry, and digest, avoiding data duplication.
frontend/src/routes/(app)/(internal)/my-profile/settings/+page.svelte (1)
203-210
: Date parsing may break with custom backend format
new Date(pat.expiry)
works only ifexpiry
is ISO-8601 or RFC-2822.
The backend usesknox_settings.EXPIRY_DATETIME_FORMAT
, which can be customised (e.g."%d/%m/%Y %H:%M"
). JS’sDate
constructor would then returnInvalid Date
.Two lightweight fixes:
- {m.expiresOn({ date: new Date(pat.expiry).toLocaleDateString() })} + {m.expiresOn({ date: dayjs(pat.expiry, 'YYYY-MM-DD HH:mm:ss').format('LL') })}(or) ask the backend to send ISO timestamps (
DateTimeField(format=None)
), keeping the existing line intact.Choose whichever side is easier to change but make them consistent.
frontend/src/routes/(app)/(internal)/my-profile/settings/+page.server.ts (4)
1-11
: Imports look good with appropriate organization.The new imports for PAT functionality are properly organized, with schema imports for form validation and necessary utilities from the superforms library.
43-50
: PAT data fetching implementation is well structured.The implementation follows existing patterns in the codebase for API requests with proper error handling. Good job using standard fetch patterns consistent with the rest of the file.
53-62
: Return object properly extended with PAT data.The return object has been cleanly extended to include the personal access tokens and related forms.
145-171
: PAT creation implementation is thorough and follows best practices.The createPAT action properly validates form data, handles API responses, and provides appropriate user feedback. Good implementation.
frontend/src/routes/(app)/(internal)/my-profile/settings/pat/components/CreatePATModal.svelte (5)
6-22
: Imports and store setup follow best practices.Good job organizing imports by category and using the appropriate stores and components for the functionality.
23-38
: Form configuration is well implemented.The superForm setup with appropriate validation, data types, and event handling shows good attention to detail and follows best practices.
39-48
: Base styles and imports are organized well.The component organization maintains a clear separation between imports, configuration, and styling.
52-66
: Modal structure is well organized.The modal implementation with appropriate header and layout structure follows UI patterns effectively.
67-83
: Form implementation for token creation looks good.The form fields are well structured with appropriate labels and help text. The validation integration works correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
backend/iam/views.py (2)
15-16
: Remove unused imports to keep the codebase clean.-from knox.views import DateTimeField, LoginView as KnoxLoginView, user_logged_in -from rest_framework import permissions, serializers, status, views, viewsets +from knox.views import DateTimeField, LoginView as KnoxLoginView +from rest_framework import permissions, serializers, status, views🧰 Tools
🪛 Ruff (0.8.2)
15-15:
knox.views.user_logged_in
imported but unusedRemove unused import:
knox.views.user_logged_in
(F401)
16-16:
rest_framework.viewsets
imported but unusedRemove unused import:
rest_framework.viewsets
(F401)
121-134
:⚠️ Potential issueHarden input validation in token creation.
The current implementation doesn't properly validate the
name
andexpiry
inputs, which could lead to unexpected behavior or server errors.def post(self, request, format=None): token_limit_per_user = self.get_token_limit_per_user() name = request.data.get("name") - expiry = request.data.get("expiry", "30") + + # Validate name is not empty + if not name or not name.strip(): + return Response( + {"error": "Name is required and cannot be empty."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Validate expiry is a positive integer + try: + expiry_days = int(request.data.get("expiry", 30)) + if expiry_days <= 0: + raise ValueError("Expiry must be positive") + except (TypeError, ValueError): + return Response( + {"error": "Expiry must be a positive integer (days)."}, + status=status.HTTP_400_BAD_REQUEST, + ) + if token_limit_per_user is not None: now = timezone.now() token = request.user.auth_token_set.filter(expiry__gt=now) if token.count() >= token_limit_per_user: return Response( {"error": "Maximum amount of tokens allowed per user exceeded."}, status=status.HTTP_403_FORBIDDEN, ) - instance, token = self.create_token(timedelta(days=int(expiry))) + instance, token = self.create_token(timedelta(days=expiry_days)) pat = PersonalAccessToken.objects.create(auth_token=instance, name=name) return self.get_post_response(request, token, pat.name, pat.auth_token)
🧹 Nitpick comments (3)
backend/iam/views.py (3)
30-31
: Remove unused import.The
PersonalAccessTokenCreateSerializer
is imported but not used anywhere in this file.- PersonalAccessTokenCreateSerializer, PersonalAccessTokenReadSerializer,
🧰 Tools
🪛 Ruff (0.8.2)
30-30:
.serializers.PersonalAccessTokenCreateSerializer
imported but unusedRemove unused import:
.serializers.PersonalAccessTokenCreateSerializer
(F401)
76-91
: Consider implementing pagination for token listing.For users with many tokens, the
get
method should implement pagination to improve performance and reduce response size. Additionally, consider adding sorting options.class AuthTokenListViewSet(views.APIView): def get_queryset(self): - return PersonalAccessToken.objects.filter(auth_token__user=self.request.user) + queryset = PersonalAccessToken.objects.filter(auth_token__user=self.request.user) + # Optional: Add ordering + return queryset.order_by('-auth_token__created')You might also consider using Django REST framework's pagination classes for the GET endpoint if you expect users to have many tokens in the future.
124-131
: Consider caching token count to improve performance.The current implementation queries the database to count active tokens on every token creation request. For frequently used APIs, consider caching this value.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
backend/iam/views.py
(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/iam/views.py (2)
backend/iam/models.py (4)
PersonalAccessToken
(867-888)digest
(884-885)delete
(440-442)expiry
(880-881)backend/iam/serializers.py (3)
PersonalAccessTokenCreateSerializer
(134-154)PersonalAccessTokenReadSerializer
(122-131)create
(139-150)
🪛 Ruff (0.8.2)
backend/iam/views.py
15-15: knox.views.user_logged_in
imported but unused
Remove unused import: knox.views.user_logged_in
(F401)
16-16: rest_framework.viewsets
imported but unused
Remove unused import: rest_framework.viewsets
(F401)
30-30: .serializers.PersonalAccessTokenCreateSerializer
imported but unused
Remove unused import: .serializers.PersonalAccessTokenCreateSerializer
(F401)
⏰ Context from checks skipped due to timeout of 90000ms (9)
- GitHub Check: startup-functional-test (3.12)
- GitHub Check: startup-docker-compose-test
- GitHub Check: enterprise-startup-functional-test (3.12)
- GitHub Check: enterprise-startup-docker-compose-test
- GitHub Check: functional-tests (3.12, chromium)
- GitHub Check: enterprise-functional-tests (3.12, chromium)
- GitHub Check: build (3.12)
- GitHub Check: Analyze (python)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
backend/iam/views.py (1)
136-144
: LGTM! Good implementation of the GET endpoint.The implementation correctly filters tokens by the authenticated user and uses the appropriate serializer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
backend/iam/views.py (3)
62-68
: Improve token deletion error handling in LogoutView.The current implementation has several issues:
- It doesn't validate the presence of the
HTTP_AUTHORIZATION
header- It assumes the Authorization header format is "Bearer {token}"
- It catches all exceptions with a broad try-except block
try: logger.info("logout request", user=request.user) try: - access_token = request.META.get("HTTP_AUTHORIZATION").split(" ")[1] - digest = crypto.hash_token(access_token) - auth_token = AuthToken.objects.get(digest=digest) - auth_token.delete() + auth_header = request.META.get("HTTP_AUTHORIZATION") + if auth_header and " " in auth_header: + access_token = auth_header.split(" ")[1] + digest = crypto.hash_token(access_token) + auth_token = AuthToken.objects.get(digest=digest) + auth_token.delete() + else: + logger.warning("No valid authorization header found during logout", user=request.user) except Exception as e: - logger.error("Error deleting token", user=request.user, error=e) + logger.error("Error deleting token during logout", user=request.user, error=str(e))
142-157
: Improve error handling in token deletion.The current implementation uses a broad try-except block that catches all exceptions, and the error message might not be accurate for all cases.
def delete(self, request, *args, **kwargs): try: token = AuthToken.objects.get(digest=kwargs["pk"]) if token.user != request.user: return Response( {"error": "You do not have permission to delete this token."}, status=status.HTTP_403_FORBIDDEN, ) token.delete() return Response(status=status.HTTP_204_NO_CONTENT) - except Exception as e: - logger.error("Error deleting token", error=e) + except AuthToken.DoesNotExist: + logger.info("Attempt to delete non-existent token", digest=kwargs["pk"], user=request.user.id) return Response( {"error": "Token not found or already deleted."}, status=status.HTTP_404_NOT_FOUND, ) + except Exception as e: + logger.error("Error deleting token", error=str(e), digest=kwargs["pk"], user=request.user.id) + return Response( + {"error": "Failed to delete token due to an internal error."}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + )
114-128
: 🛠️ Refactor suggestionImprove input validation in
AuthTokenListViewSet.post
.The method lacks validation for required fields and proper error handling for invalid input types.
- expiry = request.data.get("expiry", "30") + name = request.data.get("name") + if not name or not name.strip(): + return Response( + {"error": "Name is required for the token."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + try: + expiry_days = int(request.data.get("expiry", 30)) + if expiry_days <= 0: + raise ValueError + except (TypeError, ValueError): + return Response( + {"error": "Expiry must be a positive integer (days)."}, + status=status.HTTP_400_BAD_REQUEST, + )And then update line 126 to use
expiry_days
instead of parsing the expiry string again:- instance, token = self.create_token(timedelta(days=int(expiry))) + instance, token = self.create_token(timedelta(days=expiry_days))
🧹 Nitpick comments (3)
backend/iam/views.py (3)
14-17
: Strip unused imports to keep imports tidy.-from knox.views import DateTimeField, LoginView as KnoxLoginView, user_logged_in +from knox.views import DateTimeField, LoginView as KnoxLoginView
76-139
: Consider using ModelViewSet for cleaner implementation.While using
APIView
works, Django REST Framework'sModelViewSet
could simplify the implementation for standard CRUD operations on tokens. For more customization control, consider usingGenericAPIView
with appropriate mixins.This would make the code more concise and follow Django REST Framework's patterns more closely.
76-157
: Add docstrings to new classes and methods for better documentation.The new classes and methods lack comprehensive docstrings that explain their purpose, parameters, return values, and possible exceptions. This would improve the code's maintainability and help other developers understand the implementation.
+""" +API view for listing and creating personal access tokens. +"""And similar docstrings for each public method.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
backend/iam/migrations/0012_personalaccesstoken.py
(1 hunks)backend/iam/models.py
(3 hunks)backend/iam/serializers.py
(2 hunks)backend/iam/views.py
(3 hunks)enterprise/backend/enterprise_core/settings.py
(1 hunks)frontend/messages/en.json
(1 hunks)frontend/src/routes/(app)/(internal)/my-profile/settings/+page.svelte
(3 hunks)
✅ Files skipped from review due to trivial changes (1)
- frontend/messages/en.json
🚧 Files skipped from review as they are similar to previous changes (4)
- backend/iam/serializers.py
- backend/iam/migrations/0012_personalaccesstoken.py
- backend/iam/models.py
- frontend/src/routes/(app)/(internal)/my-profile/settings/+page.svelte
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/iam/views.py (2)
backend/iam/models.py (6)
PersonalAccessToken
(868-889)Role
(599-612)RoleAssignment
(615-865)digest
(885-886)delete
(441-443)expiry
(881-882)backend/iam/serializers.py (1)
PersonalAccessTokenReadSerializer
(120-129)
⏰ Context from checks skipped due to timeout of 90000ms (10)
- GitHub Check: startup-functional-test (3.12)
- GitHub Check: startup-docker-compose-test
- GitHub Check: Analyze (python)
- GitHub Check: enterprise-startup-functional-test (3.12)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: enterprise-startup-docker-compose-test
- GitHub Check: functional-tests (3.12, chromium)
- GitHub Check: build (3.12)
- GitHub Check: test (3.12)
- GitHub Check: enterprise-functional-tests (3.12, chromium)
🔇 Additional comments (5)
enterprise/backend/enterprise_core/settings.py (2)
273-273
: Appropriate token limit configuration.Setting a limit of 5 tokens per user is a good security practice that balances usability with risk management. This helps prevent token proliferation and reduces the attack surface.
280-280
: Good explicit configuration of Knox token model.Explicitly configuring
KNOX_TOKEN_MODEL
ensures that the application uses the right token model across all components. This is essential for the new Personal Access Token implementation.backend/iam/views.py (3)
76-95
: Good implementation of utility methods for token management.The utility methods are well-structured and follow appropriate separation of concerns. Using Knox settings through getter methods provides flexibility and maintainability.
96-112
: Clean token creation and response formatting.The methods for token creation and response formatting are well-implemented. Breaking these into separate methods improves maintainability and testability.
129-138
: Good implementation of token listing endpoint.The GET method correctly filters tokens by the authenticated user and uses an appropriate serializer with context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (2)
backend/app_tests/api/test_api_personal_access_tokens.py (2)
1-264
: 🛠️ Refactor suggestionMissing DELETE operation test
The PR mentions supporting full CRUD operations for PATs, but there's no test for the delete functionality.
Add a test for token deletion to ensure complete coverage of the API functionality:
@pytest.mark.django_db def test_delete_token(self, authenticated_client, personal_access_token): """Test deleting a personal access token.""" url = f"/api/iam/auth-tokens/{personal_access_token.id}/" response = authenticated_client.delete(url) assert response.status_code == status.HTTP_204_NO_CONTENT assert not PersonalAccessToken.objects.filter(id=personal_access_token.id).exists()
1-264
: 💡 Verification agent🧩 Analysis chain
Consider adding test for handling duplicate token names
There's no test to verify how the API handles tokens with duplicate names for the same user.
🏁 Script executed:
#!/bin/bash # Check if there are any validation constraints for token names in the model or schema # Search for token name validation in models.py echo "Searching for token name validation in models.py:" grep -r "class PersonalAccessToken" --include="*.py" . rg "name\s*=" -A 5 -B 5 --include="*.py" | grep -i "unique" # Check for validation in serializers echo "Checking serializers for token name validation:" rg "TokenSerializer|AccessTokenSerializer" -A 20 --include="*.py"Length of output: 1518
🏁 Script executed:
#!/bin/bash echo "Inspecting PersonalAccessToken model constraints:" rg -n "class PersonalAccessToken" -A 30 backend/iam/models.py echo "Searching for unique constraints across the repo:" rg -n "unique=True" -g "*.py" echo "Inspecting PersonalAccessToken serializer:" rg -n "class PersonalAccessToken" -A 30 backend/iam/serializers.pyLength of output: 4367
Add duplicate token name handling and tests
The
name
field on PersonalAccessToken has no uniqueness constraint, so the API currently allows duplicate names for the same user. Decide on the desired behavior and update accordingly:
If duplicate names should be disallowed:
• Add a unique_together or unique constraint on(user, name)
inbackend/iam/models.py
.
• Enforce validation in the serializer or view (e.g., raise 400 with “Token name must be unique per user”).
• Add a test that creating a second token with the same name returns 400 and does not increase the token count.If duplicate names are acceptable:
• Add a test intest_api_personal_access_tokens.py
that creating two tokens with the same name for one user succeeds (status 200) and both appear in the list endpoint.Files to update:
- backend/iam/models.py (define uniqueness if needed)
- backend/iam/serializers.py or views (add validation)
- backend/app_tests/api/test_api_personal_access_tokens.py (add the new test)
🧹 Nitpick comments (2)
backend/app_tests/api/test_api_personal_access_tokens.py (2)
227-264
: Token expiration validationThe test correctly verifies that expired tokens cannot be used for authentication. However, line 259 modifies the TokenAuthentication class directly which could affect other tests.
Consider using a more isolated approach for overriding the validation behavior. For example:
- # Clear the authentication cache if needed - TokenAuthentication.validate_user = lambda self, user: user + # Create a temporary TokenAuthentication instance for this test + auth = TokenAuthentication() + original_validate = auth.validate_user + try: + auth.validate_user = lambda user: user + # Try to use the expired token + expired_response = test_client.get(protected_url) + assert expired_response.status_code == status.HTTP_401_UNAUTHORIZED + finally: + # Restore original method + auth.validate_user = original_validate
106-108
: Using hardcoded token limit may lead to inconsistent testsThe test uses a hardcoded token limit of 2, but the PR summary mentions a limit of 5 tokens per user.
Consider importing the actual token limit from settings to ensure tests stay in sync with application configuration:
- # Set token limit for test - monkeypatch.setattr(knox_settings, "TOKEN_LIMIT_PER_USER", 2) + # Import and use the actual token limit from settings + from ciso_assistant.settings import TOKEN_LIMIT_PER_USER + # Use a smaller limit just for this test + monkeypatch.setattr(knox_settings, "TOKEN_LIMIT_PER_USER", min(2, TOKEN_LIMIT_PER_USER))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
backend/app_tests/api/test_api_personal_access_tokens.py
(1 hunks)backend/iam/urls.py
(2 hunks)backend/iam/views.py
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- backend/iam/urls.py
- backend/iam/views.py
⏰ Context from checks skipped due to timeout of 90000ms (10)
- GitHub Check: startup-functional-test (3.12)
- GitHub Check: startup-docker-compose-test
- GitHub Check: enterprise-startup-functional-test (3.12)
- GitHub Check: enterprise-startup-docker-compose-test
- GitHub Check: test (3.12)
- GitHub Check: Analyze (python)
- GitHub Check: functional-tests (3.12, chromium)
- GitHub Check: build (3.12)
- GitHub Check: enterprise-functional-tests (3.12, chromium)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
backend/app_tests/api/test_api_personal_access_tokens.py (8)
16-50
: Well-structured fixtures for PAT testingThe test fixtures are comprehensive and well-organized, providing all the necessary components for testing the Personal Access Token functionality. Good use of pytest fixtures to create reusable test components.
52-85
: Great token creation test coverageThe tests thoroughly verify token creation with both default and custom expiry periods. The validation in lines 83-84 correctly allows for small time differences during test execution, which prevents flaky tests.
86-103
: Comprehensive validation error testingGood job testing both negative and non-numeric expiry values to ensure proper error handling in the API.
104-126
: Effective token limit enforcement testThe test effectively verifies that users cannot exceed their token limit using monkeypatching to isolate the test environment.
127-147
: Token retrieval tests are thoroughBoth cases of retrieving existing tokens and handling empty results are correctly tested.
149-185
: Good user isolation testingThe test properly verifies that users can only see their own tokens, which is a critical security requirement for PATs.
186-198
: Authentication security verificationThe test confirms that unauthenticated users cannot access or create tokens, ensuring proper access control.
199-226
: Token authentication verificationThe test successfully verifies that a generated PAT can be used to authenticate API requests, which is the primary purpose of PATs.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Style