Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Arch Linux
aurweb
Commits
8657fd33
Verified
Commit
8657fd33
authored
Sep 29, 2022
by
Kevin Morris
Browse files
feat: GET|POST /account/{name}/delete
Closes
#348
Signed-off-by:
Kevin Morris
<
kevr@0cost.org
>
parent
1180565d
Pipeline
#32031
passed with stages
in 2 minutes and 42 seconds
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
aurweb/models/package_vote.py
View file @
8657fd33
...
...
@@ -14,7 +14,7 @@ class PackageVote(Base):
User
=
relationship
(
_User
,
backref
=
backref
(
"package_votes"
,
lazy
=
"dynamic"
),
backref
=
backref
(
"package_votes"
,
lazy
=
"dynamic"
,
cascade
=
"all, delete"
),
foreign_keys
=
[
__table__
.
c
.
UsersID
],
)
...
...
aurweb/models/session.py
View file @
8657fd33
...
...
@@ -13,7 +13,7 @@ class Session(Base):
User
=
relationship
(
_User
,
backref
=
backref
(
"session"
,
uselist
=
False
),
backref
=
backref
(
"session"
,
cascade
=
"all, delete"
,
uselist
=
False
),
foreign_keys
=
[
__table__
.
c
.
UsersID
],
)
...
...
aurweb/routers/accounts.py
View file @
8657fd33
...
...
@@ -3,13 +3,13 @@ import typing
from
http
import
HTTPStatus
from
typing
import
Any
from
fastapi
import
APIRouter
,
Form
,
Request
from
fastapi
import
APIRouter
,
Form
,
HTTPException
,
Request
from
fastapi.responses
import
HTMLResponse
,
RedirectResponse
from
sqlalchemy
import
and_
,
or_
import
aurweb.config
from
aurweb
import
cookies
,
db
,
l10n
,
logging
,
models
,
util
from
aurweb.auth
import
account_type_required
,
requires_auth
,
requires_guest
from
aurweb.auth
import
account_type_required
,
creds
,
requires_auth
,
requires_guest
from
aurweb.captcha
import
get_captcha_salts
from
aurweb.exceptions
import
ValidationError
,
handle_form_exceptions
from
aurweb.l10n
import
get_translator_for_request
...
...
@@ -598,6 +598,78 @@ async def accounts_post(
return
render_template
(
request
,
"account/index.html"
,
context
)
@
router
.
get
(
"/account/{name}/delete"
)
@
requires_auth
async
def
account_delete
(
request
:
Request
,
name
:
str
):
user
=
db
.
query
(
models
.
User
).
filter
(
models
.
User
.
Username
==
name
).
first
()
if
not
user
:
raise
HTTPException
(
status_code
=
HTTPStatus
.
NOT_FOUND
)
has_cred
=
request
.
user
.
has_credential
(
creds
.
ACCOUNT_EDIT
,
approved
=
[
user
])
if
not
has_cred
:
_
=
l10n
.
get_translator_for_request
(
request
)
raise
HTTPException
(
detail
=
_
(
"You do not have permission to edit this account."
),
status_code
=
HTTPStatus
.
UNAUTHORIZED
,
)
context
=
make_context
(
request
,
"Accounts"
)
context
[
"name"
]
=
name
return
render_template
(
request
,
"account/delete.html"
,
context
)
@
db
.
async_retry_deadlock
@
router
.
post
(
"/account/{name}/delete"
)
@
handle_form_exceptions
@
requires_auth
async
def
account_delete_post
(
request
:
Request
,
name
:
str
,
passwd
:
str
=
Form
(
default
=
str
()),
confirm
:
bool
=
Form
(
default
=
False
),
):
user
=
db
.
query
(
models
.
User
).
filter
(
models
.
User
.
Username
==
name
).
first
()
if
not
user
:
raise
HTTPException
(
status_code
=
HTTPStatus
.
NOT_FOUND
)
has_cred
=
request
.
user
.
has_credential
(
creds
.
ACCOUNT_EDIT
,
approved
=
[
user
])
if
not
has_cred
:
_
=
l10n
.
get_translator_for_request
(
request
)
raise
HTTPException
(
detail
=
_
(
"You do not have permission to edit this account."
),
status_code
=
HTTPStatus
.
UNAUTHORIZED
,
)
context
=
make_context
(
request
,
"Accounts"
)
context
[
"name"
]
=
name
confirm
=
util
.
strtobool
(
confirm
)
if
not
confirm
:
context
[
"errors"
]
=
[
"The account has not been deleted, check the confirmation checkbox."
]
return
render_template
(
request
,
"account/delete.html"
,
context
,
status_code
=
HTTPStatus
.
BAD_REQUEST
,
)
if
not
request
.
user
.
valid_password
(
passwd
):
context
[
"errors"
]
=
[
"Invalid password."
]
return
render_template
(
request
,
"account/delete.html"
,
context
,
status_code
=
HTTPStatus
.
BAD_REQUEST
,
)
with
db
.
begin
():
db
.
delete
(
user
)
return
RedirectResponse
(
"/"
,
status_code
=
HTTPStatus
.
SEE_OTHER
)
def
render_terms_of_service
(
request
:
Request
,
context
:
dict
,
terms
:
typing
.
Iterable
):
if
not
terms
:
return
RedirectResponse
(
"/"
,
status_code
=
HTTPStatus
.
SEE_OTHER
)
...
...
po/aurweb.pot
View file @
8657fd33
...
...
@@ -2346,3 +2346,7 @@ msgstr ""
#: templates/partials/packages/package_metadata.html
msgid "dependencies"
msgstr ""
#: aurweb/routers/accounts.py
msgid "The account has not been deleted, check the confirmation checkbox."
msgstr ""
templates/account/delete.html
0 → 100644
View file @
8657fd33
{% extends "partials/layout.html" %}
{% block pageContent %}
<div
class=
"box"
>
<h2>
{{ "Accounts" | tr }}
</h2>
{% include "partials/error.html" %}
<p>
{{
"You can use this form to permanently delete the AUR account %s%s%s."
| tr | format("
<strong>
", name, "
</strong>
") | safe
}}
</p>
<p>
{{
"%sWARNING%s: This action cannot be undone."
| tr | format("
<strong>
", "
</strong>
") | safe
}}
</p>
<form
id=
"edit-profile-form"
action=
"{{ '/account/%s/delete' | format(name) }}"
method=
"post"
>
<fieldset>
<p>
<label
for=
"id_passwd"
>
{{ "Password" | tr }}:
</label>
<input
id=
"id_passwd"
type=
"password"
size=
"30"
name=
"passwd"
>
</p>
<p>
<label
class=
"confirmation"
>
<input
type=
"checkbox"
name=
"confirm"
>
{{ "Confirm deletion" | tr }}
</label>
</p>
<p>
<button
class=
"button"
type=
"submit"
>
{{ "Delete" | tr }}
</button>
</p>
</fieldset>
</form>
</div>
{% endblock %}
test/test_accounts_routes.py
View file @
8657fd33
...
...
@@ -1949,3 +1949,106 @@ def test_accounts_unauthorized(client: TestClient, user: User):
resp
=
request
.
get
(
"/accounts"
,
cookies
=
cookies
,
allow_redirects
=
False
)
assert
resp
.
status_code
==
int
(
HTTPStatus
.
SEE_OTHER
)
assert
resp
.
headers
.
get
(
"location"
)
==
"/"
def
test_account_delete_self_unauthorized
(
client
:
TestClient
,
tu_user
:
User
):
with
db
.
begin
():
user
=
create_user
(
"some_user"
)
user2
=
create_user
(
"user2"
)
cookies
=
{
"AURSID"
:
user
.
login
(
Request
(),
"testPassword"
)}
endpoint
=
f
"/account/
{
user2
.
Username
}
/delete"
with
client
as
request
:
resp
=
request
.
get
(
endpoint
,
cookies
=
cookies
)
assert
resp
.
status_code
==
HTTPStatus
.
UNAUTHORIZED
resp
=
request
.
post
(
endpoint
,
cookies
=
cookies
)
assert
resp
.
status_code
==
HTTPStatus
.
UNAUTHORIZED
# But a TU does have access
cookies
=
{
"AURSID"
:
tu_user
.
login
(
Request
(),
"testPassword"
)}
with
TestClient
(
app
=
app
)
as
request
:
resp
=
request
.
get
(
endpoint
,
cookies
=
cookies
)
assert
resp
.
status_code
==
HTTPStatus
.
OK
def
test_account_delete_self_not_found
(
client
:
TestClient
,
user
:
User
):
cookies
=
{
"AURSID"
:
user
.
login
(
Request
(),
"testPassword"
)}
endpoint
=
"/account/non-existent-user/delete"
with
client
as
request
:
resp
=
request
.
get
(
endpoint
,
cookies
=
cookies
)
assert
resp
.
status_code
==
HTTPStatus
.
NOT_FOUND
resp
=
request
.
post
(
endpoint
,
cookies
=
cookies
)
assert
resp
.
status_code
==
HTTPStatus
.
NOT_FOUND
def
test_account_delete_self
(
client
:
TestClient
,
user
:
User
):
username
=
user
.
Username
# Confirm that we can view our own account deletion page
cookies
=
{
"AURSID"
:
user
.
login
(
Request
(),
"testPassword"
)}
endpoint
=
f
"/account/
{
username
}
/delete"
with
client
as
request
:
resp
=
request
.
get
(
endpoint
,
cookies
=
cookies
)
assert
resp
.
status_code
==
HTTPStatus
.
OK
# The checkbox must be checked
with
client
as
request
:
resp
=
request
.
post
(
endpoint
,
data
=
{
"passwd"
:
"fakePassword"
,
"confirm"
:
False
},
cookies
=
cookies
,
)
assert
resp
.
status_code
==
HTTPStatus
.
BAD_REQUEST
errors
=
get_errors
(
resp
.
text
)
assert
(
errors
[
0
].
text
.
strip
()
==
"The account has not been deleted, check the confirmation checkbox."
)
# The correct password must be supplied
with
client
as
request
:
resp
=
request
.
post
(
endpoint
,
data
=
{
"passwd"
:
"fakePassword"
,
"confirm"
:
True
},
cookies
=
cookies
,
)
assert
resp
.
status_code
==
HTTPStatus
.
BAD_REQUEST
errors
=
get_errors
(
resp
.
text
)
assert
errors
[
0
].
text
.
strip
()
==
"Invalid password."
# Supply everything correctly and delete ourselves
with
client
as
request
:
resp
=
request
.
post
(
endpoint
,
data
=
{
"passwd"
:
"testPassword"
,
"confirm"
:
True
},
cookies
=
cookies
,
)
assert
resp
.
status_code
==
HTTPStatus
.
SEE_OTHER
# Check that our User record no longer exists in the database
record
=
db
.
query
(
User
).
filter
(
User
.
Username
==
username
).
first
()
assert
record
is
None
def
test_account_delete_as_tu
(
client
:
TestClient
,
tu_user
:
User
):
with
db
.
begin
():
user
=
create_user
(
"user2"
)
cookies
=
{
"AURSID"
:
tu_user
.
login
(
Request
(),
"testPassword"
)}
username
=
user
.
Username
endpoint
=
f
"/account/
{
username
}
/delete"
# Delete the user
with
client
as
request
:
resp
=
request
.
post
(
endpoint
,
data
=
{
"passwd"
:
"testPassword"
,
"confirm"
:
True
},
cookies
=
cookies
,
)
assert
resp
.
status_code
==
HTTPStatus
.
SEE_OTHER
# Check that our User record no longer exists in the database
record
=
db
.
query
(
User
).
filter
(
User
.
Username
==
username
).
first
()
assert
record
is
None
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment