File indexing completed on 2026-04-09 07:58:20
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011 """----------------------
0012 Web service app
0013 ----------------------"""
0014
0015 import logging
0016
0017 import flask
0018 from flask import Flask, Response
0019
0020 from idds.common import exceptions
0021
0022 from idds.common.constants import HTTP_STATUS_CODE
0023 from idds.common.utils import get_rest_debug, setup_logging
0024
0025 from idds.core.authentication import authenticate_x509, authenticate_oidc, authenticate_is_super_user
0026
0027 from idds.rest.v1 import requests
0028 from idds.rest.v1 import transforms
0029 from idds.rest.v1 import catalog
0030 from idds.rest.v1 import cacher
0031 from idds.rest.v1 import hyperparameteropt
0032 from idds.rest.v1 import logs
0033 from idds.rest.v1 import monitor
0034 from idds.rest.v1 import messages
0035 from idds.rest.v1 import ping
0036 from idds.rest.v1 import auth
0037 from idds.rest.v1 import metainfo
0038 from idds.rest.v1 import workflowtask
0039
0040
0041 class LoggingMiddleware(object):
0042 def __init__(self, app, logger, url_map):
0043 self._app = app
0044 self._logger = logger
0045 self._url_map = url_map
0046 self._logger.setLevel(logging.DEBUG)
0047
0048 def __call__(self, environ, resp):
0049 import pprint
0050
0051
0052 self._logger.info(pprint.pprint(('URLMAP', self._url_map)))
0053 self._logger.info(pprint.pprint(('REQUEST', environ)))
0054
0055 def log_response(status, headers, *args):
0056
0057 self._logger.info(('RESPONSE', status, headers))
0058 return resp(status, headers, *args)
0059
0060 return self._app(environ, log_response)
0061
0062
0063 def get_normal_blueprints():
0064 bps = []
0065 bps.append(requests.get_blueprint())
0066 bps.append(transforms.get_blueprint())
0067 bps.append(catalog.get_blueprint())
0068 bps.append(cacher.get_blueprint())
0069 bps.append(hyperparameteropt.get_blueprint())
0070 bps.append(logs.get_blueprint())
0071
0072 bps.append(messages.get_blueprint())
0073 bps.append(ping.get_blueprint())
0074 bps.append(metainfo.get_blueprint())
0075 bps.append(workflowtask.get_blueprint())
0076
0077 return bps
0078
0079
0080 def get_auth_blueprints():
0081 bps = []
0082 bps.append(auth.get_blueprint())
0083 bps.append(monitor.get_blueprint())
0084 return bps
0085
0086
0087 def generate_failed_auth_response(exc_msg=None):
0088 resp = Response(response=None, status=HTTP_STATUS_CODE.Unauthorized, content_type='application/json')
0089 resp.headers['ExceptionClass'] = exceptions.IDDSException.__name__
0090 resp.headers['ExceptionMessage'] = exc_msg
0091 return resp
0092
0093
0094 def before_request_auth():
0095
0096
0097
0098
0099 auth_type = flask.request.headers.get('X-IDDS-Auth-Type', default='x509_proxy')
0100 vo = flask.request.headers.get('X-IDDS-Auth-VO', default=None)
0101 if auth_type in ['x509_proxy']:
0102 dn = flask.request.environ.get('SSL_CLIENT_S_DN', None)
0103 client_cert = flask.request.environ.get('SSL_CLIENT_CERT', None)
0104 if dn:
0105 dn = dn.strip()
0106 if client_cert:
0107 client_cert = client_cert.strip()
0108 if not dn or len(dn) == 0:
0109 dn = flask.request.headers.get('SSL-CLIENT-S-DN', default=None)
0110 if not client_cert or len(client_cert) == 0:
0111 client_cert = flask.request.headers.get('SSL-CLIENT-CERT', default=None)
0112
0113 is_authenticated, errors, username = authenticate_x509(vo, dn, client_cert)
0114 if not is_authenticated:
0115 return generate_failed_auth_response(errors)
0116
0117
0118 is_super_user = authenticate_is_super_user(username, dn)
0119 if is_super_user:
0120 original_username = flask.request.headers.get('X-IDDS-Auth-Username-Original', default=None)
0121 original_usercert = flask.request.headers.get('X-IDDS-Auth-Usercert-Original', default=None)
0122 original_userdn = flask.request.headers.get('X-IDDS-Auth-Userdn-Original', default=None)
0123 if original_userdn and original_usercert:
0124 is_authenticated, errors, username = authenticate_x509(vo, original_userdn, original_usercert)
0125 if not is_authenticated:
0126 return generate_failed_auth_response(errors)
0127 elif original_username:
0128 username = original_username
0129
0130 flask.request.environ['username'] = username
0131 elif auth_type in ['oidc']:
0132 token = flask.request.headers.get('X-IDDS-Auth-Token', default=None)
0133 is_authenticated, errors, username = authenticate_oidc(vo, token)
0134 if not is_authenticated:
0135 return generate_failed_auth_response(errors)
0136
0137
0138 is_super_user = authenticate_is_super_user(username)
0139 if is_super_user:
0140 original_username = flask.request.headers.get('X-IDDS-Auth-Username-Original', default=None)
0141 original_usertoken = flask.request.headers.get('X-IDDS-Auth-Usertoken-Original', default=None)
0142 if original_usertoken:
0143 is_authenticated, errors, username = authenticate_oidc(vo, original_usertoken)
0144 if not is_authenticated:
0145 return generate_failed_auth_response(errors)
0146 elif original_username:
0147 username = original_username
0148
0149 flask.request.environ['username'] = username
0150 else:
0151 errors = "Authentication method %s is not supported" % auth_type
0152 return generate_failed_auth_response(errors)
0153
0154
0155 def after_request(response):
0156 return response
0157
0158
0159 def create_app(auth_type=None):
0160
0161 setup_logging(name='idds_app', log_file="idds_rest.log")
0162
0163
0164
0165 application = Flask(__name__)
0166
0167 bps = get_auth_blueprints()
0168 for bp in bps:
0169
0170 application.register_blueprint(bp)
0171
0172 bps = get_normal_blueprints()
0173 for bp in bps:
0174 bp.before_request(before_request_auth)
0175 bp.after_request(after_request)
0176
0177 application.register_blueprint(bp)
0178
0179
0180
0181 if get_rest_debug():
0182 application.wsgi_app = LoggingMiddleware(application.wsgi_app, application.logger, application.url_map)
0183
0184 @application.errorhandler(404)
0185 @application.errorhandler(405)
0186 def _handle_api_error(ex):
0187 status = HTTP_STATUS_CODE.NotFound
0188 if hasattr(ex, 'code'):
0189 status = ex.code
0190 resp = Response(response=None, status=status)
0191 resp.headers['ExceptionClass'] = exceptions.IDDSException.__name__
0192 resp.headers['ExceptionMessage'] = 'The requested REST API is not defined: %s' % ex
0193 return resp
0194
0195 return application