#32 Implement CSRF protection

병합
sindre csrf 에서 master 로 1 commits 를 머지했습니다 5 년 전
  1. +8
    -7
      src/app/templates/apply.html
  2. +5
    -4
      src/app/templates/login.html
  3. +1
    -0
      src/app/templates/new_project.html
  4. +3
    -2
      src/app/templates/project.html
  5. +2
    -1
      src/app/templates/register.html
  6. +5
    -4
      src/app/views/app.py
  7. +7
    -8
      src/app/views/apply.py
  8. +2
    -1
      src/app/views/login.py
  9. +24
    -24
      src/app/views/new_project.py
  10. +7
    -7
      src/app/views/project.py
  11. +6
    -6
      src/app/views/register.py
  12. +44
    -4
      src/app/views/utils.py

+ 8
- 7
src/app/templates/apply.html 파일 보기

@@ -6,9 +6,9 @@ $def with (nav, apply_form, get_apply_permission_form, project, applicants, perm
<link rel="stylesheet" type="text/css" href="static/stylesheet.css">
<link rel="shortcut icon" type="image/png" href="static/honeybee.png"/>
</head>
<body>
$:nav

<h1>Apply for this project</h1>
@@ -19,8 +19,9 @@ $def with (nav, apply_form, get_apply_permission_form, project, applicants, perm
<p>Description: $project[4]</p>
<p>Status: $project[5]</p>
</div>
<form method="POST">
$:csrf_field()
<h4>Add Users</h4>
$:apply_form.render()
<h4>Users to apply:</h4>
@@ -29,8 +30,8 @@ $def with (nav, apply_form, get_apply_permission_form, project, applicants, perm
<div class="applicant">
User: $applicants[i][1]
$ apply_permissions_form = get_apply_permissions_form(i, permissions[i][0], permissions[i][1], permissions[i][2], applicants[i])
$:apply_permissions_form.render()
$:apply_permissions_form.render()
</div>
</form>
</body>
</form>
</body>

+ 5
- 4
src/app/templates/login.html 파일 보기

@@ -9,20 +9,21 @@ $def with (nav, login_form, message)
</head>

<body>
$:nav

$if not session.username:
<h3>Log In</h3>
<form method="POST">
$:csrf_field()
$:login_form.render()
</form>
</form>

$else:
<h1>Logged in as $session.username</h1>
<p>$:message</p>

<img src="static/honeybee.png" alt="Honeybee">

</body>
</body>

+ 1
- 0
src/app/templates/new_project.html 파일 보기

@@ -14,6 +14,7 @@ $def with (nav, project_form, project_buttons, messasge)

<h2>Add project!</h2>
<form method="POST">
$:csrf_field()
<div>
$:project_form.render()
</div>


+ 3
- 2
src/app/templates/project.html 파일 보기

@@ -9,7 +9,7 @@ $def with (nav, project_form, project, tasks, permissions, categories)
</head>

<body>
$:nav

$if len(project) and (permissions[0] or project[5] == "open" or session.userid == project[2]) and session.username:
@@ -37,6 +37,7 @@ $def with (nav, project_form, project, tasks, permissions, categories)
<a href="$filename[0]" download>$filename[0].split("/")[-1]</a>
<br>
<form method="POST" enctype="multipart/form-data" action="">
$:csrf_field()
$project_form.taskid.set_value(task[0])
$:project_form.taskid.render()
$if (task[5] == "waiting for delivery" or task[5] == "declined"):
@@ -53,4 +54,4 @@ $def with (nav, project_form, project, tasks, permissions, categories)
</ul>
$else:
<p>You do not have permissions to view this project</p>
</body>
</body>

+ 2
- 1
src/app/templates/register.html 파일 보기

@@ -13,8 +13,9 @@ $def with (nav, register_form, message)
$:nav

<h2>Register user!</h2>
<form method="POST">
$:csrf_field()
$:register_form.render()
</form>



+ 5
- 4
src/app/views/app.py 파일 보기

@@ -1,6 +1,6 @@
import os
import web
from views.utils import get_nav_bar
from views.utils import get_nav_bar, csrf_field
from views.login import Login
from views.logout import Logout
from views.register import Register
@@ -22,7 +22,7 @@ urls = (
'/project', 'Project',
'/apply', 'Apply',
)
# Initialize application using the web py framework
app = web.application(urls, globals())

@@ -42,6 +42,9 @@ else:
# Add session to global variables
render._add_global(session, 'session')

# Add CSRF field to global variables
web.template.Template.globals['csrf_field'] = csrf_field

# Make the session available cross modules through webctx
def session_hook():
web.ctx.session = session
@@ -50,5 +53,3 @@ def session_hook():
app.add_processor(web.loadhook(session_hook))

app = app.wsgifunc()



+ 7
- 8
src/app/views/apply.py 파일 보기

@@ -1,7 +1,7 @@
import web
import models.project
from models.user import get_user_name_by_id
from views.utils import get_nav_bar, get_element_count
from views.utils import get_nav_bar, get_element_count, csrf_protected
from views.forms import get_apply_form, get_apply_permissions_form

# Get html templates
@@ -34,6 +34,7 @@ class Apply:

return render.apply(nav, apply_form, get_apply_permissions_form, project, applicants, permissions)

@csrf_protected
def POST(self):
"""
Post an application to the view, adding selected users to a project
@@ -46,7 +47,7 @@ class Apply:
applicants = [session.username]
apply_form = get_apply_form()
apply_permission_form = get_apply_permissions_form()
# Prepare globals
render = web.template.render('templates/', globals={"get_apply_permissions_form":get_apply_permissions_form, 'session':session})
if data.projectid:
@@ -54,12 +55,12 @@ class Apply:

if data.add_user:
applicants, permissions = self.get_applicants(data, "add_user")
return render.apply(nav, apply_form, get_apply_permissions_form, project, applicants,permissions)
return render.apply(nav, apply_form, get_apply_permissions_form, project, applicants,permissions)

elif data.remove_user:
applicants, permissions = self.get_applicants(data, "remove_user")
return render.apply(nav, apply_form, get_apply_permissions_form, project, applicants, permissions)
return render.apply(nav, apply_form, get_apply_permissions_form, project, applicants, permissions)
# Set users as working on project and set project status in progress
elif data.apply:
applicants, permissions = self.get_applicants(data, "")
@@ -67,7 +68,7 @@ class Apply:
models.project.set_projects_user(data.projectid, str(applicant[0]), permission[0], permission[1], permission[2])
models.project.update_project_status(data.projectid, "in progress")
raise web.seeother(('/project?projectid=' + str(data.projectid)))
def get_applicants(self, data, operation):
"""
Get applicants and corresponding permissions from the input data with and operation
@@ -123,5 +124,3 @@ class Apply:
permissions.append(["TRUE", "FALSE", "FALSE"])

return applicants, permissions


+ 2
- 1
src/app/views/login.py 파일 보기

@@ -1,8 +1,8 @@
import web
from views.forms import login_form
from views.utils import get_nav_bar, csrf_protected
import models.session
import models.user
from views.utils import get_nav_bar
import random
import string
import hashlib
@@ -34,6 +34,7 @@ class Login():

return render.login(nav, login_form, "")

@csrf_protected
def POST(self):
"""
Log in to the web application and register the session


+ 24
- 24
src/app/views/new_project.py 파일 보기

@@ -1,9 +1,9 @@
import web
from web import form
from views.forms import get_task_form_elements, get_project_form_elements, get_user_form_elements, project_buttons
from views.utils import get_nav_bar, get_element_count, csrf_protected
import models.project
import models.user
from views.utils import get_nav_bar, get_element_count

# Get html templates
render = web.template.render('templates/')
@@ -13,7 +13,7 @@ class New_project:
def GET(self):
"""
Get the project registration form
:return: New project page
"""
session = web.ctx.session
@@ -26,43 +26,44 @@ class New_project:
project_form = form.Form(*(project_form_elements + task_form_elements + user_form_elements))
return render.new_project(nav, project_form, project_buttons, "")

@csrf_protected
def POST(self):
"""
Create a new project

:return: Redirect to main page
"""
"""
session = web.ctx.session
nav = get_nav_bar(session)

# Try the different URL input parameters to determine how to generate the form
data = web.input(add_user=None, remove_user=None,
data = web.input(add_user=None, remove_user=None,
add_task=None, remove_task = None, create_project=None)

# Add a set of task fields to the form
if data.add_task:
project_form = self.compose_form(data, "add_task")
return render.new_project(nav, project_form, project_buttons, "")
return render.new_project(nav, project_form, project_buttons, "")
# Remove a set of task fields from the form
if data.remove_task:
project_form = self.compose_form(data, "remove_task")
return render.new_project(nav, project_form, project_buttons, "")
return render.new_project(nav, project_form, project_buttons, "")
if data.add_user:
project_form = self.compose_form(data, "add_user")
return render.new_project(nav, project_form, project_buttons, "")
return render.new_project(nav, project_form, project_buttons, "")
if data.remove_user:
project_form = self.compose_form(data, "remove_user")
return render.new_project(nav, project_form, project_buttons, "")
return render.new_project(nav, project_form, project_buttons, "")
# Post the form data and save the project in the database
if data.create_project:
project_form = self.compose_form(data, None)
if not project_form.validates():
return render.new_project(nav, project_form, project_buttons, "")
return render.new_project(nav, project_form, project_buttons, "")

task_count = get_element_count(data, "task_title_")
user_count = get_element_count(data, "user_name_")
@@ -74,18 +75,18 @@ class New_project:

# Validate the input user names
for i in range(0, user_count):
if len(data["user_name_"+str(i)]) and not models.user.get_user_id_by_name(data["user_name_"+str(i)]):
if len(data["user_name_"+str(i)]) and not models.user.get_user_id_by_name(data["user_name_"+str(i)]):
return render.new_project(nav, project_form, project_buttons, "Invalid user: " + data["user_name_"+str(i)])

# Save the project to the database
projectid = models.project.set_project(data.category_name, str(session.userid),
projectid = models.project.set_project(data.category_name, str(session.userid),
data.project_title, data.project_description, status)

# Save the tasks in the database
for i in range(0, task_count):
models.project.set_task(str(projectid), (data["task_title_" + str(i)]),
models.project.set_task(str(projectid), (data["task_title_" + str(i)]),
(data["task_description_" + str(i)]), (data["budget_" + str(i)]))
# Save the users in the database given that the input field is not empty
for i in range(0, user_count):
if len(data["user_name_"+str(i)]):
@@ -108,10 +109,10 @@ class New_project:
modify = "TRUE"
except Exception as e:
modify = "FALSE"
pass
pass
models.project.set_projects_user(str(projectid), str(userid), read, write, modify)
raise web.seeother('/?projects=my')
def compose_form(self, data, operation):
"""
Compose a new project form by adding or removing a task
@@ -128,14 +129,14 @@ class New_project:
task_count -= 1
if operation == "remove_user" and user_count >=1:
user_count -= 1
# Recreate project form fields
project_form_elements = get_project_form_elements(data.project_title, data.project_description, data.category_name)

# Recreate task form fields
task_form_elements = ()
for i in range(0, task_count):
old_task_form_element = get_task_form_elements(i, data["task_title_"+str(i)],
old_task_form_element = get_task_form_elements(i, data["task_title_"+str(i)],
data["task_description_"+str(i)], data["budget_"+str(i)])
task_form_elements = (task_form_elements + old_task_form_element)

@@ -165,7 +166,7 @@ class New_project:
user_form_elements = (user_form_elements + old_user_form_element)

if operation == "add_task":
new_task_form_elements = get_task_form_elements(task_count)
new_task_form_elements = get_task_form_elements(task_count)
project_form = form.Form(*(project_form_elements + task_form_elements + new_task_form_elements + user_form_elements))
return project_form

@@ -176,4 +177,3 @@ class New_project:

project_form = form.Form(*(project_form_elements + task_form_elements + user_form_elements))
return project_form

+ 7
- 7
src/app/views/project.py 파일 보기

@@ -1,6 +1,6 @@
import web
import models.project
from views.utils import get_nav_bar
from views.utils import get_nav_bar, csrf_protected
from views.forms import project_form
import os
from time import sleep
@@ -17,7 +17,7 @@ class Project:

:return: Project info page
"""
# Get session
session = web.ctx.session
# Get navbar
@@ -41,12 +41,13 @@ class Project:
render = web.template.render('templates/', globals={'get_task_files':models.project.get_task_files, 'session':session})
return render.project(nav, project_form, project, tasks,permissions, categories)

@csrf_protected
def POST(self):
# Get session
session = web.ctx.session
data = web.input(myfile={}, deliver=None, accepted=None, declined=None, projectid=0)
fileitem = data['myfile']
permissions = models.project.get_user_permissions(str(session.userid), data.projectid)
categories = models.project.get_categories()
tasks = models.project.get_tasks_by_project_id(data.projectid)
@@ -81,7 +82,7 @@ class Project:
task_waiting = False
task_delivered = False
for task in tasks:
if task[0] == int(data.taskid):
if task[0] == int(data.taskid):
if(task[5] == "waiting for delivery" or task[5] == "declined"):
task_waiting = True
if(task[5] == 'accepted'):
@@ -90,7 +91,7 @@ class Project:
# Deliver task
if data.deliver and not task_delivered:
models.project.update_task_status(data.taskid, "delivered")
# Accept task delivery
elif data.accepted:
models.project.update_task_status(data.taskid, "accepted")
@@ -107,6 +108,5 @@ class Project:
# Decline task delivery
elif data.declined:
models.project.update_task_status(data.taskid, "declined")
raise web.seeother(('/project?projectid=' + data.projectid))

raise web.seeother(('/project?projectid=' + data.projectid))

+ 6
- 6
src/app/views/register.py 파일 보기

@@ -1,8 +1,8 @@
import web
from views.forms import register_form
from views.utils import get_nav_bar, csrf_protected
import models.register
import models.user
from views.utils import get_nav_bar
import hashlib
import re

@@ -22,6 +22,7 @@ class Register:
nav = get_nav_bar(session)
return render.register(nav, register_form, "")

@csrf_protected
def POST(self):
"""
Handle input data and register new user in database
@@ -40,10 +41,9 @@ class Register:
if models.user.get_user_id_by_name(data.username):
return render.register(nav, register, "Invalid user, already exists.")

models.register.set_user(data.username,
hashlib.md5(b'TDT4237' + data.password.encode('utf-8')).hexdigest(),
data.full_name, data.company, data.email, data.street_address,
models.register.set_user(data.username,
hashlib.md5(b'TDT4237' + data.password.encode('utf-8')).hexdigest(),
data.full_name, data.company, data.email, data.street_address,
data.city, data.state, data.postal_code, data.country)
return render.register(nav, register_form, "User registered!")

return render.register(nav, register_form, "User registered!")

+ 44
- 4
src/app/views/utils.py 파일 보기

@@ -1,3 +1,6 @@
import web
from uuid import uuid4


def get_nav_bar(session):
"""
@@ -20,13 +23,13 @@ def get_nav_bar(session):
result += '</nav>'
return result

def get_element_count(data, element):
"""
Determine the number of tasks created by removing
the four other elements from count and divide by the
Determine the number of tasks created by removing
the four other elements from count and divide by the
number of variables in one task.
:param data: The data object from web.input
:return: The number of tasks opened by the client
"""
@@ -38,3 +41,40 @@ def get_element_count(data, element):
except:
break
return task_count


def csrf_token():
"""
Get the CSRF token for the session
"""
session = web.ctx.session

if 'csrf_token' not in session:
session.csrf_token = uuid4().hex

return session.csrf_token


def csrf_field():
"""
Return a HTML form field for the CSRF token
"""
return f'<input type="hidden" name="csrf_token" value="{csrf_token()}" />'


def csrf_protected(f):
"""
Decorate a function to do a CSRF check.
"""
def decorated(*args, **kwargs):
session = web.ctx.session
inp = web.input()
if not ('csrf_token' in inp and inp.csrf_token == session.pop('csrf_token', None)):
raise web.HTTPError(
'400 Bad request',
{'content-type': 'text/html'},
'Cross-site request forgery attempt',
)
return f(*args, **kwargs)

return decorated

불러오는 중...
취소
저장