Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
N
NACSOS Core
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
MCC APSIS
NACSOS
NACSOS Core
Commits
8384a083
Commit
8384a083
authored
1 year ago
by
Tim Repke
Browse files
Options
Downloads
Patches
Plain Diff
improve security handling and add token handling options
parent
e76d632b
No related branches found
No related tags found
1 merge request
!41
Master
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
server/api/routes/auth.py
+41
-12
41 additions, 12 deletions
server/api/routes/auth.py
server/api/routes/users.py
+36
-6
36 additions, 6 deletions
server/api/routes/users.py
server/util/security.py
+1
-1
1 addition, 1 deletion
server/util/security.py
with
78 additions
and
19 deletions
server/api/routes/auth.py
+
41
−
12
View file @
8384a083
from
fastapi
import
APIRouter
,
Depends
,
HTTPException
,
status
from
typing
import
TYPE_CHECKING
from
fastapi
import
APIRouter
,
Depends
from
fastapi.security
import
OAuth2PasswordRequestForm
from
fastapi.security
import
OAuth2PasswordRequestForm
from
nacsos_data.db.schemas.users
import
AuthToken
from
sqlalchemy
import
select
from
nacsos_data.models.users
import
UserModel
,
AuthTokenModel
from
nacsos_data.models.users
import
UserModel
,
AuthTokenModel
from
server.util.security
import
get_current_active_user
,
auth_helper
,
InvalidCredentialsError
from
server.api.errors
import
NoDataForKeyError
from
server.util.security
import
get_current_active_user
,
auth_helper
,
InvalidCredentialsError
,
NotAuthenticated
from
server.util.logging
import
get_logger
from
server.util.logging
import
get_logger
from
server
import
db_engine
if
TYPE_CHECKING
:
from
sqlalchemy.ext.asyncio
import
AsyncSession
# noqa F401
logger
=
get_logger
(
'
nacsos.api.route.login
'
)
logger
=
get_logger
(
'
nacsos.api.route.login
'
)
router
=
APIRouter
()
router
=
APIRouter
()
...
@@ -20,11 +29,35 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
...
@@ -20,11 +29,35 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
token
=
await
auth_helper
.
refresh_or_create_token
(
username
=
user
.
username
)
token
=
await
auth_helper
.
refresh_or_create_token
(
username
=
user
.
username
)
return
token
return
token
except
InvalidCredentialsError
as
e
:
except
InvalidCredentialsError
as
e
:
raise
HTTPException
(
raise
NotAuthenticated
(
repr
(
e
))
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
repr
(
e
),
headers
=
{
'
WWW-Authenticate
'
:
'
Bearer
'
},
@router.put
(
'
/token/{token_id}
'
,
response_model
=
AuthTokenModel
)
)
async
def
refresh_token
(
token_id
:
str
,
current_user
:
UserModel
=
Depends
(
get_current_active_user
))
->
AuthTokenModel
:
try
:
token
=
await
auth_helper
.
refresh_or_create_token
(
token_id
=
token_id
,
verify_username
=
current_user
.
username
)
return
token
except
(
InvalidCredentialsError
,
AssertionError
)
as
e
:
raise
NotAuthenticated
(
repr
(
e
))
@router.delete
(
'
/token/{token_id}
'
)
async
def
refresh_token
(
token_id
:
str
,
current_user
:
UserModel
=
Depends
(
get_current_active_user
)):
await
auth_helper
.
clear_token_by_id
(
token_id
=
token_id
,
verify_username
=
current_user
.
username
)
@router.get
(
'
/my-tokens
'
,
response_model
=
list
[
AuthTokenModel
])
async
def
read_tokens_me
(
current_user
:
UserModel
=
Depends
(
get_current_active_user
)):
async
with
db_engine
.
session
()
as
session
:
# type: AsyncSession
stmt
=
select
(
AuthToken
)
\
.
where
(
AuthToken
.
username
==
current_user
.
username
)
\
.
order_by
(
AuthToken
.
valid_till
)
tokens
=
(
await
session
.
scalars
(
stmt
)).
all
()
if
tokens
is
None
or
len
(
tokens
)
==
0
:
raise
NoDataForKeyError
(
'
No auth token for this user (this error should not exist)
'
)
return
[
AuthTokenModel
.
parse_obj
(
token
.
__dict__
)
for
token
in
tokens
]
@router.get
(
'
/me
'
,
response_model
=
UserModel
)
@router.get
(
'
/me
'
,
response_model
=
UserModel
)
...
@@ -37,11 +70,7 @@ async def logout(current_user: UserModel = Depends(get_current_active_user)):
...
@@ -37,11 +70,7 @@ async def logout(current_user: UserModel = Depends(get_current_active_user)):
username
=
current_user
.
username
username
=
current_user
.
username
if
username
is
None
:
if
username
is
None
:
raise
HTTPException
(
raise
NotAuthenticated
(
'
RuntimeError(empty username)
'
)
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
'
RuntimeError(empty username)
'
,
headers
=
{
'
WWW-Authenticate
'
:
'
Bearer
'
},
)
await
auth_helper
.
clear_tokens_by_user
(
username
=
username
)
await
auth_helper
.
clear_tokens_by_user
(
username
=
username
)
...
...
This diff is collapsed.
Click to expand it.
server/api/routes/users.py
+
36
−
6
View file @
8384a083
from
typing
import
TYPE_CHECKING
import
uuid
from
fastapi
import
APIRouter
,
Depends
,
Query
,
HTTPException
,
status
as
http_status
from
fastapi
import
APIRouter
,
Depends
,
Query
,
HTTPException
,
status
as
http_status
from
nacsos_data.models.users
import
UserModel
,
UserInDBModel
,
UserBaseModel
from
nacsos_data.models.users
import
UserModel
,
UserInDBModel
,
UserBaseModel
from
nacsos_data.util.auth
import
UserPermissions
from
nacsos_data.util.auth
import
UserPermissions
from
nacsos_data.db.schemas
import
User
from
nacsos_data.db.crud.users
import
\
from
nacsos_data.db.crud.users
import
\
read_users
,
\
read_users
,
\
read_user_by_id
,
\
read_user_by_id
,
\
read_users_by_ids
,
\
read_users_by_ids
,
\
create_or_update_user
create_or_update_user
,
get_password_hash
from
sqlalchemy
import
select
from
server.data
import
db_engine
from
server.data
import
db_engine
from
server.api.errors
import
DataNotFoundWarning
,
UserNotFoundError
from
server.api.errors
import
DataNotFoundWarning
,
UserNotFoundError
,
UserPermissionError
from
server.util.logging
import
get_logger
from
server.util.logging
import
get_logger
from
server.util.security
import
UserPermissionChecker
,
get_current_active_user
from
server.util.security
import
UserPermissionChecker
,
get_current_active_user
if
TYPE_CHECKING
:
from
sqlalchemy.ext.asyncio
import
AsyncSession
# noqa F401
logger
=
get_logger
(
'
nacsos.api.route.admin.users
'
)
logger
=
get_logger
(
'
nacsos.api.route.admin.users
'
)
router
=
APIRouter
()
router
=
APIRouter
()
...
@@ -62,9 +70,31 @@ async def get_users_by_ids(user_id: list[str] = Query(),
...
@@ -62,9 +70,31 @@ async def get_users_by_ids(user_id: list[str] = Query(),
async
def
save_user
(
user
:
UserInDBModel
|
UserModel
,
current_user
:
UserModel
=
Depends
(
get_current_active_user
)):
async
def
save_user
(
user
:
UserInDBModel
|
UserModel
,
current_user
:
UserModel
=
Depends
(
get_current_active_user
)):
# Users can only edit their own info, admins can edit all.
# Users can only edit their own info, admins can edit all.
if
user
.
user_id
!=
current_user
.
user_id
and
not
current_user
.
is_superuser
:
if
user
.
user_id
!=
current_user
.
user_id
and
not
current_user
.
is_superuser
:
raise
HTTPException
(
raise
UserPermissionError
(
'
You do not have permission to perform this action.
'
)
status_code
=
http_status
.
HTTP_403_FORBIDDEN
,
detail
=
'
You do not have permission to perform this action.
'
,
)
return
await
create_or_update_user
(
user
,
engine
=
db_engine
)
return
await
create_or_update_user
(
user
,
engine
=
db_engine
)
@router.put
(
'
/my-details
'
,
response_model
=
str
)
async
def
save_user_self
(
user
:
UserInDBModel
|
UserModel
,
current_user
:
UserModel
=
Depends
(
get_current_active_user
)):
if
current_user
.
user_id
!=
user
.
user_id
:
raise
UserPermissionError
(
'
This is not you!
'
)
async
with
db_engine
.
session
()
as
session
:
# type: AsyncSession
user_db
:
User
|
None
=
(
await
session
.
scalars
(
select
(
User
).
where
(
User
.
user_id
==
user
.
user_id
))).
one_or_none
()
password
:
str
|
None
=
getattr
(
user
,
'
password
'
,
None
)
if
password
is
not
None
:
user_db
.
password
=
get_password_hash
(
password
)
user_db
.
email
=
user
.
email
user_db
.
full_name
=
user
.
full_name
user_db
.
affiliation
=
user
.
affiliation
user_id
=
str
(
user_db
.
user_id
)
# save changes
await
session
.
commit
()
return
user_id
This diff is collapsed.
Click to expand it.
server/util/security.py
+
1
−
1
View file @
8384a083
...
@@ -87,5 +87,5 @@ class UserPermissionChecker:
...
@@ -87,5 +87,5 @@ class UserPermissionChecker:
__all__
=
[
'
InsufficientPermissionError
'
,
'
InvalidCredentialsError
'
,
'
InsufficientPermissions
'
,
__all__
=
[
'
InsufficientPermissionError
'
,
'
InvalidCredentialsError
'
,
'
InsufficientPermissions
'
,
'
auth_helper
'
,
'
oauth2_scheme
'
,
'
UserPermissionChecker
'
,
'
UserPermissions
'
,
'
auth_helper
'
,
'
oauth2_scheme
'
,
'
UserPermissionChecker
'
,
'
UserPermissions
'
,
'
NotAuthenticated
'
,
'
get_current_user
'
,
'
get_current_active_user
'
,
'
get_current_active_superuser
'
]
'
get_current_user
'
,
'
get_current_active_user
'
,
'
get_current_active_superuser
'
]
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
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!
Save comment
Cancel
Please
register
or
sign in
to comment