initial commit
This commit is contained in:
commit
9d6828d5ab
59 changed files with 910 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Python
|
||||||
|
.venv/
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
33
.idea/SOME.iml
Normal file
33
.idea/SOME.iml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="django" name="Django">
|
||||||
|
<configuration>
|
||||||
|
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||||
|
<option name="settingsModule" value="SOME/settings.py" />
|
||||||
|
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
|
||||||
|
<option name="environment" value="<map/>" />
|
||||||
|
<option name="doNotUseTestRunner" value="false" />
|
||||||
|
<option name="trackFilePattern" value="migrations" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/TouYube" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/Witter" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||||
|
<option name="TEMPLATE_FOLDERS">
|
||||||
|
<list>
|
||||||
|
<option value="$MODULE_DIR$/TouYube/apps/TouYube/templates" />
|
||||||
|
<option value="$MODULE_DIR$/Witter/apps/Witter/templates" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
7
.idea/misc.xml
Normal file
7
.idea/misc.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.11 (SOME)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (SOME)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/SOME.iml" filepath="$PROJECT_DIR$/.idea/SOME.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
15
.idea/webResources.xml
Normal file
15
.idea/webResources.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="WebResourcesPaths">
|
||||||
|
<contentEntries>
|
||||||
|
<entry url="file://$PROJECT_DIR$">
|
||||||
|
<entryData>
|
||||||
|
<resourceRoots>
|
||||||
|
<path value="file://$PROJECT_DIR$/TouYube/apps/TouYube/static" />
|
||||||
|
<path value="file://$PROJECT_DIR$/Witter/apps/Witter/static" />
|
||||||
|
</resourceRoots>
|
||||||
|
</entryData>
|
||||||
|
</entry>
|
||||||
|
</contentEntries>
|
||||||
|
</component>
|
||||||
|
</project>
|
4
TouYube/.gitignore
vendored
Normal file
4
TouYube/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Django
|
||||||
|
.static/
|
||||||
|
.media/
|
||||||
|
db.sqlite3
|
5
TouYube/README.md
Normal file
5
TouYube/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# TouYube
|
||||||
|
This is an example website that host video.
|
||||||
|
The point of this website is to demonstrate SOME attack that exploit a CORS vulnerability.
|
||||||
|
|
||||||
|
CORS configuration can be edited in the `configuration/settings.py` file at the "ENABLE_CROSS_ORIGIN_SECURITY" settings.
|
0
TouYube/__init__.py
Normal file
0
TouYube/__init__.py
Normal file
0
TouYube/apps/TouYube/__init__.py
Normal file
0
TouYube/apps/TouYube/__init__.py
Normal file
3
TouYube/apps/TouYube/admin.py
Normal file
3
TouYube/apps/TouYube/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
6
TouYube/apps/TouYube/apps.py
Normal file
6
TouYube/apps/TouYube/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TouyubeConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'apps.TouYube'
|
19
TouYube/apps/TouYube/forms.py
Normal file
19
TouYube/apps/TouYube/forms.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(forms.Form):
|
||||||
|
"""
|
||||||
|
A form to login
|
||||||
|
"""
|
||||||
|
|
||||||
|
username = forms.CharField(max_length=32)
|
||||||
|
password = forms.CharField(widget=forms.PasswordInput())
|
||||||
|
|
||||||
|
|
||||||
|
class UploadForm(forms.Form):
|
||||||
|
"""
|
||||||
|
A form to upload a video
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = forms.CharField(max_length=128)
|
||||||
|
content = forms.FileField()
|
27
TouYube/apps/TouYube/migrations/0001_initial.py
Normal file
27
TouYube/apps/TouYube/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 5.0.6 on 2024-05-14 18:08
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Video',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=128)),
|
||||||
|
('content', models.FileField(upload_to='videos/')),
|
||||||
|
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='videos', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
TouYube/apps/TouYube/migrations/__init__.py
Normal file
0
TouYube/apps/TouYube/migrations/__init__.py
Normal file
16
TouYube/apps/TouYube/models.py
Normal file
16
TouYube/apps/TouYube/models.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Video(models.Model):
|
||||||
|
"""
|
||||||
|
Represent a video.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
|
||||||
|
|
||||||
|
name = models.CharField(max_length=128)
|
||||||
|
author = models.ForeignKey(to=get_user_model(), on_delete=models.CASCADE, related_name="videos")
|
||||||
|
content = models.FileField(upload_to=f"videos/")
|
3
TouYube/apps/TouYube/static/TouYube/embed.css
Normal file
3
TouYube/apps/TouYube/static/TouYube/embed.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
12
TouYube/apps/TouYube/templates/TouYube/base/base.html
Normal file
12
TouYube/apps/TouYube/templates/TouYube/base/base.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{% block head %}
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{% block title %}TouYube{% endblock %}</title>
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<nav>
|
||||||
|
<button><a href="{% url 'homepage' %}">Homepage</a></button>
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<button><a href="{% url 'video_upload' %}">Upload</a></button>
|
||||||
|
<button><a href="{% url 'logout' %}">Logout</a></button>
|
||||||
|
{% else %}
|
||||||
|
<button><a href="{% url 'login' %}">Login</a></button>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
16
TouYube/apps/TouYube/templates/TouYube/homepage.html
Normal file
16
TouYube/apps/TouYube/templates/TouYube/homepage.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends "TouYube/base/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} - Homepage{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{% include "TouYube/base/navigation.html" %}
|
||||||
|
|
||||||
|
<h1>Homepage</h1>
|
||||||
|
|
||||||
|
{# go through all the videos #}
|
||||||
|
{% for video in videos %}
|
||||||
|
{# show the video #}
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'video_full' video.id %}"><b>{{ video.name }}</b> - {{ video.author }}</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
12
TouYube/apps/TouYube/templates/TouYube/login.html
Normal file
12
TouYube/apps/TouYube/templates/TouYube/login.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "TouYube/base/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} - Login{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{% include "TouYube/base/navigation.html" %}
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form_login }}
|
||||||
|
<input type="submit" value="Login" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
12
TouYube/apps/TouYube/templates/TouYube/upload.html
Normal file
12
TouYube/apps/TouYube/templates/TouYube/upload.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "TouYube/base/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} - Upload{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{% include "TouYube/base/navigation.html" %}
|
||||||
|
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form_upload }}
|
||||||
|
<input type="submit" value="Upload" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
15
TouYube/apps/TouYube/templates/TouYube/video_embed.html
Normal file
15
TouYube/apps/TouYube/templates/TouYube/video_embed.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends "TouYube/base/base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ block.super }}
|
||||||
|
<link rel="stylesheet" href="{% static 'TouYube/embed.css' %}" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} - Video {{ video.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<video controls width="100%" height="100%">
|
||||||
|
<source src="{{ video.content.url }}" type="video/mp4"/>
|
||||||
|
</video>
|
||||||
|
{% endblock %}
|
18
TouYube/apps/TouYube/templates/TouYube/video_full.html
Normal file
18
TouYube/apps/TouYube/templates/TouYube/video_full.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{% extends "TouYube/base/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} - Video {{ video.name }}{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
{% include "TouYube/base/navigation.html" %}
|
||||||
|
|
||||||
|
<h1>{{ video.name }}</h1>
|
||||||
|
<h2>{{ video.author }}</h2>
|
||||||
|
|
||||||
|
<video controls width="500">
|
||||||
|
<source src="{{ video.content.url }}" type="video/mp4"/>
|
||||||
|
</video>
|
||||||
|
|
||||||
|
{% if request.user == video.author %}
|
||||||
|
<button><a href="{% url 'video_delete' video.id %}">Delete</a></button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
3
TouYube/apps/TouYube/tests.py
Normal file
3
TouYube/apps/TouYube/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
0
TouYube/apps/TouYube/urls.py
Normal file
0
TouYube/apps/TouYube/urls.py
Normal file
132
TouYube/apps/TouYube/views.py
Normal file
132
TouYube/apps/TouYube/views.py
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
|
from django.http import HttpResponse, HttpResponseForbidden
|
||||||
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
|
|
||||||
|
from apps.TouYube import forms, models
|
||||||
|
|
||||||
|
|
||||||
|
def view_homepage(request: WSGIRequest) -> HttpResponse:
|
||||||
|
videos = models.Video.objects.all()
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"TouYube/homepage.html",
|
||||||
|
dict(videos=videos)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def view_login(request: WSGIRequest) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Login to the website
|
||||||
|
"""
|
||||||
|
|
||||||
|
form_login = forms.LoginForm(request.POST)
|
||||||
|
|
||||||
|
if form_login.is_valid():
|
||||||
|
# try to authenticate the user
|
||||||
|
user = authenticate(
|
||||||
|
request,
|
||||||
|
username=form_login.cleaned_data["username"],
|
||||||
|
password=form_login.cleaned_data["password"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# if authenticated, log him persistently
|
||||||
|
if user is not None:
|
||||||
|
login(request, user)
|
||||||
|
return redirect("homepage")
|
||||||
|
|
||||||
|
# otherwise add an error to the form
|
||||||
|
form_login.add_error("password", "invalid credentials")
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"TouYube/login.html",
|
||||||
|
dict(form_login=form_login)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def view_logout(request: WSGIRequest) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Logout from the website
|
||||||
|
"""
|
||||||
|
|
||||||
|
logout(request)
|
||||||
|
|
||||||
|
return redirect("homepage")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def view_video_upload(request: WSGIRequest) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
The page to upload a file
|
||||||
|
"""
|
||||||
|
|
||||||
|
form_upload = forms.UploadForm(request.POST, request.FILES)
|
||||||
|
|
||||||
|
if form_upload.is_valid():
|
||||||
|
# save the video
|
||||||
|
video = models.Video.objects.create(
|
||||||
|
author=request.user,
|
||||||
|
name=form_upload.cleaned_data["name"],
|
||||||
|
content=form_upload.cleaned_data["content"]
|
||||||
|
)
|
||||||
|
video.save()
|
||||||
|
|
||||||
|
# redirect the user to his own video
|
||||||
|
return redirect("video_full", video.id)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"TouYube/upload.html",
|
||||||
|
dict(form_upload=form_upload)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def view_video_delete(request: WSGIRequest, video_id: UUID) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Delete a video
|
||||||
|
"""
|
||||||
|
|
||||||
|
video = get_object_or_404(models.Video, id=video_id)
|
||||||
|
|
||||||
|
# check if the user is the video's author
|
||||||
|
if request.user != video.author:
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
|
# delete the video
|
||||||
|
video.delete()
|
||||||
|
|
||||||
|
return redirect("homepage")
|
||||||
|
|
||||||
|
|
||||||
|
def view_video_full(request: WSGIRequest, video_id: UUID) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Render the page for a video
|
||||||
|
"""
|
||||||
|
|
||||||
|
video = get_object_or_404(models.Video, id=video_id)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"TouYube/video_full.html",
|
||||||
|
dict(video=video)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def view_video_embed(request: WSGIRequest, video_id: UUID) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Render the page for an embedded video
|
||||||
|
"""
|
||||||
|
|
||||||
|
video = get_object_or_404(models.Video, id=video_id)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"TouYube/video_embed.html",
|
||||||
|
dict(video=video)
|
||||||
|
)
|
0
TouYube/apps/__init__.py
Normal file
0
TouYube/apps/__init__.py
Normal file
0
TouYube/configuration/__init__.py
Normal file
0
TouYube/configuration/__init__.py
Normal file
16
TouYube/configuration/asgi.py
Normal file
16
TouYube/configuration/asgi.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
ASGI config for configuration project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
149
TouYube/configuration/settings.py
Normal file
149
TouYube/configuration/settings.py
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
"""
|
||||||
|
Django settings for configuration project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.0.6.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-87uz=bnd&m+$(!qfzs3$bum)!e6pw$8gaw_^cehmuyuft5q4!#'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
ENABLE_CROSS_ORIGIN_SECURITY = False
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
|
*(("corsheaders",) if ENABLE_CROSS_ORIGIN_SECURITY else ()),
|
||||||
|
|
||||||
|
'apps.TouYube',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
*(("corsheaders.middleware.CorsMiddleware",) if ENABLE_CROSS_ORIGIN_SECURITY else ()),
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
*(('django.middleware.csrf.CsrfViewMiddleware') if ENABLE_CROSS_ORIGIN_SECURITY else ()),
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
*(('django.middleware.clickjacking.XFrameOptionsMiddleware',) if ENABLE_CROSS_ORIGIN_SECURITY else ()),
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'configuration.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'configuration.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
STATIC_ROOT = ".static"
|
||||||
|
|
||||||
|
# Media files
|
||||||
|
|
||||||
|
MEDIA_URL = "media/"
|
||||||
|
MEDIA_ROOT = ".media"
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# CORS Settings
|
||||||
|
# https://pypi.org/project/django-cors-headers/
|
||||||
|
|
||||||
|
# CORS_ALLOWED_ORIGINS: list[str] = []
|
||||||
|
# CORS_ALLOW_ALL_ORIGINS: bool = True
|
||||||
|
|
||||||
|
if not ENABLE_CROSS_ORIGIN_SECURITY:
|
||||||
|
X_FRAME_OPTIONS = 'ALLOWALL'
|
||||||
|
|
||||||
|
|
||||||
|
# Login Settings
|
||||||
|
LOGIN_URL: str = "login/"
|
38
TouYube/configuration/urls.py
Normal file
38
TouYube/configuration/urls.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
"""
|
||||||
|
URL configuration for configuration project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
import apps.TouYube.views
|
||||||
|
from configuration import settings
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', apps.TouYube.views.view_homepage, name="homepage"),
|
||||||
|
path('login/', apps.TouYube.views.view_login, name="login"),
|
||||||
|
path('logout/', apps.TouYube.views.view_logout, name="logout"),
|
||||||
|
path('video/upload/', apps.TouYube.views.view_video_upload, name="video_upload"),
|
||||||
|
path('video/delete/<uuid:video_id>/', apps.TouYube.views.view_video_delete, name="video_delete"),
|
||||||
|
path('video/view/<uuid:video_id>/', apps.TouYube.views.view_video_full, name="video_full"),
|
||||||
|
path('video/embed/<uuid:video_id>/', apps.TouYube.views.view_video_embed, name="video_embed"),
|
||||||
|
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
16
TouYube/configuration/wsgi.py
Normal file
16
TouYube/configuration/wsgi.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
WSGI config for configuration project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
22
TouYube/manage.py
Executable file
22
TouYube/manage.py
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
2
TouYube/requirements.txt
Normal file
2
TouYube/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
django
|
||||||
|
django-cors-headers
|
3
Witter/.gitignore
vendored
Normal file
3
Witter/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Django
|
||||||
|
db.sqlite3
|
||||||
|
|
3
Witter/README.md
Normal file
3
Witter/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Witter
|
||||||
|
This is an example website that will embed video maliciously.
|
||||||
|
The point of this website is to demonstrate SOME attack that exploit a CORS vulnerability.
|
0
Witter/__init__.py
Normal file
0
Witter/__init__.py
Normal file
0
Witter/apps/Witter/__init__.py
Normal file
0
Witter/apps/Witter/__init__.py
Normal file
3
Witter/apps/Witter/admin.py
Normal file
3
Witter/apps/Witter/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
6
Witter/apps/Witter/apps.py
Normal file
6
Witter/apps/Witter/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class WitterConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'apps.Witter'
|
1
Witter/apps/Witter/forms.py
Normal file
1
Witter/apps/Witter/forms.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from django import forms
|
0
Witter/apps/Witter/migrations/__init__.py
Normal file
0
Witter/apps/Witter/migrations/__init__.py
Normal file
1
Witter/apps/Witter/models.py
Normal file
1
Witter/apps/Witter/models.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from django.db import models
|
12
Witter/apps/Witter/templates/Witter/base/base.html
Normal file
12
Witter/apps/Witter/templates/Witter/base/base.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{% block head %}
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{% block title %}Witter{% endblock %}</title>
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
10
Witter/apps/Witter/templates/Witter/homepage.html
Normal file
10
Witter/apps/Witter/templates/Witter/homepage.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "Witter/base/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }} - Homepage{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h1>Homepage</h1>
|
||||||
|
|
||||||
|
<a href="http://localhost:8080/video/delete/9cf5f8e6-9333-41de-a913-f4ec2e698a9d/">
|
||||||
|
Je suis un bouton qui ne va sûrement pas supprimer ta vidéo préférée 😊
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
3
Witter/apps/Witter/tests.py
Normal file
3
Witter/apps/Witter/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
0
Witter/apps/Witter/urls.py
Normal file
0
Witter/apps/Witter/urls.py
Normal file
12
Witter/apps/Witter/views.py
Normal file
12
Witter/apps/Witter/views.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from apps.Witter import forms, models
|
||||||
|
|
||||||
|
|
||||||
|
def view_homepage(request: WSGIRequest) -> HttpResponse:
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"Witter/homepage.html",
|
||||||
|
)
|
0
Witter/apps/__init__.py
Normal file
0
Witter/apps/__init__.py
Normal file
0
Witter/configuration/__init__.py
Normal file
0
Witter/configuration/__init__.py
Normal file
16
Witter/configuration/asgi.py
Normal file
16
Witter/configuration/asgi.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
ASGI config for configuration project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
129
Witter/configuration/settings.py
Normal file
129
Witter/configuration/settings.py
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
"""
|
||||||
|
Django settings for configuration project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.0.6.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-8p^3+c8q#t1i@8do06^#+%lr&*n8474!d_tp4%zua+7!^qw2jj'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
|
'corsheaders',
|
||||||
|
|
||||||
|
'apps.Witter',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'configuration.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'configuration.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
STATIC_ROOT = ".static"
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
31
Witter/configuration/urls.py
Normal file
31
Witter/configuration/urls.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"""
|
||||||
|
URL configuration for configuration project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
import apps.Witter.views
|
||||||
|
from configuration import settings
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', apps.Witter.views.view_homepage, name="homepage"),
|
||||||
|
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
16
Witter/configuration/wsgi.py
Normal file
16
Witter/configuration/wsgi.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
WSGI config for configuration project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
22
Witter/manage.py
Executable file
22
Witter/manage.py
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
2
Witter/requirements.txt
Normal file
2
Witter/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
django
|
||||||
|
django-cors-headers
|
Loading…
Reference in a new issue