Skip to content

Commit

Permalink
#146-Make viewer endpoints public when the security is enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
fescobar committed Jan 14, 2021
1 parent 584d4cd commit c8ee94b
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 42 deletions.
156 changes: 115 additions & 41 deletions allure-docker-api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,46 @@ def __str__(self):
URL_PREFIX = ''
OPTIMIZE_STORAGE = 0
ENABLE_SECURITY_LOGIN = False
MAKE_VIEWER_ENDPOINTS_PUBLIC = False
SECURITY_USER = None
SECURITY_PASS = None
SECURITY_VIEWER_USER = None
SECURITY_VIEWER_PASS = None
USERS_INFO = {}
ADMIN_ROLE_NAME = 'admin'
VIEWER_ROLE_NAME = 'viewer'
ADMIN_ENDPOINTS = [
{
"method": "post",
"path": "/send-results",
"endpoint": "send_results_endpoint"
},
{
"method": "get",
"path": "/generate-report",
"endpoint": "generate_report_endpoint"
},
{
"method": "get",
"path": "/clean-results",
"endpoint": "clean_results_endpoint"
},
{
"method": "get",
"path": "/clean-history",
"endpoint": "clean_history_endpoint"
},
{
"method": "post",
"path": "/projects",
"endpoint": "create_project_endpoint"
},
{
"method": "delete",
"path": "/projects/{id}",
"endpoint": "delete_project_endpoint"
}
]

GENERATE_REPORT_PROCESS = '{}/generateAllureReport.sh'.format(os.environ['ROOT'])
KEEP_HISTORY_PROCESS = '{}/keepAllureHistory.sh'.format(os.environ['ROOT'])
Expand Down Expand Up @@ -165,6 +198,15 @@ def __str__(self):
except Exception as ex:
LOGGER.error('Wrong env var value. Setting OPTIMIZE_STORAGE=0 by default')

if "MAKE_VIEWER_ENDPOINTS_PUBLIC" in os.environ:
try:
VIEWER_ENDPOINTS_PUBLIC_TMP = int(os.environ['MAKE_VIEWER_ENDPOINTS_PUBLIC'])
if VIEWER_ENDPOINTS_PUBLIC_TMP == 1:
MAKE_VIEWER_ENDPOINTS_PUBLIC = True
LOGGER.info('Overriding MAKE_VIEWER_ENDPOINTS_PUBLIC=%s', VIEWER_ENDPOINTS_PUBLIC_TMP)
except Exception as ex:
LOGGER.error('Wrong env var value. Setting VIEWER_ENDPOINTS_PUBLIC=0 by default')

if "SECURITY_USER" in os.environ:
SECURITY_USER_TMP = os.environ['SECURITY_USER']
if SECURITY_USER_TMP and SECURITY_USER_TMP.strip():
Expand All @@ -177,17 +219,18 @@ def __str__(self):
SECURITY_PASS = SECURITY_PASS_TMP
LOGGER.info('Setting SECURITY_PASS')

if "SECURITY_VIEWER_USER" in os.environ:
SECURITY_VIEWER_USER_TMP = os.environ['SECURITY_VIEWER_USER']
if SECURITY_VIEWER_USER_TMP and SECURITY_VIEWER_USER_TMP.strip():
SECURITY_VIEWER_USER = SECURITY_VIEWER_USER_TMP.lower()
LOGGER.info('Setting SECURITY_VIEWER_USER')
if MAKE_VIEWER_ENDPOINTS_PUBLIC is False:
if "SECURITY_VIEWER_USER" in os.environ:
SECURITY_VIEWER_USER_TMP = os.environ['SECURITY_VIEWER_USER']
if SECURITY_VIEWER_USER_TMP and SECURITY_VIEWER_USER_TMP.strip():
SECURITY_VIEWER_USER = SECURITY_VIEWER_USER_TMP.lower()
LOGGER.info('Setting SECURITY_VIEWER_USER')

if "SECURITY_VIEWER_PASS" in os.environ:
SECURITY_VIEWER_PASS_TMP = os.environ['SECURITY_VIEWER_PASS']
if SECURITY_VIEWER_PASS_TMP and SECURITY_VIEWER_PASS_TMP.strip():
SECURITY_VIEWER_PASS = SECURITY_VIEWER_PASS_TMP
LOGGER.info('Setting SECURITY_VIEWER_PASS')
if "SECURITY_VIEWER_PASS" in os.environ:
SECURITY_VIEWER_PASS_TMP = os.environ['SECURITY_VIEWER_PASS']
if SECURITY_VIEWER_PASS_TMP and SECURITY_VIEWER_PASS_TMP.strip():
SECURITY_VIEWER_PASS = SECURITY_VIEWER_PASS_TMP
LOGGER.info('Setting SECURITY_VIEWER_PASS')

if "SECURITY_ENABLED" in os.environ:
try:
Expand All @@ -201,10 +244,11 @@ def __str__(self):
'pass': SECURITY_PASS,
'roles': [ADMIN_ROLE_NAME]
}
USERS_INFO[SECURITY_VIEWER_USER] = {
'pass': SECURITY_VIEWER_PASS,
'roles': [VIEWER_ROLE_NAME]
}
if SECURITY_VIEWER_USER is not None and SECURITY_VIEWER_PASS is not None:
USERS_INFO[SECURITY_VIEWER_USER] = {
'pass': SECURITY_VIEWER_PASS,
'roles': [VIEWER_ROLE_NAME]
}
else:
LOGGER.info('Setting SECURITY_ENABLED=0 by default')
else:
Expand Down Expand Up @@ -292,6 +336,24 @@ def get_security_specs():
security_specs[file] = eval(get_file_as_string(file_path)) #pylint: disable=eval-used
return security_specs

def is_endpoint_protected(endpoint):
if MAKE_VIEWER_ENDPOINTS_PUBLIC is False:
return True

for info in ADMIN_ENDPOINTS:
if endpoint == info['endpoint']:
return True
return False

def is_endpoint_swagger_protected(method, path):
if MAKE_VIEWER_ENDPOINTS_PUBLIC is False:
return True

for info in ADMIN_ENDPOINTS:
if info['method'] == method and path == info['path']:
return True
return False

def generate_security_swagger_spec():
try:
security_specs = get_security_specs()
Expand All @@ -309,20 +371,20 @@ def generate_security_swagger_spec():
security_401_response = security_specs['security_unauthorized_response.json']
security_403_response = security_specs['security_forbidden_response.json']
security_crsf = security_specs['security_csrf.json']
for path in data['paths']:
for path in data['paths']: #pylint: disable=too-many-nested-blocks
for method in data['paths'][path]:
if set(ensure_tags) & set(data['paths'][path][method]['tags']):
data['paths'][path][method]['security'] = security_type
data['paths'][path][method]['responses']['401'] = security_401_response
data['paths'][path][method]['responses']['403'] = security_403_response
if method in ['post', 'put', 'patch', 'delete']:
if 'parameters' in data['paths'][path][method]:
params = data['paths'][path][method]['parameters']
params.append(security_crsf)
data['paths'][path][method]['parameters'] = params
else:
data['paths'][path][method]['parameters'] = [security_crsf]

if is_endpoint_swagger_protected(method, path):
if set(ensure_tags) & set(data['paths'][path][method]['tags']):
data['paths'][path][method]['security'] = security_type
data['paths'][path][method]['responses']['401'] = security_401_response
data['paths'][path][method]['responses']['403'] = security_403_response
if method in ['post', 'put', 'patch', 'delete']:
if 'parameters' in data['paths'][path][method]:
params = data['paths'][path][method]['parameters']
params.append(security_crsf)
data['paths'][path][method]['parameters'] = params
else:
data['paths'][path][method]['parameters'] = [security_crsf]
with open("{}/swagger/swagger_security.json".format(STATIC_CONTENT), 'w') as outfile:
json.dump(data, outfile)
except Exception as ex:
Expand Down Expand Up @@ -406,15 +468,17 @@ def jwt_required(fn): #pylint: disable=invalid-name, function-redefined
@wraps(fn)
def wrapper(*args, **kwargs):
if ENABLE_SECURITY_LOGIN:
verify_jwt_in_request()
if is_endpoint_protected(request.endpoint):
verify_jwt_in_request()
return fn(*args, **kwargs)
return wrapper

def jwt_refresh_token_required(fn): #pylint: disable=invalid-name, function-redefined
@wraps(fn)
def wrapper(*args, **kwargs):
if ENABLE_SECURITY_LOGIN:
verify_jwt_refresh_token_in_request()
if is_endpoint_protected(request.endpoint):
verify_jwt_refresh_token_in_request()
return fn(*args, **kwargs)
return wrapper

Expand Down Expand Up @@ -667,6 +731,7 @@ def config_endpoint():
keep_history_latest = os.getenv('KEEP_HISTORY_LATEST', '20')
tls = os.getenv('TLS', '0')
security_enabled = int(ENABLE_SECURITY_LOGIN)
make_viewer_endpoints_public = os.getenv('MAKE_VIEWER_ENDPOINTS_PUBLIC', '0')

body = {
'data': {
Expand All @@ -678,7 +743,8 @@ def config_endpoint():
'security_enabled': security_enabled,
'url_prefix': URL_PREFIX,
'api_response_less_verbose': API_RESPONSE_LESS_VERBOSE,
'optimize_storage': OPTIMIZE_STORAGE
'optimize_storage': OPTIMIZE_STORAGE,
"make_viewer_endpoints_public": make_viewer_endpoints_public
},
'meta_data': {
'message' : "Config successfully obtained"
Expand Down Expand Up @@ -756,7 +822,7 @@ def latest_report_endpoint():
@jwt_required
def send_results_endpoint(): #pylint: disable=too-many-branches
try:
if check_access(ADMIN_ROLE_NAME) is False:
if check_admin_access(current_user, request.endpoint) is False:
return jsonify({ 'meta_data': { 'message': 'Access Forbidden' } }), 403

content_type = str(request.content_type)
Expand Down Expand Up @@ -852,7 +918,7 @@ def send_results_endpoint(): #pylint: disable=too-many-branches
@jwt_required
def generate_report_endpoint():
try:
if check_access(ADMIN_ROLE_NAME) is False:
if check_admin_access(current_user, request.endpoint) is False:
return jsonify({ 'meta_data': { 'message': 'Access Forbidden' } }), 403

project_id = resolve_project(request.args.get('project_id'))
Expand Down Expand Up @@ -945,7 +1011,7 @@ def generate_report_endpoint():
@jwt_required
def clean_history_endpoint():
try:
if check_access(ADMIN_ROLE_NAME) is False:
if check_admin_access(current_user, request.endpoint) is False:
return jsonify({ 'meta_data': { 'message': 'Access Forbidden' } }), 403

project_id = resolve_project(request.args.get('project_id'))
Expand Down Expand Up @@ -986,7 +1052,7 @@ def clean_history_endpoint():
@jwt_required
def clean_results_endpoint():
try:
if check_access(ADMIN_ROLE_NAME) is False:
if check_admin_access(current_user, request.endpoint) is False:
return jsonify({ 'meta_data': { 'message': 'Access Forbidden' } }), 403

project_id = resolve_project(request.args.get('project_id'))
Expand Down Expand Up @@ -1176,7 +1242,7 @@ def report_export_endpoint():
@jwt_required
def create_project_endpoint():
try:
if check_access(ADMIN_ROLE_NAME) is False:
if check_admin_access(current_user, request.endpoint) is False:
return jsonify({ 'meta_data': { 'message': 'Access Forbidden' } }), 403

if not request.is_json:
Expand Down Expand Up @@ -1209,7 +1275,7 @@ def create_project_endpoint():
@jwt_required
def delete_project_endpoint(project_id):
try:
if check_access(ADMIN_ROLE_NAME) is False:
if check_admin_access(current_user, request.endpoint) is False:
return jsonify({ 'meta_data': { 'message': 'Access Forbidden' } }), 403

if project_id == 'default':
Expand Down Expand Up @@ -1536,14 +1602,22 @@ def resolve_project(project_id_param):
project_id = project_id_param
return project_id

def check_access(role):
def check_admin_access(user, endpoint):
if ENABLE_SECURITY_LOGIN is False:
return True

granted = False
if role in current_user.roles:
granted = True
return granted
if is_endpoint_protected(endpoint) is False:
return True
return check_access(ADMIN_ROLE_NAME, user)

def check_access(role, user):
if user.roles is None:
return False

if role in user.roles:
return True

return False

def check_process(process_file, project_id):
tmp = os.popen('ps -Af | grep -w {}'.format(project_id)).read()
Expand Down
3 changes: 2 additions & 1 deletion docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ services:
SECURITY_PASS: "my_password"
SECURITY_VIEWER_USER: "view_user"
SECURITY_VIEWER_PASS: "view_pass"
SECURITY_ENABLED: 0
MAKE_VIEWER_ENDPOINTS_PUBLIC: 0
SECURITY_ENABLED: 1
OPTIMIZE_STORAGE: 0
ports:
- "${ALLURE_DOCKER_SERVICE_API_PORT}:5050"
Expand Down

0 comments on commit c8ee94b

Please sign in to comment.