Add validation to package API

This commit is contained in:
rubenwardy 2021-02-02 22:34:51 +00:00
parent 551996ca14
commit ca58c70206
5 changed files with 116 additions and 33 deletions

@ -227,8 +227,6 @@ class PackageForm(FlaskForm):
media_license = QuerySelectField("Media License", [DataRequired()], allow_blank=True, query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name)
tags = QuerySelectMultipleField('Tags', query_factory=lambda: Tag.query.order_by(db.asc(Tag.name)), get_pk=lambda a: a.id, get_label=makeLabel)
content_warnings = QuerySelectMultipleField('Content Warnings', query_factory=lambda: ContentWarning.query.order_by(db.asc(ContentWarning.name)), get_pk=lambda a: a.id, get_label=makeLabel)
# harddep_str = StringField("Hard Dependencies", [Optional()])
# softdep_str = StringField("Soft Dependencies", [Optional()])
repo = StringField("VCS Repository URL", [Optional(), URL()], filters = [lambda x: x or None])
website = StringField("Website URL", [Optional(), URL()], filters = [lambda x: x or None])
issueTracker = StringField("Issue Tracker URL", [Optional(), URL()], filters = [lambda x: x or None])

@ -24,7 +24,7 @@ Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
* PUT `/api/packages/<author>/<name>/` (Update)
* JSON dictionary with any of these keys (all are optional):
* `title`: Human-readable title.
* `short_desc`
* `short_description`
* `desc`
* `type`: One of `GAME`, `MOD`, `TXP`.
* `license`: A license name.

@ -15,24 +15,96 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import re, validators
from app.logic.LogicError import LogicError
from app.models import User, Package, PackageType, MetaPackage, Tag, ContentWarning, db, Permission, NotificationType, AuditSeverity
from app.models import User, Package, PackageType, MetaPackage, Tag, ContentWarning, db, Permission, NotificationType, AuditSeverity, License
from app.utils import addNotification, addAuditLog
def check(cond: bool, msg: str):
if not cond:
raise LogicError(400, msg)
def get_license(name):
if type(name) == License:
return name
license = License.query.filter(License.name.ilike(name)).first()
if license is None:
raise LogicError(400, "Unknown license: " + name)
return license
name_re = re.compile("^[a-z0-9_]+$")
TYPES = {
"name": str,
"title": str,
"short_description": str,
"short_desc": str,
"desc": str,
"tags": list,
"content_warnings": list,
"repo": str,
"website": str,
"issue_tracker": str,
"issueTracker": str,
"forums": int,
}
def is_int(val):
try:
int(val)
return True
except ValueError:
return False
def validate(data: dict):
for key, typ in TYPES.items():
if data.get(key) is not None:
check(isinstance(data[key], typ), key + " must be a " + typ.__name__)
if "name" in data:
name = data["name"]
check(isinstance(name, str), "Name must be a string")
check(bool(name_re.match(name)),
"Name can only contain lower case letters (a-z), digits (0-9), and underscores (_)")
for key in ["repo", "website", "issue_tracker", "issueTracker"]:
value = data.get(key)
if value is not None:
check(value.startswith("http://") or value.startswith("https://"),
key + " must start with http:// or https://")
check(validators.url(value, public=True), key + " must be a valid URL")
def do_edit_package(user: User, package: Package, was_new: bool, data: dict, reason: str = None):
if not package.checkPerm(user, Permission.EDIT_PACKAGE):
raise LogicError(403, "You do not have permission to edit this package")
if "name" in data and package.name != data["name"] and \
not package.checkPerm(user, Permission.CHANGE_NAME):
raise LogicError(403, "You do not have permission to change the package name")
if not package.checkPerm(user, Permission.EDIT_PACKAGE):
raise LogicError(403, "You do not have permission to edit this package")
for alias, to in { "short_description": "short_desc" }.items():
for alias, to in { "short_description": "short_desc", "issue_tracker": "issueTracker" }.items():
if alias in data:
data[to] = data[alias]
validate(data)
if "type" in data:
data["type"] = PackageType.coerce(data["type"])
if "license" in data:
data["license"] = get_license(data["license"])
if "media_license" in data:
data["media_license"] = get_license(data["media_license"])
for key in ["name", "title", "short_desc", "desc", "type", "license", "media_license",
"repo", "website", "issueTracker", "forums"]:
if key in data:
@ -45,16 +117,27 @@ def do_edit_package(user: User, package: Package, was_new: bool, data: dict, rea
m = MetaPackage.GetOrCreate(package.name, {})
package.provides.append(m)
if "tags" in data:
package.tags.clear()
if "tag" in data:
for tag in data["tag"]:
package.tags.append(Tag.query.get(tag))
for tag_id in data["tags"]:
if is_int(tag_id):
package.tags.append(Tag.query.get(tag_id))
else:
tag = Tag.query.filter_by(name=tag_id).first()
if tag is None:
raise LogicError(400, "Unknown tag: " + tag_id)
package.tags.append(tag)
if "content_warnings" in data:
package.content_warnings.clear()
for warning in data["content_warnings"]:
package.content_warnings.append(ContentWarning.query.get(warning))
for warning_id in data["content_warnings"]:
if is_int(warning_id):
package.content_warnings.append(ContentWarning.query.get(warning_id))
else:
warning = ContentWarning.query.filter_by(name=warning_id).first()
if warning is None:
raise LogicError(400, "Unknown warning: " + warning_id)
package.content_warnings.append(warning)
if not was_new:
if reason is None:

@ -17,7 +17,6 @@
import datetime
import enum
from urllib.parse import urlparse
from flask import url_for
from flask_sqlalchemy import BaseQuery

@ -1,4 +1,4 @@
Flask
Flask~=1.1.2
Flask-FlatPages
Flask-Gravatar
Flask-Login
@ -12,24 +12,24 @@ GitHub-Flask
SQLAlchemy-Searchable
bcrypt
markdown
bleach
passlib
markdown~=3.2.2
bleach~=3.1.5
passlib~=1.7.2
pygments
beautifulsoup4
celery
kombu
beautifulsoup4~=4.9.1
celery~=4.4.6
kombu~=4.6.11
GitPython
git-archive-all
lxml
pillow
pillow~=7.2.0
pyScss
redis
redis~=3.5.3
psycopg2
pytest
pytest~=5.4.3
pytest-cov
email_validator
@ -38,8 +38,11 @@ pyyaml
ua-parser
user-agents
Werkzeug
WTForms
SQLAlchemy
requests
alembic
Werkzeug~=1.0.1
WTForms~=2.3.1
SQLAlchemy~=1.3.18
requests~=2.24.0
alembic~=1.4.2
validators~=0.16.0
gitdb~=4.0.5