Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
4700204
local-configs
willyao99 Oct 4, 2023
daa6557
fix broken pwd validation
willyao99 Oct 4, 2023
ccedde0
register step 2 template
willyao99 Oct 12, 2023
fa5399d
registration-wip
willyao99 Oct 17, 2023
5685bdd
fix-register-templates
willyao99 Oct 17, 2023
860c929
reg-flow-operational
willyao99 Oct 17, 2023
a8b505a
reg-redirect
willyao99 Oct 17, 2023
d362b21
tapir navbar wip
willyao99 Oct 18, 2023
27bf236
tapir styles and tapir landing wip
willyao99 Oct 18, 2023
da927e8
approved blocked list tooltip et al
willyao99 Dec 6, 2023
81ce5de
user-profile-etc
willyao99 Jan 12, 2024
25250d2
mod-model-fix
willyao99 Jan 24, 2024
d9834ed
moderators-table
willyao99 Jan 24, 2024
4b02e93
clean
willyao99 Jan 24, 2024
1eb62b9
audit log and mods features
willyao99 Feb 6, 2024
8c1baad
endorsement controller
willyao99 Feb 6, 2024
a33d0a1
endorsement wip
willyao99 Feb 9, 2024
f50b043
email-template-mgmt-wip
willyao99 Feb 21, 2024
594ee0f
search route and more email temps
willyao99 Feb 22, 2024
69d060e
search wip
willyao99 Feb 23, 2024
8206087
wip profile
willyao99 Mar 4, 2024
1e639a6
paper detail
willyao99 Mar 6, 2024
9b45552
profile updates
willyao99 Mar 7, 2024
19d1f89
suspects join fix
willyao99 Mar 7, 2024
d4e0096
htmx requests
willyao99 Mar 19, 2024
71705cb
emails-wip
willyao99 Apr 8, 2024
683a73e
tapir sessions wireframing
willyao99 Dec 20, 2023
97b0dce
end html wireframe wip
willyao99 Apr 20, 2024
dd88072
approved and blocked list wip
willyao99 Dec 4, 2023
773ebeb
email templ index link
willyao99 Apr 29, 2024
076c11f
susp link fix
willyao99 May 2, 2024
75d9fc8
index page links
willyao99 May 1, 2024
62fe2e3
flag placeholders wip
willyao99 May 9, 2024
67ce1f0
search function char
willyao99 May 4, 2024
9796891
flask gitignore
willyao99 May 13, 2024
985b1ea
endorsement controllers
willyao99 Apr 15, 2024
af16749
tapir interactive features merge
willyao99 May 13, 2024
03eacdb
endorsement listing feature fix
willyao99 May 13, 2024
2c6afab
landing links
willyao99 May 13, 2024
1d02a7e
redirect links
willyao99 May 13, 2024
64bcb4e
confirm pages wip
willyao99 May 14, 2024
954e6f8
confirm screen wip
willyao99 May 16, 2024
773b75f
Update README.md
willyao99 Jun 12, 2024
7e46f30
Update README.md
willyao99 Jun 12, 2024
1021232
Update README.md
willyao99 Jun 12, 2024
d1db1f6
Update README.md
willyao99 Jun 12, 2024
0f75aed
email temp misc
willyao99 Jun 12, 2024
410a37c
Webapp runs with new base including auth, no arxiv_db, and no flask_s…
mnazzaro Jun 24, 2024
86fc58a
Refactor config, and now pages are actually working
mnazzaro Jun 24, 2024
8ffcff5
A few tests still failing due to db weirdness
mnazzaro Jun 26, 2024
a95e5f3
Just weird domain thing remaining
mnazzaro Jun 27, 2024
d98651b
Switch domain to arxiv.org from .arxiv.org in Domain cookie
mnazzaro Jun 27, 2024
a7b30be
Leaving off for today
mnazzaro Jul 1, 2024
fead199
In cloud run with all routes admin scoped
mnazzaro Jul 3, 2024
bffba2c
One still failing
mnazzaro Jul 8, 2024
a9d6367
Fix @anonymous to add cookies manually
mnazzaro Jul 8, 2024
5dfbdd8
Add some creature comfort for get things going.
ntai-arxiv Jul 8, 2024
4a0a1f0
Add some creature comfort for get things going.
ntai-arxiv Jul 8, 2024
f62b553
Better (and json) logging.
ntai-arxiv Jul 12, 2024
66b05ab
just a doc update
ntai-arxiv Jul 15, 2024
9bf4f26
merge doc
ntai-arxiv Jul 17, 2024
9ff5344
The test password should be string, not bytes.
ntai-arxiv Jul 22, 2024
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
flask_session/

# Scrapy stuff:
.scrapy
Expand Down Expand Up @@ -154,3 +155,4 @@ accounts/deploy/env_values.txt
*.key
*.cer

/.bootstrap
9 changes: 9 additions & 0 deletions .localenv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export BASE_SERVER='localhost.arxiv.org'
export FLASK_APP=admin_webapp/app.py
export CLASSIC_DB_URI=sqlite:///test.db
export DEFAULT_LOGIN_REDIRECT_URL='/protected'
export AUTH_SESSION_COOKIE_DOMAIN='localhost.arxiv.org'
export CLASSIC_COOKIE_NAME='LOCALHOST_DEV_admin_webapp_classic_cookie'
export AUTH_SESSION_COOKIE_SECURE=0
export DEFAULT_LOGOUT_REDIRECT_URL='/login'
export REDIS_FAKE=1
45 changes: 45 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
FROM python:3.11.8-bookworm
RUN apt-get update && apt-get -y upgrade

ENV PYTHONFAULTHANDLER=1 \
PYTHONUNBUFFERED=1 \
PYTHONHASHSEED=random \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100 \
POETRY_VERSION=1.3.2 \
TRACE=1 \
LC_ALL=en_US.utf8 \
LANG=en_US.utf8 \
APP_HOME=/app

WORKDIR /app

RUN apt-get -y install default-libmysqlclient-dev

ENV VIRTUAL_ENV=/opt/venv
RUN python -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
RUN pip install -U pip "poetry==$POETRY_VERSION"

COPY poetry.lock pyproject.toml ./
RUN poetry config virtualenvs.create false && \
poetry install --no-interaction --no-ansi

RUN pip install "gunicorn==20.1.0"

ADD admin_webapp /app/admin_webapp
ADD tests /app/tests

EXPOSE 8080

RUN useradd e-prints
RUN chown e-prints:e-prints /app/tests/data/
RUN chmod 775 /app/tests/data/
USER e-prints

ENV GUNICORN gunicorn --bind :8080 \
--workers 4 --threads 8 --timeout 0 \
"admin_webapp.factory:create_web_app()"

CMD exec $GUNICORN
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
PYTHON := python3.11

default: .bootstrap

venv:
${PYTHON} -m venv venv
. venv/bin/activate && pip install --upgrade pip
. venv/bin/activate && pip install poetry
. venv/bin/activate && poetry install

.bootstrap: venv
touch .bootstrap

test.db: .bootstrap
#. venv/bin/activate && . ./.localenv && poetry run python create_user.py create-user --password boguspassword --username bob --email bogus@example.com --first-name Bob --last-name Bogus --suffix-name '' --affiliation FSU --home-page https://asdf.com
. venv/bin/activate && . ./.localenv && poetry run python create_user.py load-users tests/fixtures/bogus.yaml


run: test.db
script/run_local.sh
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ This repo provides a web app for for admin tools, forms, reports and APIs.

# How to get started
```bash
cd $HOME/arxiv # Or, your desired location
git clone git@github.com:arXiv/admin-webapp.git
cd admin-webapp
pip install poetry
poetry install # installs to a venv
poetry shell # activates the venv
LOCALHOST_DEV=1 \
python create_user.py # fill out a test user
LOCALHOST_DEV=1 \
FLASK_APP=admin_webapp/app.py \
flask run
# git switch tapir-dev
make run
```

Makefile sets up the "venv" and runs script/run_local.sh after bootstrapping the environment.

Then go to http://localhost.arxiv.org:5000/login and log in with the user and pw you just created.

To use with MySQL:
Expand All @@ -30,6 +29,12 @@ create tables. Conventional read/write access should be sufficient.

You should be able to go to a page like http://localhost:5000/login or http://localhost:5000/register

While developing, it's best to open up dev.arxiv.org/admin for legacy Tapir so you can make changes freely. In some cases it can be helpful to open up production Tapir at arxiv.org/admin, but tread carefully so you don't unintentionally modify a user profile. In most cases, however, there isn't too much to worry about.

# Database Schema

Since there are several db schemata running around, this application makes some assumptions. They are a) a Moderators table in associative_tables.py in `arxiv-db` cannot exist and b) in the tapir_users.py file in `arxiv-db` the tapir_nickanames field needs to be one to one which means `useList` needs to be set to `false`.

# Running the tests

After setting up you should be able to run the tests with
Expand Down
2 changes: 1 addition & 1 deletion admin_webapp/admin_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from flask import current_app, request


from arxiv_db.models import TapirAdminAudit
from arxiv.db.models import TapirAdminAudit


Actions = Literal['new-user',
Expand Down
214 changes: 88 additions & 126 deletions admin_webapp/config.py
Original file line number Diff line number Diff line change
@@ -1,136 +1,98 @@
"""Flask configuration."""

import os
from typing import Optional, List, Tuple
import re
from zoneinfo import ZoneInfo
from arxiv.config import Settings as BaseSettings

class Settings (BaseSettings):

BASE_SERVER: str = "arxiv.org"
SERVER_NAME: str = BASE_SERVER

REDIS_HOST: str = 'localhost'
REDIS_PORT: int = 7000
REDIS_DATABASE: str = '0'
REDIS_TOKEN: Optional[str] = None
"""This is the token used in the AUTH procedure."""
REDIS_CLUSTER: bool = True

REDIS_FAKE: bool = False
"""Use the FakeRedis library instead of a redis service.

Useful for testing, dev, beta."""

JWT_SECRET: str = 'foosecret'

DEFAULT_LOGIN_REDIRECT_URL: str = 'https://arxiv.org/user'
DEFAULT_LOGOUT_REDIRECT_URL: str = 'https://arxiv.org'

LOGIN_REDIRECT_REGEX: str = fr'(/.*)|(https://([a-zA-Z0-9\-.])*{re.escape(BASE_SERVER)}/.*)'
"""Regex to check next_page of /login.

Only next_page values that match this regex will be allowed. All
others will go to the DEFAULT_LOGOUT_REDIRECT_URL. The default value
for this allows relative URLs and URLs to subdomains of the
BASE_SERVER.
"""
URLS: List[Tuple[str, str, str]] = [
("lost_password", "/user/lost_password", BASE_SERVER),
("account", "/user", BASE_SERVER)
]

AUTH_SESSION_COOKIE_NAME: str = 'ARXIVNG_SESSION_ID'
AUTH_SESSION_COOKIE_DOMAIN: str = '.arxiv.org'
AUTH_SESSION_COOKIE_SECURE: bool = True

CLASSIC_COOKIE_NAME: str = 'tapir_session'
CLASSIC_PERMANENT_COOKIE_NAME: str = 'tapir_permanent'

CLASSIC_TRACKING_COOKIE: str = 'browser'
CLASSIC_TOKEN_RECOVERY_TIMEOUT: int = 86400

CLASSIC_SESSION_HASH: str = 'foosecret'
SESSION_DURATION: int = 36000

CAPTCHA_SECRET: str = 'foocaptcha'
"""Used to encrypt captcha answers, so that we don't need to store them."""

CAPTCHA_FONT: Optional[str] = None

CREATE_DB: bool = False

AUTH_UPDATED_SESSION_REF: bool = True # see ARXIVNG-1920

BASE_SERVER = os.environ.get('BASE_SERVER', 'arxiv.org')
LOCALHOST_DEV: bool = False
"""Enables a set of config vars that facilites development on localhost"""

SECRET_KEY = os.environ.get('SECRET_KEY', 'asdf1234')
"""SECRET_KEY used for flask sessions."""
WTF_CSRF_ENABLED: bool = False
"""Enable CSRF.

SESSION_COOKIE_PATH = os.environ.get('APPLICATION_ROOT', '/')
Do not disable in production."""

REDIS_HOST = os.environ.get('REDIS_HOST', 'localhost')
REDIS_PORT = os.environ.get('REDIS_PORT', '7000')
REDIS_DATABASE = os.environ.get('REDIS_DATABASE', '0')
REDIS_TOKEN = os.environ.get('REDIS_TOKEN', None)
"""This is the token used in the AUTH procedure."""
REDIS_CLUSTER = os.environ.get('REDIS_CLUSTER', '1')
WTF_CSRF_EXEMPT: str = 'admin_webapp.routes.ui.login,admin_webapp.routes.ui.logout'
"""Comma seperted list of views to not do CSRF protection on.

REDIS_FAKE = os.environ.get('REDIS_FAKE', False)
"""Use the FakeRedis library instead of a redis service.
Login and logout lack the setup for this."""

Useful for testing, dev, beta."""
# if LOCALHOST_DEV:
# # Don't want to setup redis just for local developers
# REDIS_FAKE=True
# FLASK_DEBUG=True
# DEBUG=True
# if not SQLALCHEMY_DATABASE_URI:
# # SQLALCHEMY_DATABASE_URI = 'sqlite:///../locahost_dev.db'
# SQLALCHEMY_DATABASE_URI='mysql+mysqldb://root:root@localhost:3306/arXiv'

JWT_SECRET = os.environ.get('JWT_SECRET', 'foosecret')
# # CLASSIC_DATABASE_URI = SQLALCHEMY_DATABASE_URI

DEFAULT_LOGIN_REDIRECT_URL = os.environ.get(
'DEFAULT_LOGIN_REDIRECT_URL',
'https://arxiv.org/user'
)
DEFAULT_LOGOUT_REDIRECT_URL = os.environ.get(
'DEFAULT_LOGOUT_REDIRECT_URL',
'https://arxiv.org'
)

LOGIN_REDIRECT_REGEX = os.environ.get('LOGIN_REDIRECT_REGEX',
fr'(/.*)|(https://([a-zA-Z0-9\-.])*{re.escape(BASE_SERVER)}/.*)')
"""Regex to check next_page of /login.

Only next_page values that match this regex will be allowed. All
others will go to the DEFAULT_LOGOUT_REDIRECT_URL. The default value
for this allows relative URLs and URLs to subdomains of the
BASE_SERVER.
"""

login_redirect_pattern = re.compile(LOGIN_REDIRECT_REGEX)

AUTH_SESSION_COOKIE_NAME = 'ARXIVNG_SESSION_ID'
AUTH_SESSION_COOKIE_DOMAIN = os.environ.get('AUTH_SESSION_COOKIE_DOMAIN', '.arxiv.org')
AUTH_SESSION_COOKIE_SECURE = bool(int(os.environ.get('AUTH_SESSION_COOKIE_SECURE', '1')))

CLASSIC_COOKIE_NAME = os.environ.get('CLASSIC_COOKIE_NAME', 'tapir_session')
CLASSIC_PERMANENT_COOKIE_NAME = os.environ.get(
'CLASSIC_PERMANENT_COOKIE_NAME',
'tapir_permanent'
)
CLASSIC_TRACKING_COOKIE = os.environ.get('CLASSIC_TRACKING_COOKIE', 'browser')
CLASSIC_TOKEN_RECOVERY_TIMEOUT = os.environ.get(
'CLASSIC_TOKEN_RECOVERY_TIMEOUT',
'86400'
)
CLASSIC_SESSION_HASH = os.environ.get('CLASSIC_SESSION_HASH', 'foosecret')
SESSION_DURATION = os.environ.get(
'SESSION_DURATION',
'36000'
)

ARXIV_BUSINESS_TZ = ZoneInfo(os.environ.get('ARXIV_BUSINESS_TZ', 'America/New_York'))

SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI')
CLASSIC_DATABASE_URI = SQLALCHEMY_DATABASE_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False

CAPTCHA_SECRET = os.environ.get('CAPTCHA_SECRET', 'foocaptcha')
"""Used to encrypt captcha answers, so that we don't need to store them."""

CAPTCHA_FONT = os.environ.get('CAPTCHA_FONT', None)

URLS = [
("lost_password", "/user/lost_password", BASE_SERVER),
("account", "/user", BASE_SERVER)
]

CREATE_DB = bool(int(os.environ.get('CREATE_DB', 0)))


AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID', 'nope')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY', 'nope')
AWS_REGION = os.environ.get('AWS_REGION', 'us-east-1')
FLASKS3_BUCKET_NAME = os.environ.get('FLASKS3_BUCKET_NAME', 'some_bucket')
FLASKS3_CDN_DOMAIN = os.environ.get('FLASKS3_CDN_DOMAIN', 'static.arxiv.org')
FLASKS3_USE_HTTPS = os.environ.get('FLASKS3_USE_HTTPS', 1)
FLASKS3_FORCE_MIMETYPE = os.environ.get('FLASKS3_FORCE_MIMETYPE', 1)
FLASKS3_ACTIVE = os.environ.get('FLASKS3_ACTIVE', 0)
"""Flask-S3 plugin settings."""

AUTH_UPDATED_SESSION_REF = True # see ARXIVNG-1920

LOCALHOST_DEV = os.environ.get('LOCALHOST_DEV', False)
"""Enables a set of config vars that facilites development on localhost"""



WTF_CSRF_ENABLED = os.environ.get('WTF_CSRF_ENABLED', True)
"""Enable CSRF.

Do not disable in production."""

WTF_CSRF_EXEMPT = os.environ.get('WTF_CSRF_EXEMPT',
'''admin_webapp.routes.ui.login,admin_webapp.routes.ui.logout''')
"""Comma seperted list of views to not do CSRF protection on.

Login and logout lack the setup for this."""

if LOCALHOST_DEV:
# Don't want to setup redis just for local developers
REDIS_FAKE=True
FLASK_DEBUG=True
DEBUG=True
if not SQLALCHEMY_DATABASE_URI:
SQLALCHEMY_DATABASE_URI = 'sqlite:///../locahost_dev.db'
CLASSIC_DATABASE_URI = SQLALCHEMY_DATABASE_URI

DEFAULT_LOGIN_REDIRECT_URL='/protected'
# Need to use this funny name where we have a DNS entry to 127.0.0.1
# because browsers will reject cookie domains with fewer than 2 dots
AUTH_SESSION_COOKIE_DOMAIN='localhost.arxiv.org'
# Want to not conflict with any existing cookies for subdomains of arxiv.org
# so give it a different name
CLASSIC_COOKIE_NAME='LOCALHOST_DEV_admin_webapp_classic_cookie'
# Don't want to use HTTPS for local dev
AUTH_SESSION_COOKIE_SECURE=0
# Redirect to relative pages instead of arxiv.org pages
DEFAULT_LOGOUT_REDIRECT_URL='/login'
DEFAULT_LOGIN_REDIRECT_URL='/protected'
# DEFAULT_LOGIN_REDIRECT_URL='/protected'
# # Need to use this funny name where we have a DNS entry to 127.0.0.1
# # because browsers will reject cookie domains with fewer than 2 dots
# AUTH_SESSION_COOKIE_DOMAIN='localhost.arxiv.org'
# # Want to not conflict with any existing cookies for subdomains of arxiv.org
# # so give it a different name
# CLASSIC_COOKIE_NAME='LOCALHOST_DEV_admin_webapp_classic_cookie'
# # Don't want to use HTTPS for local dev
# AUTH_SESSION_COOKIE_SECURE=0
# # Redirect to relative pages instead of arxiv.org pages
# DEFAULT_LOGOUT_REDIRECT_URL='/login'
# DEFAULT_LOGIN_REDIRECT_URL='/protected'
Loading