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

ARXIVCE-190: eust can become a moderator for testing arxiv-check #95

Merged
merged 8 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions accounts/accounts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
AUTH_SESSION_COOKIE_NAME = 'ARXIVNG_SESSION_ID'
AUTH_SESSION_COOKIE_DOMAIN = os.environ.get('AUTH_SESSION_COOKIE_DOMAIN', f'.{BASE_SERVER}')
AUTH_SESSION_COOKIE_SECURE = bool(int(os.environ.get('AUTH_SESSION_COOKIE_SECURE', '1')))
MASQUERADE_COOKIE_NAME = 'MASQUERADE'


#################### Classic Auth ####################
Expand Down
3 changes: 2 additions & 1 deletion accounts/accounts/controllers/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ def logout(session_cookie: Optional[str],
data = {
'cookies': {
'auth_session_cookie': ('', 0),
'classic_cookie': ('', 0)
'classic_cookie': ('', 0),
'MASQUERADE_COOKIE': ('', 0),
}
}
return data, status.HTTP_303_SEE_OTHER, {'Location': good_next_page(next_page)}
Expand Down
228 changes: 226 additions & 2 deletions accounts/accounts/routes/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@
from accounts.next_page import good_next_page
from accounts.controllers import captcha_image, registration, authentication

# for become_user:
import jwt
import os
import uuid
from arxiv_auth.auth.sessions.store import _generate_nonce
from arxiv_auth.auth.tokens import decode
from arxiv_auth.domain import Session as JWTSession
from arxiv_auth.legacy.cookies import pack, unpack
from arxiv_auth.legacy.models import db, DBSession, DBUserNickname, DBUser
from arxiv_auth.legacy.models import TapirAdminAudit
from arxiv_auth.legacy.util import compute_capabilities, epoch, get_session_duration, now
DEBUG=0


EASTERN = timezone('US/Eastern')

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -56,14 +70,21 @@
domain = current_app.config['AUTH_SESSION_COOKIE_DOMAIN']
logger.info('Set cookie %s with %s, max_age %s domain %s',
cookie_name, cookie_value, max_age, domain)
params = dict(httponly=True, domain=domain)
is_httponly = False if cookie_name == "MASQUERADE" else True
params = dict(httponly=is_httponly, domain=domain)
if current_app.config['AUTH_SESSION_COOKIE_SECURE']:
# Setting samesite to lax, to allow reasonable links to
# authenticated views using GET requests.
params.update({'secure': True, 'samesite': 'lax'})
response.set_cookie(key=cookie_name, value=cookie_value, max_age=max_age,
**params)

# Not sure unset_masquerade_cookie will be needed,
# as this other code should be enough to clear it first:
# accounts/accounts/controllers/authentication.py:190
def unset_masquerade_cookie(response: Response) -> None:
cookie_name = current_app.config[f'MASQUERADE_COOKIE_NAME']
response.set_cookie(key=cookie_name, value='', max_age=0, httponly=True)

Check warning

Code scanning / CodeQL

Failure to use secure cookies Medium

Cookie is added without the Secure attribute properly set.

# This is unlikely to be useful once the classic submission UI is disabled.
def unset_submission_cookie(response: Response) -> None:
Expand Down Expand Up @@ -157,7 +178,6 @@
)
return response


@blueprint.route('/logout', methods=['GET'])
def logout() -> Response:
"""Log out of arXiv."""
Expand All @@ -178,6 +198,7 @@
unset_submission_cookie(response) # Fix for ARXIVNG-1149.
# Partial fix for ARXIVNG-1653, ARXIVNG-1644
unset_permanent_cookie(response)
unset_masquerade_cookie(response)
return response
return redirect(safe_next_page, code=status.HTTP_302_FOUND)

Expand Down Expand Up @@ -206,3 +227,206 @@
return next_page
else:
return otherwise

# Only use post in production to avoid caching issues in fastly,
# but can include GET in dev for testing.
@blueprint.route('/become_user', methods=['POST'])
def become_user_become_user_id() -> Response:

become_user_id = int(request.args.get('become_user_id'))

classic_cookie_name = current_app.config['CLASSIC_COOKIE_NAME']
classic_cookie = request.cookies.get(classic_cookie_name, None)
if DEBUG:
print("BU-DEBUG: classic_cookie", classic_cookie_name, classic_cookie)
classic_cookie_data = unpack(classic_cookie)
if DEBUG:
print("BU-DEBUG: classic_cookie_data", classic_cookie_data)

permanent_cookie_name = current_app.config['CLASSIC_PERMANENT_COOKIE_NAME']
permanent_cookie = request.cookies.get(permanent_cookie_name, None)
if DEBUG:
print("BU-DEBUG: permanent_cookie_name", permanent_cookie_name, permanent_cookie)

session_cookie_name = current_app.config['AUTH_SESSION_COOKIE_NAME']
session_cookie = request.cookies.get(session_cookie_name, None)
if DEBUG:
print("BU-DEBUG: session_cookie_name", session_cookie_name, session_cookie)

session_cookie_domain = current_app.config['AUTH_SESSION_COOKIE_DOMAIN']
if DEBUG:
print("BU-DEBUG: session_cookie_domain", session_cookie_domain)

session_cookie_secure = current_app.config['AUTH_SESSION_COOKIE_SECURE']
if DEBUG:
print("BU-DEBUG: session_cookie_secure", session_cookie_secure)


submit_cookie_name = 'submit_session'
submit_cookie = request.cookies.get(submit_cookie_name, None)
if DEBUG:
print("BU-DEBUG: submit_cookie", submit_cookie_name, submit_cookie)

tracking_cookie_name = os.environ.get('CLASSIC_TRACKING_COOKIE', 'browser')
tracking_cookie = request.cookies.get(tracking_cookie_name, None)
if DEBUG:
print("BU-DEBUG: tracking_cookie", tracking_cookie_name, tracking_cookie)

secret = os.environ.get('JWT_SECRET')

ip_address = request.remote_addr
if DEBUG:
print("BU-DEBUG: ip_address", ip_address)

valid_user = False
jwt_session = None
if session_cookie:

data = jwt.decode(session_cookie, secret, algorithms=["HS256"])
if DEBUG:
print("BU-DEBUG: jwt decode session_cookie:", data)

user_id = f"{ data.get('user_id') }"
if user_id:
user_id = int(user_id)
if user_id > 0:
if DEBUG:
print("BU-DEBUG: jwt user_id", user_id, type(user_id))

admin_user = db.session.query(DBUser) \
.filter(DBUser.user_id == int(user_id)) \
.filter(DBUser.flag_edit_users == 1) \
.filter(DBUser.flag_deleted == 0) \
.filter(DBUser.flag_banned == 0) \
.filter(DBUser.flag_approved == 1) \
.first()

if DEBUG:
print("BU-DEBUG: look for admin_user:", admin_user)
if admin_user:
valid_user = True

valid_become_user_id = False
if valid_user:
if become_user_id > 0:
if DEBUG:
print("BU-DEBUG: become_user_id", become_user_id)
become_user = db.session.query(DBUser) \
.filter(DBUser.user_id == int(become_user_id)) \
.filter(DBUser.flag_edit_users == 0) \
.filter(DBUser.flag_deleted == 0) \
.filter(DBUser.flag_banned == 0) \
.filter(DBUser.flag_approved == 1) \
.first()
#.filter(DBUser.flag_can_lock == 0) \

if DEBUG:
print("BU-DEBUG: become_user", become_user)
if become_user:
valid_become_user_id= True

if DEBUG:
print(dir(become_user))


found_username = False
become_username = None
if valid_become_user_id:
become_user_nickname = db.session.query(DBUserNickname) \
.filter(DBUserNickname.user_id == int(become_user_id)) \
.filter(DBUserNickname.flag_valid == 1) \
.first()
if DEBUG:
print("BU-DEBUG: become_user_nickname", become_user_nickname)
if become_user_nickname:
become_username = become_user_nickname.nickname
found_username = True
if DEBUG:
print("BU-DEBUG: become_username", become_username)

if not (valid_user and valid_become_user_id and found_username):
response = make_response(redirect("/login", code=status.HTTP_303_SEE_OTHER))
return response
else:

start_time = ( datetime.now(tz=UTC) ).replace(microsecond=0)
expires = ( start_time + timedelta(seconds=3600) ).replace(microsecond=0)
now1 = now()
if DEBUG:
print("BU-DEBUG: dates.start_time:", start_time)
print("BU-DEBUG: dates.expires:", expires)
print("BU-DEBUG: dates.now1:", now1)

become_session = DBSession(
end_time=0,
last_reissue=now1,
start_time=now1,
user_id=become_user.user_id,
)
db.session.add(become_session)
db.session.commit()
if DEBUG:
print("BU-DEBUG: become_session", become_session)

admin_audit = TapirAdminAudit(
action="become-user",
admin_user=admin_user.user_id,
affected_user=become_user.user_id,
comment='No-comment',
data=become_session.session_id,
ip_addr=ip_address,
log_date=now1,
session=become_session,
tracking_cookie=tracking_cookie,
)
db.session.add(admin_audit)
db.session.commit()
if DEBUG:
print("BU-DEBUG: admin_audit", admin_audit)

become_jwt_data = {
'user_id': become_session.user_id,
'session_id': str(uuid.uuid4()),
'nonce': _generate_nonce(),
"expires": expires.isoformat(),
"start_time": start_time.isoformat(),
}
become_jwt = jwt.encode(become_jwt_data, secret)
if DEBUG:
print("BU-DEBUG: become_jwt", become_jwt)

next_page = "https://check.dev.arxiv.org/"
data: Dict[str, Any] = {
'next_page': next_page,
'admin_user': admin_user,
'become_user': become_user,
'become_username': become_username,
}
response = Response(
render_template("accounts/become_user.html", **data),
status=200
)

become_session_cookie = pack(
become_session.session_id,
become_session.user_id,
ip_address,
start_time,
compute_capabilities(become_user),
)
if DEBUG:
print("BU-DEBUG: become_session_cookie", become_session_cookie)

data: Dict[str, Any] = {
'cookies': {
'AUTH_SESSION_COOKIE': (become_jwt, 3600),
'CLASSIC_COOKIE': (become_session_cookie, 3600),
'MASQUERADE_COOKIE': ('1', 3600),
}
}
set_cookies(response, data)
unset_submission_cookie(response)
unset_permanent_cookie(response)
response.set_cookie(key=tracking_cookie_name, value='', max_age=0, httponly=True)

Check warning

Code scanning / CodeQL

Failure to use secure cookies Medium

Cookie is added without the Secure attribute properly set.

return response
21 changes: 21 additions & 0 deletions accounts/accounts/templates/accounts/become_user.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div>
<p>You were admin user:
<ul>
<li>{{ admin_user.user_id }}</li>
<li>{{ admin_user.first_name }}</li>
<li>{{ admin_user.last_name }}</li>
<li>{{ admin_user.email }}</li>
</ul>
</p>
<p>You are now logged in as:
<ul>
<li>{{ become_user.user_id }}</li>
<li>{{ become_user.first_name }}</li>
<li>{{ become_user.last_name }}</li>
<li>{{ become_user.email }}</li>
<li>{{ become_username}}</li>
</ul>
</p>
<a href="{{ next_page }}">{{ next_page }}</a>
</div>

5 changes: 5 additions & 0 deletions accounts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ redis-py-cluster = "*" # constrained by what is set in arxiv-auth
arxiv-auth = {path = "../arxiv-auth"}
arxiv-base = {git = "https://github.com/arXiv/arxiv-base.git", rev = "1.0.1"}

# I needed to add these for local testing,
# but do not want to commit them and modify what's running at cit.
# Werkzeug = "2.2.3"
# pydantic = "2.1.1"

[tool.poetry.dev-dependencies]
mimesis = "*"
mypy = "*"
Expand Down
45 changes: 44 additions & 1 deletion arxiv-auth/arxiv_auth/legacy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,47 @@ class DBSession(db.Model):
user = relationship('DBUser')


class TapirAdminAudit(db.Model):
'''
mysql> desc tapir_admin_audit;
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| log_date | int unsigned | NO | MUL | 0 | |
| session_id | int unsigned | YES | MUL | NULL | |
| ip_addr | varchar(16) | NO | MUL | | |
| remote_host | varchar(255) | NO | | | |
| admin_user | int unsigned | YES | MUL | NULL | |
| affected_user | int unsigned | NO | MUL | 0 | |
| tracking_cookie | varchar(255) | NO | | | |
| action | varchar(32) | NO | | | |
| data | text | NO | MUL | NULL | |
| comment | text | NO | | NULL | |
| entry_id | int unsigned | NO | PRI | NULL | auto_increment |
+-----------------+--------------+------+-----+---------+----------------+
'''
__tablename__ = 'tapir_admin_audit'

log_date = Column(Integer, nullable=False, index=True, server_default=text("'0'"))
session_id = Column(ForeignKey('tapir_sessions.session_id'), index=True)
ip_addr = Column(String(16), nullable=False, index=True, server_default=text("''"))
remote_host = Column(String(255), nullable=False, server_default=text("''"))
admin_user = Column(ForeignKey('tapir_users.user_id'), index=True)
affected_user = Column(ForeignKey('tapir_users.user_id'), nullable=False, index=True, server_default=text("'0'"))
tracking_cookie = Column(String(255), nullable=False, server_default=text("''"))
action = Column(String(32), nullable=False, server_default=text("''"))
data = Column(Text, nullable=False, index=True)
comment = Column(Text, nullable=False)
entry_id = Column(Integer, primary_key=True)

#tapir_users = relationship('DBSession', primaryjoin='TapirAdminAudit.admin_user == DBUser.user_id')
#tapir_users1 = relationship('DBSession', primaryjoin='TapirAdminAudit.affected_user == DBUser.user_id')
session = relationship('DBSession')

#tapir_users = relationship('TapirUsers', primaryjoin='TapirAdminAudit.admin_user == TapirUsers.user_id')
#tapir_users1 = relationship('TapirUsers', primaryjoin='TapirAdminAudit.affected_user == TapirUsers.user_id')


class DBSessionsAudit(db.Model):
"""Legacy arXiv session audit table. Notably has a tracking cookie."""

Expand Down Expand Up @@ -92,7 +133,9 @@ class DBUser(db.Model):
flag_html_email = Column(Integer, nullable=False, server_default=text("'0'"))
tracking_cookie = Column(String(255), nullable=False, index=True, server_default=text("''"))
flag_allow_tex_produced = Column(Integer, nullable=False, server_default=text("'0'"))

flag_can_lock = Column(Integer, nullable=False, index=False, server_default=text("'0'"))
def __repr__(self):
return f"{ type(self) }:{ self.user_id }/{ self.first_name}/{ self.last_name}"

class DBPolicyClass(db.Model):
"""Legacy authorization table."""
Expand Down
5 changes: 5 additions & 0 deletions arxiv-auth/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ pyjwt = "*"
redis = "==2.10.6"
redis-py-cluster = "==1.3.6"
pydantic = "^1.0"

# I needed to add this for localhost testing,
# but do not want to commit them and modify what's running at cit.
#Werkzeug = "2.3.6"

arxiv-base = {git = "https://github.com/arXiv/arxiv-base.git", rev = "1.0.1"}

[tool.poetry.dev-dependencies]
Expand Down
Loading