Skip to content

Commit

Permalink
Restrict login_required decorator to specific roles; Improved permiss…
Browse files Browse the repository at this point in the history
…ions error handling; minor refactoring
  • Loading branch information
bsatoriu committed Aug 15, 2024
1 parent 9ceb775 commit 4a2bcbd
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 86 deletions.
45 changes: 25 additions & 20 deletions api/auth/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
from api.auth.cas_auth import validate_proxy, validate_bearer, validate_cas_request
from api.maap_database import db
from api.models.member import Member
from api.models.role import Role

HEADER_PROXY_TICKET = "proxy-ticket"
HEADER_CP_TICKET = "cpticket"
HEADER_AUTHORIZATION = "Authorization"
HEADER_CAS_AUTHORIZATION = "cas-authorization"
HEADER_DPS_TOKEN = "dps-token"
MEMBER_STATUS_ACTIVE = "active"
MEMBER_STATUS_SUSPENDED = "suspended"


def get_authorized_user():
Expand All @@ -34,34 +37,36 @@ def get_authorized_user():
return None


def login_required(wrapped_function):
@wraps(wrapped_function)
def wrap(*args, **kwargs):
def login_required(role=Role.ROLE_GUEST):
def login_required_outer(wrapped_function):
@wraps(wrapped_function)
def wrap(*args, **kwargs):
auth = get_auth_header()

auth = get_auth_header()
if auth == HEADER_PROXY_TICKET or auth == HEADER_CP_TICKET:
member_session = validate_proxy(request.headers[auth])

if ((auth == HEADER_PROXY_TICKET or auth == HEADER_CP_TICKET) and
validate_proxy(request.headers[auth]) is not None):
return wrapped_function(*args, **kwargs)
if member_session is not None and member_session.member.role_id >= role:
return wrapped_function(*args, **kwargs)

if auth == HEADER_AUTHORIZATION:
bearer = request.headers.get(auth)
token = bearer.split()[1]
authorized = validate_bearer(token)
if auth == HEADER_AUTHORIZATION:
bearer = request.headers.get(auth)
token = bearer.split()[1]
authorized = validate_bearer(token)

if authorized is not None:
return wrapped_function(*args, **kwargs)

if auth == HEADER_CAS_AUTHORIZATION and validate_cas_request(request.headers[auth]) is not None:
return wrapped_function(*args, **kwargs)
if authorized is not None:
return wrapped_function(*args, **kwargs)

if auth == HEADER_DPS_TOKEN and valid_dps_request():
return wrapped_function(*args, **kwargs)
if auth == HEADER_CAS_AUTHORIZATION and validate_cas_request(request.headers[auth]) is not None:
return wrapped_function(*args, **kwargs)

abort(status.HTTP_403_FORBIDDEN, description="Not authorized.")
if auth == HEADER_DPS_TOKEN and valid_dps_request():
return wrapped_function(*args, **kwargs)

return wrap
abort(status.HTTP_403_FORBIDDEN, description="Not authorized.")

return wrap
return login_required_outer

def valid_dps_request():
if HEADER_DPS_TOKEN in request.headers:
Expand Down
13 changes: 6 additions & 7 deletions api/endpoints/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from flask_restx import Resource
from flask import request
from flask_api import status
from api.models.role import Role
from api.restplus import api
from api.auth.security import login_required
from api.maap_database import db
Expand All @@ -10,16 +11,16 @@
from datetime import datetime
import json

log = logging.getLogger(__name__)
from api.utils.http_util import err_response

log = logging.getLogger(__name__)
ns = api.namespace('admin', description='Operations related to the MAAP admin')


@ns.route('/pre-approved')
class PreApprovedEmails(Resource):

@api.doc(security='ApiKeyAuth')
@login_required
@login_required(role=Role.ROLE_ADMIN)
def get(self):
pre_approved = db.session.query(
PreApproved.email,
Expand All @@ -31,7 +32,7 @@ def get(self):
return result

@api.doc(security='ApiKeyAuth')
@login_required
@login_required(role=Role.ROLE_ADMIN)
def post(self):

"""
Expand Down Expand Up @@ -79,7 +80,7 @@ def post(self):
class PreApprovedEmails(Resource):

@api.doc(security='ApiKeyAuth')
@login_required
@login_required(role=Role.ROLE_ADMIN)
def delete(self, email):
"""
Delete pre-approved email
Expand All @@ -94,5 +95,3 @@ def delete(self, email):
db.session.commit()

return {"code": status.HTTP_200_OK, "message": "Successfully deleted {}.".format(email)}


9 changes: 5 additions & 4 deletions api/endpoints/algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from flask import request, Response
from flask_restx import Resource, reqparse
from flask_api import status

from api.models.member import Member
from api.restplus import api
import re
import json
import traceback
import api.utils.github_util as git
import api.utils.hysds_util as hysds
Expand Down Expand Up @@ -91,7 +92,7 @@ class Register(Resource):
]""")

@api.doc(security='ApiKeyAuth')
@login_required
@login_required()
def post(self):
"""
This will create the hysds spec files and commit to git
Expand Down Expand Up @@ -422,7 +423,7 @@ def get(self, algo_id):
.format(ex, tb)), status=status.HTTP_500_INTERNAL_SERVER_ERROR, mimetype='text/xml')

@api.doc(security='ApiKeyAuth')
@login_required
@login_required()
def delete(self, algo_id):
"""
delete a registered algorithm
Expand Down Expand Up @@ -494,7 +495,7 @@ def post(self):
class Publish(Resource):

@api.doc(security='ApiKeyAuth')
@login_required
@login_required()
def post(self):
"""
This endpoint is called by a logged-in user to make an algorithm public
Expand Down
8 changes: 4 additions & 4 deletions api/endpoints/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
class Submit(Resource):

@api.doc(security='ApiKeyAuth')
@login_required
@login_required()
def post(self):
"""
This will submit jobs to the Job Execution System (HySDS)
Expand Down Expand Up @@ -468,7 +468,7 @@ class Jobs(Resource):
parser.add_argument('username', required=False, type=str, help="Username of job submitter")

@api.doc(security='ApiKeyAuth')
@login_required
@login_required()
def get(self):
"""
Returns a list of jobs for a given user
Expand Down Expand Up @@ -581,7 +581,7 @@ class JobsByUser(Resource):
parser.add_argument('status', type=str, help="Job status, e.g. Accepted, Running, Succeeded, Failed, etc.", required=False)

@api.doc(security='ApiKeyAuth')
@login_required
@login_required()
def get(self, username):
"""
Returns a list of jobs for a given user
Expand Down Expand Up @@ -619,7 +619,7 @@ class StopJobs(Resource):
help="Wait for Cancel job to finish")

@api.doc(security='ApiKeyAuth')
@login_required
@login_required()
def post(self, job_id):
# TODO: add optional parameter wait_for_completion to wait for cancel job to complete.
# Since this can take a long time, we don't wait by default.
Expand Down
Loading

0 comments on commit 4a2bcbd

Please sign in to comment.