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

β → 🜚🏭 ❻ #472

Merged
merged 85 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
c5350ff
Move logging.ini to logging.ini.example
DasSkelett Oct 22, 2021
e6ff44d
Copy logging.ini.example before running pytest
DasSkelett Oct 22, 2021
562b275
Add logging.ini to .gitignore
DasSkelett Oct 22, 2021
da4ff97
Merge pull request #411 from DasSkelett/fix/loggingini
DasSkelett Oct 22, 2021
25ce10a
Show a warning comment for PRs targeting the wrong base branch
DasSkelett Oct 23, 2021
368e001
Update CONTRIBUTING.md and link to the wiki
DasSkelett Oct 23, 2021
af337da
git add remote upstream
HebaruSan Oct 23, 2021
59e5117
git diff --staged
HebaruSan Oct 23, 2021
0755ae0
Merge pull request #412 from DasSkelett/feature/branch-warning
HebaruSan Oct 23, 2021
f086be5
Quoted and negated search terms, up front search help
HebaruSan Nov 13, 2021
380bf52
Downloads: Don't count from bots, send HTML to Discord
HebaruSan Nov 29, 2021
79fb0da
Merge pull request #436 from HebaruSan/fix/bot-downloads
HebaruSan Dec 1, 2021
f4611b6
Escape user-edited fields in OpenGraph meta tags
HebaruSan Oct 28, 2021
7cd6259
Bump cached-path-relative from 1.0.2 to 1.1.0 in /frontend
dependabot[bot] Jan 27, 2022
36d0336
Suppress mypy error
HebaruSan Jan 28, 2022
28564bd
Merge pull request #439 from KSP-SpaceDock/dependabot/npm_and_yarn/fr…
HebaruSan Jan 28, 2022
d6dc2d2
Bump node-sass from 6.0.1 to 7.0.0 in /frontend
dependabot[bot] Feb 11, 2022
4e73161
Merge pull request #440 from KSP-SpaceDock/dependabot/npm_and_yarn/fr…
HebaruSan Mar 10, 2022
d2485b0
Upgrade to Flask 2
HebaruSan Mar 12, 2022
b632cb8
Merge pull request #434 from HebaruSan/feature/flask-2
DasSkelett Mar 12, 2022
8fc4ecf
Batch retrieval of download counts with /api/download_counts
HebaruSan Oct 13, 2021
082ed46
Merge pull request #401 from HebaruSan/feature/api-download-counts
HebaruSan Mar 12, 2022
9a3314b
Merge pull request #415 from HebaruSan/fix/og-escaping
DasSkelett Mar 13, 2022
edf126f
Scan uploads with pyclamd
HebaruSan Nov 11, 2021
cf96ea6
Bump minimist from 1.2.0 to 1.2.6 in /frontend
dependabot[bot] Mar 30, 2022
7e01461
Merge pull request #443 from KSP-SpaceDock/dependabot/npm_and_yarn/fr…
HebaruSan Apr 1, 2022
17b5a7a
Fix curl commands in API doc
HebaruSan Apr 18, 2022
706b0c8
Merge pull request #446 from HebaruSan/fix/api-doc-curl
HebaruSan Apr 18, 2022
82015c1
Make ./start-server.sh work on Windows
HebaruSan Apr 24, 2022
4f5c5c6
Merge pull request #448 from HebaruSan/fix/docker-win
HebaruSan Apr 24, 2022
52ed368
Bump moment from 2.29.1 to 2.29.2 in /frontend
dependabot[bot] Apr 9, 2022
9f43388
Merge pull request #444 from KSP-SpaceDock/dependabot/npm_and_yarn/fr…
HebaruSan Apr 24, 2022
399580a
Update Dropzone, display upload errors correctly
HebaruSan Apr 25, 2022
2598ede
Update checkout action to v3
HebaruSan May 8, 2022
ce73249
Merge pull request #449 from HebaruSan/fix/dz-errs-timeout
HebaruSan May 10, 2022
4d873ef
Merge pull request #450 from KSP-SpaceDock/alpha
HebaruSan Jun 1, 2022
d89e449
Bump shell-quote from 1.7.2 to 1.7.3 in /frontend
dependabot[bot] Jun 23, 2022
93871a8
Merge pull request #453 from KSP-SpaceDock/dependabot/npm_and_yarn/fr…
HebaruSan Jul 1, 2022
c27f273
Bump moment from 2.29.1 to 2.29.4 in /frontend (#454)
dependabot[bot] Jul 8, 2022
bacac71
Bump moment from 2.29.1 to 2.29.4 in /frontend (#454)
dependabot[bot] Jul 8, 2022
3f1f580
Use mod version entered by user unaltered
HebaruSan Jul 8, 2022
e7a469c
α → β ⑫ (#456)
HebaruSan Aug 31, 2022
c44ca3e
Bump json-schema from 0.2.3 to 0.4.0 in /frontend (#457)
dependabot[bot] Sep 2, 2022
21d1b4e
Modifieed piwik code to reflect switch to matomo
V1TA5 Sep 4, 2022
edc33d8
Modified tracking js code to reflect switch from wiwik to matomo
V1TA5 Sep 4, 2022
de38106
Bump minimatch from 3.0.4 to 3.0.8 in /frontend (#458)
dependabot[bot] Nov 25, 2022
9cf0dcd
Merge pull request #455 from HebaruSan/fix/version-secure
V1TA5 Feb 7, 2023
41a124c
Merge pull request #432 from HebaruSan/feature/search-quotes-and-nega…
V1TA5 Feb 7, 2023
80edb5f
Merge pull request #429 from HebaruSan/feature/antivirus
V1TA5 Feb 7, 2023
871c4a0
Add game ID filter
RedstoneWizard08 Mar 2, 2023
f2d9d6b
Add for /api/browse/new
RedstoneWizard08 Mar 2, 2023
828ba54
Add version filter
RedstoneWizard08 Mar 2, 2023
c8635fe
Fix query not being affected by order
RedstoneWizard08 Mar 2, 2023
766a749
Merge pull request #466 from RedstoneWizard08/feature/search-filters
V1TA5 Mar 2, 2023
1b51757
Add browse filters to api doc, filter by friendly game version (#467)
HebaruSan Mar 2, 2023
b274d20
Fix game_version_id filter
HebaruSan Mar 2, 2023
e81be0e
Check user profile field lengths
HebaruSan Mar 3, 2023
6656065
Merge pull request #470 from HebaruSan/fix/profile-desc-len
V1TA5 Mar 3, 2023
48aaf6b
Clean up login/register layout (#421)
HebaruSan Mar 3, 2023
9b8eafe
Admin pagination usability fixes (#423)
HebaruSan Mar 3, 2023
9bf0d35
Co-authorship fixes (#425)
HebaruSan Mar 3, 2023
94820ce
Widescreen layout for mod page (#426)
HebaruSan Mar 3, 2023
51cfdfa
Fix mobile hamburger menu and search (#427)
HebaruSan Mar 3, 2023
c537073
Paginated search buttons (#431)
HebaruSan Mar 3, 2023
8c46489
Send download size, co-authors, and authors' GitHub and forum names t…
HebaruSan Mar 3, 2023
7a5bd33
Parse @username links and colon emoji codes in markdown (#441)
HebaruSan Mar 3, 2023
861c4ce
API route for auto-generated remote version files (#451)
HebaruSan Mar 3, 2023
538e016
Allow mod authors to replace downloads (#428)
HebaruSan Mar 3, 2023
5c7219b
Bump scss-tokenizer and node-sass in /frontend (#462)
dependabot[bot] Mar 3, 2023
179ed0a
Deferred loading and pre-rendering for mod changelogs (#414)
HebaruSan Mar 3, 2023
95fb5fb
Fix edit_version's broken Dropzone
HebaruSan Mar 3, 2023
b529f43
Add missing quotes
HebaruSan Mar 3, 2023
5c25b69
Fix downloads performance (#416)
HebaruSan Mar 3, 2023
a36e0c0
Fix modpack backgrounds and thumbnails (#422)
HebaruSan Mar 3, 2023
dcb9168
Add Locked Mods tab to admin pages (#419)
HebaruSan Mar 3, 2023
65ad7c2
Non-public draft blog posts (#418)
HebaruSan Mar 3, 2023
3d48ded
HTML emails (#400)
HebaruSan Mar 3, 2023
356a4a7
Load editor before mods script
HebaruSan Mar 3, 2023
9dd101e
Option to profile everything, save data only for slow responses (#430)
HebaruSan Mar 3, 2023
04bc51c
α → β ⑬ (#471)
HebaruSan Mar 3, 2023
e0c6598
Fix SCSS syntax
HebaruSan Mar 4, 2023
7124859
Fix SCSS syntax
HebaruSan Mar 4, 2023
12e007b
Merge branch 'master' into beta
HebaruSan Mar 4, 2023
3b7c159
Only access ModVersion.changelog if needed
HebaruSan Mar 4, 2023
64c46e7
Only access ModVersion.changelog if needed
HebaruSan Mar 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions KerbalStuff/antivirus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import os
from pathlib import Path
from shutil import move
from typing import Optional

import pyclamd

from .config import _cfg, _cfgi, site_logger
from .objects import User
from .email import send_mod_locked
from .ckan import notify_ckan

clam_daemon = None


def file_contains_malware(where: str) -> bool:
global clam_daemon
try:
if not clam_daemon:
clam_daemon = pyclamd.ClamdNetworkSocket(host=_cfg('clamav-host'), port=_cfgi('clamav-port', 3310))
result = clam_daemon.scan_file(where)
if result:
site_logger.error(f'ClamAV says {where} contains malware')
return True
except Exception as exc:
# No ClamAV daemon found, log it and let the file through
site_logger.error(f'Failed to connect to ClamAV, skipping scan of {where}', exc_info=exc)
return False


def quarantine_malware(path: str) -> None:
quarantine_folder = _cfg('clamav-quarantine-path')
if quarantine_folder:
move(path, Path(quarantine_folder) / Path(path).name)
else:
os.remove(path)


def punish_malware(user: User) -> None:
# Lock all of this author's mods
for other_mod in user.mods:
if not other_mod.locked:
other_mod.locked = True
other_mod.published = False
other_mod.locked_by = None
other_mod.lock_reason = 'Malware detected in upload'
send_mod_locked(other_mod, user)
notify_ckan(other_mod, 'locked', True)
40 changes: 22 additions & 18 deletions KerbalStuff/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import subprocess
import xml.etree.ElementTree as ET
from time import strftime
from datetime import timedelta
from typing import Tuple, List, Dict, Any, Optional, Union
from pathlib import Path

Expand All @@ -14,16 +15,16 @@
from flask import Flask, render_template, g, url_for, Response, request
from flask_login import LoginManager, current_user
from flaskext.markdown import Markdown
from sqlalchemy import desc
from werkzeug.exceptions import HTTPException, InternalServerError, NotFound
from flask.typing import ResponseReturnValue
from jinja2 import ChainableUndefined
from pymdownx.emoji import gemoji, to_alt

from .blueprints.accounts import accounts
from .blueprints.admin import admin
from .blueprints.anonymous import anonymous
from .blueprints.api import api
from .blueprints.blog import blog
from .blueprints.blog import blog, get_all_announcement_posts, get_non_member_announcement_posts
from .blueprints.lists import lists
from .blueprints.login_oauth import list_defined_oauths, login_oauth
from .blueprints.mods import mods
Expand Down Expand Up @@ -60,16 +61,29 @@
app.secret_key = _cfg("secret-key")
app.json_encoder = CustomJSONEncoder
app.session_interface = OnlyLoggedInSessionInterface()
Markdown(app, extensions=[KerbDown(), 'fenced_code'])
Markdown(app, extensions=[KerbDown(), 'fenced_code', 'pymdownx.emoji'],
extension_configs={'pymdownx.emoji': {
# GitHub's emojis
'emoji_index': gemoji,
# Unicode output
'emoji_generator': to_alt
}})
login_manager = LoginManager(app)

prof_dir = _cfg('profile-dir')
if prof_dir:
from .middleware.profiler import ConditionalProfilerMiddleware
from .profiling import sampling_function
Path(prof_dir).mkdir(parents=True, exist_ok=True)
app.wsgi_app = ConditionalProfilerMiddleware( # type: ignore[assignment]
app.wsgi_app, stream=None, profile_dir=prof_dir, sampling_function=sampling_function)
log_if_longer = _cfg('profile-threshold-ms')
if log_if_longer:
from .middleware.profiler import CherrypickingProfilerMiddleware
Path(prof_dir).mkdir(parents=True, exist_ok=True)
app.wsgi_app = CherrypickingProfilerMiddleware( # type: ignore[assignment]
app.wsgi_app, stream=None, profile_dir=prof_dir, log_if_longer=timedelta(milliseconds=int(log_if_longer)))
else:
from .middleware.profiler import ConditionalProfilerMiddleware
from .profiling import sampling_function
Path(prof_dir).mkdir(parents=True, exist_ok=True)
app.wsgi_app = ConditionalProfilerMiddleware( # type: ignore[assignment]
app.wsgi_app, stream=None, profile_dir=prof_dir, sampling_function=sampling_function)


@login_manager.user_loader
Expand Down Expand Up @@ -321,13 +335,3 @@ def inject() -> Dict[str, Any]:
'donation_header_link': _cfgb('donation-header-link') if not dismissed_donation else False,
'registration': _cfgb('registration')
}


def get_all_announcement_posts() -> List[BlogPost]:
return BlogPost.query.filter(BlogPost.announcement).order_by(desc(BlogPost.created)).all()


def get_non_member_announcement_posts() -> List[BlogPost]:
return BlogPost.query.filter(
BlogPost.announcement, BlogPost.members_only != True
).order_by(desc(BlogPost.created)).all()
40 changes: 29 additions & 11 deletions KerbalStuff/blueprints/admin.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import math
from typing import Union, List, Tuple, Dict, Any
from typing import Union, List, Tuple, Dict, Any, Optional
import datetime
from datetime import timezone
from pathlib import Path
from subprocess import run, PIPE

from flask import Blueprint, render_template, redirect, request, abort, url_for
from flask_login import login_user, current_user
from sqlalchemy import desc, or_, func
from sqlalchemy import or_, func
from sqlalchemy.orm import Query
import werkzeug.wrappers

Expand All @@ -19,6 +19,7 @@

admin = Blueprint('admin', __name__)
ITEMS_PER_PAGE = 10
MODS_PER_PAGE = 30


@admin.route("/admin")
Expand Down Expand Up @@ -121,7 +122,7 @@ def users(page: int) -> Union[str, werkzeug.wrappers.Response]:
users = search_users(query.lower()) if query else User.query
if not show_non_public:
users = users.filter(User.public)
users = users.order_by(desc(User.created))
users = users.order_by(User.created.desc())
user_count = users.count()
# We can limit here because SqlAlchemy executes queries lazily.
users = users.offset((page - 1) * ITEMS_PER_PAGE).limit(ITEMS_PER_PAGE)
Expand All @@ -135,6 +136,23 @@ def users(page: int) -> Union[str, werkzeug.wrappers.Response]:
query=query, show_non_public=show_non_public)


@admin.route("/admin/locked_mods/<int:page>")
@adminrequired
def locked_mods(page: int) -> Union[str, werkzeug.wrappers.Response]:
if page < 1:
return redirect(url_for('admin.locked_mods', page=1, **request.args))
locked_mods = Mod.query.filter(Mod.locked == True)\
.order_by(Mod.updated.desc())
locked_mods_count = locked_mods.count()
total_pages = max(1, math.ceil(locked_mods_count / MODS_PER_PAGE))
if page > total_pages:
return redirect(url_for('admin.locked_mods', page=total_pages, **request.args))
return render_template("admin-locked-mods.html",
page=page, total_pages=total_pages,
locked_mods=locked_mods.offset((page - 1) * MODS_PER_PAGE)\
.limit(MODS_PER_PAGE))


@admin.route("/admin/blog")
@adminrequired
def blog() -> str:
Expand All @@ -143,15 +161,15 @@ def blog() -> str:

@admin.route("/admin/publishers/<int:page>")
@adminrequired
def publishers(page: int, error: str = None) -> Union[str, werkzeug.wrappers.Response]:
def publishers(page: int, error: Optional[str] = None) -> Union[str, werkzeug.wrappers.Response]:
if page < 1:
return redirect(url_for('admin.publishers', page=1, **request.args))
show_none_active = (request.args.get('show_none_active', '').lower() in TRUE_STR)
query = request.args.get('query', type=str)
publishers = search_publishers(query.lower()) if query else Publisher.query
if not show_none_active:
publishers = publishers.join(Publisher.games).filter(Game.active).distinct(Publisher.id)
publishers = publishers.order_by(desc(Publisher.id))
publishers = publishers.order_by(Publisher.id.desc())
publisher_count = publishers.count()
publishers = publishers.offset((page - 1) * ITEMS_PER_PAGE).limit(ITEMS_PER_PAGE)

Expand All @@ -167,23 +185,23 @@ def publishers(page: int, error: str = None) -> Union[str, werkzeug.wrappers.Res

@admin.route("/admin/games/<int:page>")
@adminrequired
def games(page: int, error: str = None) -> Union[str, werkzeug.wrappers.Response]:
def games(page: int, error: Optional[str] = None) -> Union[str, werkzeug.wrappers.Response]:
if page < 1:
return redirect(url_for('admin.games', page=1, **request.args))
show_inactive = (request.args.get('show_inactive', '').lower() in TRUE_STR)
query = request.args.get('query', type=str)
games = search_games(query.lower()) if query else Game.query
if not show_inactive:
games = games.filter(Game.active)
games = games.order_by(desc(Game.id))
games = games.order_by(Game.id.desc())
game_count = games.count()
games = games.offset((page - 1) * ITEMS_PER_PAGE).limit(ITEMS_PER_PAGE)

total_pages = max(1, math.ceil(game_count / ITEMS_PER_PAGE))
if page > total_pages:
return redirect(url_for('admin.games', page=total_pages, **request.args))

publishers = Publisher.query.order_by(desc(Publisher.id))
publishers = Publisher.query.order_by(Publisher.id.desc())

return render_template('admin-games.html',
games=games, publishers=publishers, game_count=game_count,
Expand All @@ -194,23 +212,23 @@ def games(page: int, error: str = None) -> Union[str, werkzeug.wrappers.Response

@admin.route("/admin/gameversions/<int:page>")
@adminrequired
def game_versions(page: int, error: str = None) -> Union[str, werkzeug.wrappers.Response]:
def game_versions(page: int, error: Optional[str] = None) -> Union[str, werkzeug.wrappers.Response]:
if page < 1:
return redirect(url_for('admin.game_versions', page=1, **request.args))
show_inactive = (request.args.get('show_inactive', '').lower() in TRUE_STR)
query = request.args.get('query', type=str)
game_versions = search_game_versions(query.lower()) if query else GameVersion.query
if not show_inactive:
game_versions = game_versions.join(GameVersion.game).filter(Game.active)
game_versions = game_versions.order_by(desc(GameVersion.id))
game_versions = game_versions.order_by(GameVersion.id.desc())
game_version_count = game_versions.count()
game_versions = game_versions.offset((page - 1) * ITEMS_PER_PAGE).limit(ITEMS_PER_PAGE)

total_pages = max(1, math.ceil(game_version_count / ITEMS_PER_PAGE))
if page > total_pages:
return redirect(url_for('admin.game_versions', page=total_pages, **request.args))

games = Game.query.order_by(desc(Game.id))
games = Game.query.order_by(Game.id.desc())

return render_template('admin-game-versions.html',
game_versions=game_versions, games=games,
Expand Down
13 changes: 6 additions & 7 deletions KerbalStuff/blueprints/anonymous.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import werkzeug.wrappers
from flask import Blueprint, render_template, abort, request, Response, make_response, send_file
from flask_login import current_user
from sqlalchemy import desc
from datetime import timezone

from ..common import dumb_object, paginate_query, get_paginated_mods, get_game_info, get_games, \
Expand Down Expand Up @@ -61,7 +60,7 @@ def browse() -> str:

@anonymous.route("/browse/new")
def browse_new() -> str:
mods = Mod.query.filter(Mod.published).order_by(desc(Mod.created))
mods = Mod.query.filter(Mod.published).order_by(Mod.created.desc())
mods, page, total_pages = paginate_query(mods)
return render_template("browse-list.html", mods=mods, page=page, total_pages=total_pages,
url="/browse/new", name="Newest Mods", rss="/browse/new.rss")
Expand All @@ -80,7 +79,7 @@ def browse_new_rss() -> Response:

@anonymous.route("/browse/updated")
def browse_updated() -> str:
mods = Mod.query.filter(Mod.published, Mod.versions.any(ModVersion.id != Mod.default_version_id)).order_by(desc(Mod.updated))
mods = Mod.query.filter(Mod.published, Mod.versions.any(ModVersion.id != Mod.default_version_id)).order_by(Mod.updated.desc())
mods, page, total_pages = paginate_query(mods)
return render_template("browse-list.html", mods=mods, page=page, total_pages=total_pages,
url="/browse/updated", name="Recently Updated Mods", rss="/browse/updated.rss")
Expand All @@ -107,7 +106,7 @@ def browse_top() -> str:

@anonymous.route("/browse/featured")
def browse_featured() -> str:
mods = Featured.query.order_by(desc(Featured.created))
mods = Featured.query.order_by(Featured.created.desc())
mods, page, total_pages = paginate_query(mods)
mods = [f.mod for f in mods]
return render_template("browse-list.html", mods=mods, page=page, total_pages=total_pages,
Expand Down Expand Up @@ -150,7 +149,7 @@ def singlegame_browse(gameshort: str) -> str:
@anonymous.route("/<gameshort>/browse/new")
def singlegame_browse_new(gameshort: str) -> str:
ga = get_game_info(short=gameshort)
mods = Mod.query.filter(Mod.published, Mod.game_id == ga.id).order_by(desc(Mod.created))
mods = Mod.query.filter(Mod.published, Mod.game_id == ga.id).order_by(Mod.created.desc())
mods, page, total_pages = paginate_query(mods)
return render_template("browse-list.html", mods=mods, page=page, total_pages=total_pages, ga=ga,
url="/browse/new", name="Newest Mods", rss="/browse/new.rss")
Expand All @@ -172,7 +171,7 @@ def singlegame_browse_new_rss(gameshort: str) -> Response:
@anonymous.route("/<gameshort>/browse/updated")
def singlegame_browse_updated(gameshort: str) -> str:
ga = get_game_info(short=gameshort)
mods = Mod.query.filter(Mod.published, Mod.game_id == ga.id, Mod.versions.any(ModVersion.id != Mod.default_version_id)).order_by(desc(Mod.updated))
mods = Mod.query.filter(Mod.published, Mod.game_id == ga.id, Mod.versions.any(ModVersion.id != Mod.default_version_id)).order_by(Mod.updated.desc())
mods, page, total_pages = paginate_query(mods)
return render_template("browse-list.html", mods=mods, page=page, total_pages=total_pages, ga=ga,
url="/browse/updated", name="Recently Updated Mods", rss="/browse/updated.rss")
Expand Down Expand Up @@ -204,7 +203,7 @@ def singlegame_browse_top(gameshort: str) -> str:
def singlegame_browse_featured(gameshort: str) -> str:
ga = get_game_info(short=gameshort)
mods = Featured.query.outerjoin(Mod).filter(
Mod.game_id == ga.id).order_by(desc(Featured.created))
Mod.game_id == ga.id).order_by(Featured.created.desc())
mods, page, total_pages = paginate_query(mods)
mods = [f.mod for f in mods]
return render_template("browse-list.html", mods=mods, page=page, total_pages=total_pages, ga=ga,
Expand Down
Loading