Skip to content
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

Implement API create World #248

Merged
merged 9 commits into from
Oct 25, 2024

Conversation

odkhang
Copy link
Collaborator

@odkhang odkhang commented Oct 9, 2024

As part of issue fossasia/eventyay-tickets#358
This PR is implement API create world which using by eventyay-common

Summary by Sourcery

Implement a new API endpoint to create or update a 'World' entity, including permission checks and JWT-based authentication. Update the settings to include a new 'DOMAIN_PATH' configuration for constructing world URLs.

New Features:

  • Introduce a new API endpoint for creating or updating a 'World' entity, allowing users with the appropriate permissions to manage world configurations.

Enhancements:

  • Add a new configuration setting 'DOMAIN_PATH' in the settings file to define the base domain path for world URLs.

Copy link

sourcery-ai bot commented Oct 9, 2024

Reviewer's Guide by Sourcery

This pull request implements an API endpoint for creating and updating World objects. It introduces a new CreateWorldView class that handles POST requests to create or update a World, including authentication and permission checks. The changes also involve updates to URL routing and settings to support this new functionality.

Sequence diagram for CreateWorldView POST request

sequenceDiagram
    actor User
    participant CreateWorldView
    participant World
    User->>CreateWorldView: POST /create-world/
    CreateWorldView->>CreateWorldView: get_payload_from_token(request)
    alt User has permission
        CreateWorldView->>World: Check if World exists
        alt World exists
            CreateWorldView->>World: Update World
        else World does not exist
            CreateWorldView->>World: Create new World
        end
        World-->>CreateWorldView: World object
        CreateWorldView-->>User: 201 Created
    else User lacks permission
        CreateWorldView-->>User: 403 Forbidden
    end
Loading

Architecture diagram for API endpoint integration

graph TD;
    subgraph Django Application
        A[CreateWorldView]
        B[World Model]
    end
    subgraph Django Settings
        C[DOMAIN_PATH]
        D[BASE_PATH]
    end
    A --> B
    A --> C
    A --> D
    A -.->|POST /create-world/| User
Loading

Class diagram for CreateWorldView

classDiagram
    class CreateWorldView {
        +post(request, *args, **kwargs) JsonResponse
        +get_payload_from_token(request)
    }
    class World {
        +id
        +title
        +domain
        +locale
        +timezone
        +config
        +save()
        +create(id, title, domain, locale, timezone, config)
    }
    CreateWorldView --> World : uses
Loading

File-Level Changes

Change Details Files
Implement CreateWorldView for handling World creation and updates
  • Add CreateWorldView class with authentication and permission checks
  • Implement logic to create a new World or update an existing one
  • Generate a random secret for JWT authentication
  • Handle exceptions and return appropriate JSON responses
  • Implement method to extract payload from JWT token
server/venueless/api/views.py
Update URL routing to include the new CreateWorldView
  • Add URL path for 'create-world/' endpoint
server/venueless/api/urls.py
Update settings to include DOMAIN_PATH
  • Add DOMAIN_PATH configuration with a default value
server/venueless/settings.py
Minor code formatting changes
  • Adjust indentation in ExportView
  • Remove blank line in imports
server/venueless/api/views.py
server/venueless/api/urls.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @odkhang - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • Implement proper authentication and authorization for CreateWorldView (link)

Overall Comments:

  • Consider implementing proper authentication and authorization instead of disabling them entirely for the CreateWorldView. This would improve the security of the API endpoint.
  • The create and update operations for a World are currently mixed in a single block. Consider separating these into distinct methods for better code organization and maintainability.
Here's what I looked at during the review
  • 🟡 General issues: 3 issues found
  • 🔴 Security: 1 blocking issue, 1 other issue
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@@ -115,6 +119,70 @@ def get(self, request, **kwargs):
)


class CreateWorldView(APIView):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): Implement proper authentication and authorization for CreateWorldView

The current implementation with empty authentication_classes and permission_classes could allow unauthorized access to world creation. Consider implementing appropriate authentication and permission checks.

class CreateWorldView(APIView):
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    permission_classes = [IsAdminUser]

timezone=request.data.get('timezone'),
config=config,
)
except Exception as e:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Implement more granular exception handling

Catching a broad Exception can mask specific errors. Consider catching and handling specific exceptions (e.g., IntegrityError, ValidationError) to provide more informative error messages and easier debugging.

            except (IntegrityError, ValidationError) as e:
                logger.error(f"Error creating world: {e}")
                return JsonResponse({'error': str(e)}, status=400)
            except Exception as e:
                logger.error(f"Unexpected error creating world: {e}")
                return JsonResponse({'error': 'An unexpected error occurred'}, status=500)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bad practice to catch a very general exception class. This has been reminded before.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your feedback, we will generate fewer comments like this in the future according to the following instructions:

- Avoid suggesting changes that are already well-understood best practices unless there is a specific context or evidence that the current implementation is causing issues.
- Ensure that suggestions for code improvements are backed by specific examples or evidence of potential problems in the current code.
- Provide actionable and context-specific advice rather than general coding best practices.

raise exceptions.AuthenticationFailed(
"Invalid token header. Token string should not contain spaces."
)
payload = jwt.decode(auth_header[1], settings.SECRET_KEY, algorithms=["HS256"])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Add exception handling for JWT decoding

JWT decoding can raise exceptions if the token is invalid or expired. Implement try-except blocks to handle potential jwt.DecodeError and jwt.ExpiredSignatureError exceptions.

try:
    payload = jwt.decode(auth_header[1], settings.SECRET_KEY, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
    raise exceptions.AuthenticationFailed("Token has expired")
except jwt.DecodeError:
    raise exceptions.AuthenticationFailed("Invalid token")
return payload

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow sourcery-api.

# if world already exists, update it, otherwise create a new world
try:
if World.objects.filter(id=request.data.get('id')).exists():
world = World.objects.get(id=request.data.get('id'))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Use .get() with default value or check key existence

Using .get() without a default value on request.data can raise KeyError if the key doesn't exist. Consider using .get('id', None) or check if 'id' in request.data before accessing it.

world_id = request.data.get('id')
if world_id is not None:
    world = World.objects.get(id=world_id)

@@ -40,6 +40,7 @@
"VENUELESS_DJANGO_SECRET", config.get("django", "secret", fallback="")
)
BASE_PATH = config.get("venueless", "base_path", fallback="")
DOMAIN_PATH = config.get("venueless", "domain_path", fallback="https://app.eventyay.com")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): Consider using environment variables for configuration

Using environment variables for configuration settings like DOMAIN_PATH can improve security and flexibility, especially when deploying in different environments.

Suggested change
DOMAIN_PATH = config.get("venueless", "domain_path", fallback="https://app.eventyay.com")
DOMAIN_PATH = os.environ.get('VENUELESS_DOMAIN_PATH', 'https://app.eventyay.com')

Copy link
Member

@mariobehling mariobehling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All server style tests are failing. Please fix.

@mariobehling mariobehling self-requested a review October 10, 2024 11:06
]
}

title_default = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This impementation leave to many holes:

  • data.get() can return None and None.items() will throw exception.
  • set.pop() will throw exception if set is empty.

Also, if you don't use key, retrieving values from dict instead.


# if world already exists, update it, otherwise create a new world
try:
if World.objects.filter(id=request.data.get("id")).exists():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data received from outside work (form, HTTP request) have to be validated.

You are calling

World.objects.get(id=request.data.get("id")

but what if some attackers pass "id=abc"? This code will throw exception.

timezone=request.data.get('timezone'),
config=config,
)
except Exception as e:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bad practice to catch a very general exception class. This has been reminded before.

raise exceptions.AuthenticationFailed(
"Invalid token header. Token string should not contain spaces."
)
payload = jwt.decode(auth_header[1], settings.SECRET_KEY, algorithms=["HS256"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow sourcery-api.

world = World.objects.create(
id=request.data.get("id"),
title=request.data.get("title")[request.data.get("locale")]
or request.data.get("title")["en"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if data(get('title')) returns None or something not a dict. Validation needs to be applied to make sure the incoming data has the expected data type and shape.

@mariobehling mariobehling merged commit 928b0a7 into fossasia:development Oct 25, 2024
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants