1717from metadata .generated .schema .entity .services .connections .database .sasConnection import (
1818 SASConnection ,
1919)
20+ from metadata .generated .schema .security .ssl .verifySSLConfig import VerifySSL
2021from metadata .ingestion .connections .source_api_client import TrackedREST
2122from metadata .ingestion .ometa .client import APIError , ClientConfig
2223from metadata .utils .helpers import clean_uri
2324from metadata .utils .logger import ingestion_logger
25+ from metadata .utils .ssl_registry import get_verify_ssl_fn
2426
2527logger = ingestion_logger ()
2628
29+ SAS_CLI_AUTH_HEADER = "Basic c2FzLmNsaTo="
30+
2731
2832class SASClient :
2933 """
@@ -33,13 +37,14 @@ class SASClient:
3337 def __init__ (self , config : SASConnection ):
3438 self .config : SASConnection = config
3539 self .auth_token = self .get_token (config .serverHost , config .username , config .password .get_secret_value ())
40+
3641 client_config : ClientConfig = ClientConfig (
3742 base_url = clean_uri (config .serverHost ),
3843 auth_header = "Authorization" ,
3944 auth_token = self .get_auth_token ,
4045 api_version = "" ,
4146 allow_redirects = True ,
42- verify = False ,
47+ verify = self . _get_verify () ,
4348 )
4449 self .client = TrackedREST (client_config , source_name = "sas" )
4550 # custom setting
@@ -50,6 +55,22 @@ def __init__(self, config: SASConnection):
5055 self .enable_dataflows = config .dataflows
5156 self .custom_filter_dataflows = config .dataflowsCustomFilter
5257
58+ def _get_verify (self ):
59+ """
60+ Helper to determine the SSL verification strategy
61+ """
62+ verify = True
63+ if self .config .verifySSL == VerifySSL .ignore :
64+ verify = False
65+ elif self .config .verifySSL == VerifySSL .no_ssl :
66+ verify = False
67+ elif self .config .verifySSL == VerifySSL .validate and self .config .sslConfig :
68+ try :
69+ verify = get_verify_ssl_fn (self .config .verifySSL )(self .config .sslConfig )
70+ except Exception : # pylint: disable=broad-except
71+ verify = True
72+ return verify
73+
5374 def check_connection (self ):
5475 """
5576 Check metadata connection to SAS
@@ -71,8 +92,8 @@ def get_instance(self, instance_id):
7192 "Accept" : "application/vnd.sas.metadata.instance.entity.detail+json" ,
7293 }
7394 response = self .client .get (path = endpoint , headers = headers )
74- if "error" in response . keys (): # noqa: SIM118
75- raise APIError (response ["error" ])
95+ if response and isinstance ( response , dict ) and "error" in response :
96+ raise APIError ({ "message" : response ["error" ]} )
7697 return response
7798
7899 def get_information_catalog_link (self , instance_id ):
@@ -98,8 +119,8 @@ def list_assets(self, assets):
98119 endpoint = f"catalog/search?indices={ assets } &q={ asset_filter if str (asset_filter ) != 'None' else '*' } "
99120 headers = {"Accept-Item" : "application/vnd.sas.metadata.instance.entity+json" }
100121 response = self .client .get (path = endpoint , headers = headers )
101- if "error" in response . keys (): # noqa: SIM118
102- raise APIError (response ["error" ])
122+ if response and isinstance ( response , dict ) and "error" in response :
123+ raise APIError ({ "message" : response ["error" ]} )
103124 return response ["items" ]
104125
105126 def get_views (self , query ):
@@ -111,7 +132,7 @@ def get_views(self, query):
111132 logger .info (f"{ query } " )
112133 response = self .client .post (path = endpoint , data = query , headers = headers )
113134 if "error" in response .keys (): # noqa: SIM118
114- raise APIError (f" { response } " )
135+ raise APIError ({ "message" : "Error fetching views from SAS" } )
115136 return response
116137
117138 def get_data_source (self , endpoint ):
@@ -120,8 +141,8 @@ def get_data_source(self, endpoint):
120141 }
121142 response = self .client .get (path = endpoint , headers = headers )
122143 logger .info (f"{ response } " )
123- if "error" in response . keys (): # noqa: SIM118
124- raise APIError (response ["error" ])
144+ if response and isinstance ( response , dict ) and "error" in response :
145+ raise APIError ({ "message" : response ["error" ]} )
125146 return response
126147
127148 def get_report_link (self , resource , uri ):
@@ -135,8 +156,8 @@ def load_table(self, endpoint):
135156 def get_report_relationship (self , report_id ):
136157 endpoint = f"reports/commons/relationships/reports/{ report_id } "
137158 response = self .client .get (endpoint )
138- if "error" in response . keys (): # noqa: SIM118
139- raise APIError (response ["error" ])
159+ if response and isinstance ( response , dict ) and "error" in response :
160+ raise APIError ({ "message" : response ["error" ]} )
140161 dependencies = []
141162 for item in response ["items" ]:
142163 if item ["type" ] == "Dependent" :
@@ -145,15 +166,15 @@ def get_report_relationship(self, report_id):
145166
146167 def get_resource (self , endpoint ):
147168 response = self .client .get (endpoint )
148- if "error" in response . keys (): # noqa: SIM118
149- raise APIError (response ["error" ])
169+ if response and isinstance ( response , dict ) and "error" in response :
170+ raise APIError ({ "message" : response ["error" ]} )
150171 return response
151172
152173 def get_instances_with_param (self , data ):
153174 endpoint = f"catalog/instances?{ data } "
154175 response = self .client .get (endpoint )
155- if "error" in response . keys (): # noqa: SIM118
156- raise APIError (response ["error" ])
176+ if response and isinstance ( response , dict ) and "error" in response :
177+ raise APIError ({ "message" : response ["error" ]} )
157178 return response ["items" ]
158179
159180 def get_auth_token (self ):
@@ -164,8 +185,31 @@ def get_token(self, base_url, user, password):
164185 payload = {"grant_type" : "password" , "username" : user , "password" : password }
165186 headers = {
166187 "Content-type" : "application/x-www-form-urlencoded" ,
167- "Authorization" : "Basic c2FzLmNsaTo=" ,
188+ "Authorization" : SAS_CLI_AUTH_HEADER ,
168189 }
169190 url = base_url + endpoint
170- response = requests .request ("POST" , url , headers = headers , data = payload , verify = False , timeout = 10 )
171- return response .json ()["access_token" ]
191+
192+ response = requests .request (
193+ "POST" ,
194+ url ,
195+ headers = headers ,
196+ data = payload ,
197+ verify = self ._get_verify (),
198+ timeout = 10 ,
199+ )
200+ logger .debug (
201+ "Token request for user: %s completed with status: %s" ,
202+ user ,
203+ response .status_code ,
204+ )
205+ try :
206+ body = response .json ()
207+ except ValueError as exc :
208+ response .raise_for_status ()
209+ raise RuntimeError (f"SAS token endpoint returned non-JSON response (HTTP { response .status_code } )" ) from exc
210+
211+ response .raise_for_status ()
212+ token = body .get ("access_token" )
213+ if not token :
214+ raise RuntimeError (f"Failed to retrieve access_token from SAS (HTTP { response .status_code } )" )
215+ return token
0 commit comments