mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-18 14:34:21 +01:00
Handle processing field entries from admin panel and add permissions to viewing fields
This commit is contained in:
@@ -4,6 +4,7 @@ from wtforms.validators import InputRequired
|
||||
|
||||
from CTFd.forms import BaseForm
|
||||
from CTFd.forms.fields import SubmitField
|
||||
from CTFd.models import FieldEntries, Fields
|
||||
from CTFd.utils.countries import SELECT_COUNTRIES_LIST
|
||||
|
||||
|
||||
@@ -40,7 +41,7 @@ class PublicUserSearchForm(BaseForm):
|
||||
submit = SubmitField("Search")
|
||||
|
||||
|
||||
class UserEditForm(BaseForm):
|
||||
class UserBaseForm(BaseForm):
|
||||
name = StringField("User Name", validators=[InputRequired()])
|
||||
email = EmailField("Email", validators=[InputRequired()])
|
||||
password = PasswordField("Password")
|
||||
@@ -54,5 +55,59 @@ class UserEditForm(BaseForm):
|
||||
submit = SubmitField("Submit")
|
||||
|
||||
|
||||
class UserCreateForm(UserEditForm):
|
||||
notify = BooleanField("Email account credentials to user", default=True)
|
||||
def UserEditForm(*args, **kwargs):
|
||||
class _UserEditForm(UserBaseForm):
|
||||
pass
|
||||
|
||||
@property
|
||||
def extra(self):
|
||||
fields = []
|
||||
new_fields = Fields.query.all()
|
||||
user_fields = {}
|
||||
|
||||
for f in FieldEntries.query.filter_by(user_id=self.obj.id).all():
|
||||
user_fields[f.field_id] = f.value
|
||||
|
||||
for field in new_fields:
|
||||
form_field = getattr(self, f"fields[{field.id}]")
|
||||
form_field.data = user_fields.get(field.id, "")
|
||||
entry = (field.name, form_field)
|
||||
fields.append(entry)
|
||||
return fields
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Custom init to persist the obj parameter to the rest of the form
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
obj = kwargs.get("obj")
|
||||
if obj:
|
||||
self.obj = obj
|
||||
|
||||
new_fields = Fields.query.all()
|
||||
for field in new_fields:
|
||||
setattr(_UserEditForm, f"fields[{field.id}]", StringField(field.name))
|
||||
|
||||
return _UserEditForm(*args, **kwargs)
|
||||
|
||||
|
||||
def UserCreateForm(*args, **kwargs):
|
||||
class _UserCreateForm(UserBaseForm):
|
||||
notify = BooleanField("Email account credentials to user", default=True)
|
||||
|
||||
@property
|
||||
def extra(self):
|
||||
fields = []
|
||||
new_fields = Fields.query.all()
|
||||
|
||||
for field in new_fields:
|
||||
form_field = getattr(self, f"fields[{field.id}]")
|
||||
entry = (field.name, form_field)
|
||||
fields.append(entry)
|
||||
return fields
|
||||
|
||||
new_fields = Fields.query.all()
|
||||
for field in new_fields:
|
||||
setattr(_UserCreateForm, f"fields[{field.id}]", StringField(field.name))
|
||||
|
||||
return _UserCreateForm(*args, **kwargs)
|
||||
|
||||
@@ -15,7 +15,7 @@ class FieldEntriesSchema(ma.ModelSchema):
|
||||
class Meta:
|
||||
model = FieldEntries
|
||||
include_fk = True
|
||||
load_only = ("id", )
|
||||
load_only = ("id",)
|
||||
exclude = ("field", "user", "user_id")
|
||||
dump_only = ("user_id", "name", "description", "type")
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from marshmallow import ValidationError, pre_load, pre_dump, validate
|
||||
from marshmallow import ValidationError, post_dump, pre_load, validate
|
||||
from marshmallow.fields import Nested
|
||||
from marshmallow_sqlalchemy import field_for
|
||||
|
||||
from CTFd.models import Fields, FieldEntries, Users, ma, db
|
||||
from CTFd.models import FieldEntries, Fields, Users, ma
|
||||
from CTFd.schemas.fields import FieldEntriesSchema
|
||||
from CTFd.utils import get_config, string_types
|
||||
from CTFd.utils.crypto import verify_password
|
||||
@@ -51,7 +51,7 @@ class UserSchema(ma.ModelSchema):
|
||||
)
|
||||
country = field_for(Users, "country", validate=[validate_country_code])
|
||||
password = field_for(Users, "password")
|
||||
fields = Nested(FieldEntriesSchema(), partial=True, many=True)
|
||||
fields = Nested(FieldEntriesSchema, partial=True, many=True)
|
||||
|
||||
@pre_load
|
||||
def validate_name(self, data):
|
||||
@@ -193,7 +193,25 @@ class UserSchema(ma.ModelSchema):
|
||||
fields = data.get("fields")
|
||||
|
||||
if is_admin():
|
||||
pass
|
||||
user_id = data.get("id")
|
||||
if user_id:
|
||||
target_user = Users.query.filter_by(id=data["id"]).first()
|
||||
for f in fields:
|
||||
f.pop("id", None)
|
||||
field_id = f.get("field_id")
|
||||
|
||||
# # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce
|
||||
field = Fields.query.filter_by(id=field_id).first_or_404()
|
||||
|
||||
# Get the existing field entry if one exists
|
||||
entry = FieldEntries.query.filter_by(
|
||||
field_id=field.id, user_id=target_user.id
|
||||
).first()
|
||||
if entry:
|
||||
f["id"] = entry.id
|
||||
else:
|
||||
# Marshmallow automatically links the fields to newly created users
|
||||
pass
|
||||
else:
|
||||
for f in fields:
|
||||
# Remove any existing set
|
||||
@@ -201,31 +219,44 @@ class UserSchema(ma.ModelSchema):
|
||||
field_id = f.get("field_id")
|
||||
|
||||
# # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce
|
||||
# field = Fields.query.filter_by(id=field_id).first_or_404()
|
||||
field = Fields.query.filter_by(id=field_id).first_or_404()
|
||||
|
||||
# Get the existing field entry if one exists
|
||||
entry = FieldEntries.query.filter_by(field_id=field.id, user_id=current_user.id).first()
|
||||
entry = FieldEntries.query.filter_by(
|
||||
field_id=field.id, user_id=current_user.id
|
||||
).first()
|
||||
if entry:
|
||||
f["id"] = entry.id
|
||||
|
||||
@pre_dump
|
||||
def process_fields(self, obj):
|
||||
@post_dump()
|
||||
def process_fields(self, data):
|
||||
"""
|
||||
Handle permissions levels for fields.
|
||||
This is post_dump to manipulate JSON instead of the raw db object
|
||||
|
||||
Admins can see all fields.
|
||||
Users (self) can see their edittable and public fields
|
||||
Public (user) can only see public fields
|
||||
"""
|
||||
# Make the object detatched so that changes don't accidentally persist
|
||||
db.session.expunge(obj)
|
||||
for i, entry in enumerate(obj.fields):
|
||||
# Gather all possible fields
|
||||
removed_field_ids = []
|
||||
fields = Fields.query.all()
|
||||
|
||||
# Select fields for removal based on current view and properties of the field
|
||||
for field in fields:
|
||||
if self.view == "user":
|
||||
if entry.field.public is False:
|
||||
del obj.fields[i]
|
||||
if field.public is False:
|
||||
removed_field_ids.append(field.id)
|
||||
elif self.view == "self":
|
||||
if entry.field.editable is False and entry.field.public is False:
|
||||
del obj.fields[i]
|
||||
if field.editable is False and field.public is False:
|
||||
removed_field_ids.append(field.id)
|
||||
|
||||
# Rebuild fuilds
|
||||
fields = data.get("fields")
|
||||
if fields:
|
||||
data["fields"] = [
|
||||
field for field in fields if field["field_id"] not in removed_field_ids
|
||||
]
|
||||
|
||||
views = {
|
||||
"user": [
|
||||
|
||||
@@ -11,6 +11,19 @@ function createUser(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#user-info-create-form").serializeJSON(true);
|
||||
|
||||
params.fields = [];
|
||||
|
||||
for (const property in params) {
|
||||
if (property.match(/fields\[\d+\]/)) {
|
||||
let field = {};
|
||||
let id = parseInt(property.slice(7, -1));
|
||||
field["field_id"] = id;
|
||||
field["value"] = params[property];
|
||||
params.fields.push(field);
|
||||
delete params[property];
|
||||
}
|
||||
}
|
||||
|
||||
// Move the notify value into a GET param
|
||||
let url = "/api/v1/users";
|
||||
let notify = params.notify;
|
||||
@@ -57,6 +70,19 @@ function updateUser(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#user-info-edit-form").serializeJSON(true);
|
||||
|
||||
params.fields = [];
|
||||
|
||||
for (const property in params) {
|
||||
if (property.match(/fields\[\d+\]/)) {
|
||||
let field = {};
|
||||
let id = parseInt(property.slice(7, -1));
|
||||
field["field_id"] = id;
|
||||
field["value"] = params[property];
|
||||
params.fields.push(field);
|
||||
delete params[property];
|
||||
}
|
||||
}
|
||||
|
||||
CTFd.fetch("/api/v1/users/" + window.USER_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,6 +24,14 @@
|
||||
{{ form.country.label }}
|
||||
{{ form.country(class="form-control custom-select") }}
|
||||
</div>
|
||||
|
||||
{% for k, v in form.extra %}
|
||||
<div class="form-group">
|
||||
<b>{{ v.label }}</b>
|
||||
{{ v(class="form-control") }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check form-check-inline">
|
||||
{{ form.type(class="form-control form-inline custom-select", id="type-select") }}
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
{{ form.country.label }}
|
||||
{{ form.country(class="form-control custom-select") }}
|
||||
</div>
|
||||
|
||||
{% for k, v in form.extra %}
|
||||
<div class="form-group">
|
||||
<b>{{ v.label }}</b>
|
||||
{{ v(class="form-control") }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check form-check-inline">
|
||||
{{ form.type(class="form-control form-inline custom-select", id="type-select") }}
|
||||
|
||||
Reference in New Issue
Block a user