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 4 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
220 changes: 219 additions & 1 deletion 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=1


EASTERN = timezone('US/Eastern')

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -64,6 +78,8 @@
response.set_cookie(key=cookie_name, value=cookie_value, max_age=max_age,
**params)

def unset_masquerade_cookie(response: Response) -> None:
response.set_cookie(key='MASQUERADE', value='', max_age=0, httponly=True)
Fixed Show fixed Hide fixed

# 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 +173,6 @@
)
return response


@blueprint.route('/logout', methods=['GET'])
def logout() -> Response:
"""Log out of arXiv."""
Expand All @@ -178,6 +193,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 +222,205 @@
return next_page
else:
return otherwise

# TODO: just post, but easier to test in dev with GET.
@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': (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>

2 changes: 2 additions & 0 deletions accounts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ redis = "*" # constrained by what is set in arxiv-auth
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"}
Werkzeug = "2.2.3"
pydantic = "2.1.1"

[tool.poetry.dev-dependencies]
mimesis = "*"
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
3 changes: 2 additions & 1 deletion arxiv-auth/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ python-dateutil = "*"
pyjwt = "*"
redis = "==2.10.6"
redis-py-cluster = "==1.3.6"
pydantic = "^1.0"
pydantic = "2.1.1"
Werkzeug = "2.2.3"
arxiv-base = {git = "https://github.com/arXiv/arxiv-base.git", rev = "1.0.1"}

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