c11_api.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import base64
  2. import os
  3. import requests
  4. from requests_toolbelt.multipart import decoder
  5. import jinja2
  6. import json
  7. import re
  8. from bs4 import BeautifulSoup
  9. from xml_prettify import prettify_xml
  10. import logging
  11. class c11_api:
  12. webservice = ""
  13. templates_dir = "tools/cognos11/templates"
  14. # templates_dir = "C:/GlobalCube/Tasks/gctools/templates"
  15. export_dir = "C:/GlobalCube/ReportOutput"
  16. log_dir = "C:/GlobalCube/Tasks/gctools/logs"
  17. headers = {}
  18. caf = ""
  19. cam = ""
  20. reports = []
  21. folders = []
  22. jobs = []
  23. def __init__(self, webservice="http://localhost:9300/bi/"):
  24. self.webservice = webservice
  25. self._env = jinja2.Environment(
  26. loader=jinja2.FileSystemLoader(self.templates_dir),
  27. autoescape=jinja2.select_autoescape(['html', 'xml'])
  28. )
  29. self.template = self._env.get_template('get_report.xml')
  30. @staticmethod
  31. def generate_token(message_base64):
  32. version = "V1".encode("utf-8")
  33. header_len = 4
  34. msg = base64.b64decode(message_base64)[1:]
  35. chunks = []
  36. while len(msg) >= header_len:
  37. chunk_len = int.from_bytes(msg[:header_len], byteorder="little")
  38. msg = msg[header_len:]
  39. chunks.append(msg[:chunk_len])
  40. msg = msg[chunk_len:]
  41. return base64.b64encode(version + chunks[-1]).decode("utf-8")
  42. def login(self, user='Global1', password='Cognos#11'):
  43. credentials = {
  44. "parameters": [
  45. {"name": "CAMNamespace", "value": "CognosEx"},
  46. {"name": "h_CAM_action", "value": "logonAs"},
  47. {"name": "CAMUsername", "value": user},
  48. {"name": "CAMPassword", "value": password}
  49. ]
  50. }
  51. self.session = requests.Session()
  52. r = self.session.get(self.webservice)
  53. self.headers = {'Content-Type': "application/json; charset=UTF-8", 'X-XSRF-TOKEN': self.session.cookies.get('XSRF-TOKEN')}
  54. r = self.session.post(self.webservice + "v1/login", data=json.dumps(credentials), headers=self.headers)
  55. self.caf = r.json()['cafContextId']
  56. self.cam = self.generate_token(r.cookies["usersessionid"])
  57. return r.status_code
  58. def get_folders(self):
  59. if len(self.folders) == 0:
  60. self.load_folder_list()
  61. return self.folders
  62. def load_folder_list(self, folder_id='_dot_public_folders', prefix='Team Content'):
  63. res = self.session.get(f"{self.webservice}v1/objects/{folder_id}/items", headers=self.headers)
  64. folder_list = res.json()['data']
  65. for f in folder_list:
  66. if f['type'] == 'folder':
  67. folder = {
  68. 'id': f['id'],
  69. 'name': prefix + '/' + f['defaultName'].replace('/', '_')
  70. }
  71. self.folders.append(folder)
  72. self.load_folder_list(folder['id'], folder['name'])
  73. elif f['type'] == 'report':
  74. report = {
  75. 'id': f['id'],
  76. 'name': f['defaultName'],
  77. 'path': prefix
  78. }
  79. self.reports.append(report)
  80. elif f['type'] == 'jobDefinition':
  81. job = {
  82. 'id': f['id'],
  83. 'name': f['defaultName'],
  84. 'path': prefix
  85. }
  86. job['details'] = self.get_job_details(job['id'])
  87. self.jobs.append(job)
  88. def get_job_details(self, job_id):
  89. fields = ",".join([
  90. 'userInterfaces,disabled',
  91. 'runInAdvancedViewer,modificationTime,canBurst',
  92. 'defaultPortalAction',
  93. 'base.defaultName,tags,target.searchPath,target.disabled',
  94. 'options,base.options'
  95. ])
  96. res = self.session.get(f"{self.webservice}v1/objects/{job_id}?fields={fields}", headers=self.headers)
  97. job = res.json()['data'][0]
  98. job.pop('_meta', None)
  99. job.pop('id', None)
  100. job.pop('type', None)
  101. job.pop('defaultName', None)
  102. fields2 = ",".join([
  103. r'id,displaySequence,stepObject{defaultName}',
  104. r'stepObject{id},stepObject{parameters}',
  105. r'stepObject{canBurst},options,parameters'
  106. ])
  107. res = self.session.get(
  108. f"{self.webservice}v1/objects/{job_id}/items?types=jobStepDefinition&fields={fields2}",
  109. headers=self.headers
  110. )
  111. steps = res.json()['data']
  112. for s in steps:
  113. s.pop('_meta', None)
  114. if s['stepObject'] is not None:
  115. s['report_id'] = s['stepObject'][0]['id']
  116. s.pop('stepObject', None)
  117. job['steps'] = steps
  118. return job
  119. def get_report(self, report_id):
  120. self.get_folders()
  121. report = [r for r in self.reports if r['id'] == report_id]
  122. if len(report) == 0:
  123. return None
  124. report = report[0]
  125. if 'meta' not in report:
  126. report = self.get_report_specs(report)
  127. return report
  128. def get_reports_in_folder(self, folder, recursive=False):
  129. self.get_folders()
  130. if recursive:
  131. return [r for r in self.reports if r['path'].startswith(folder)]
  132. return [r for r in self.reports if r['path'] == folder]
  133. def get_report_specs(self, report):
  134. headers = {
  135. 'Content-Type': 'text/xml; charset=UTF-8',
  136. 'X-XSRF-TOKEN': self.headers['X-XSRF-TOKEN'],
  137. 'X-RsCMStoreID': report['id'],
  138. 'X-UseRsConsumerMode': 'true',
  139. 'SOAPAction': 'http://www.ibm.com/xmlns/prod/cognos/reportService/202004/'
  140. }
  141. soap = self.template.render({"caf": self.caf, "cam": self.cam,
  142. "report": report, "format": 'XHTML',
  143. "prompt": 'true', "tracking": "", "params": {}})
  144. r = self.session.post(self.webservice + 'v1/reports', data=soap, headers=headers)
  145. if r.status_code == 500:
  146. bs = BeautifulSoup(r.text, 'xml')
  147. report['error'] = bs.find_all('messageString')[0].string
  148. logging.error(report['error'])
  149. return report
  150. parts = decoder.MultipartDecoder.from_response(r).parts
  151. meta = {'required': {}, 'optional': {}}
  152. bs = BeautifulSoup(parts[1].content, 'lxml')
  153. for sv in bs.find_all('selectvalue'):
  154. k = sv['parameter']
  155. req = 'required' if sv['required'] == 'true' else 'optional'
  156. v = dict([(opt['usevalue'], opt['displayvalue']) for opt in sv.find_all('selectoption')])
  157. meta[req][k] = v
  158. for sv in bs.find_all('selectdate'):
  159. k = sv['parameter']
  160. req = 'required' if sv['required'] == 'true' else 'optional'
  161. v = dict([(opt['usevalue'], opt.get('displayvalue', '')) for opt in sv.find_all('selectoption')])
  162. meta[req][k] = v
  163. filename = self.log_dir + f"/config/{report['path']}/{report['name']}.json"
  164. os.makedirs(os.path.dirname(filename), exist_ok=True)
  165. json.dump(meta, open(filename, 'w'), indent=2)
  166. report['meta'] = meta
  167. report['spec'] = parts[2].text
  168. path = report['path'].replace('Team Content/ReportOutput', '')
  169. report['filename'] = f"{self.export_dir}/{path}/{report['name']}.pdf"
  170. report['params'] = list(re.findall(r'\[([^\]]+)\]', report['filename']))
  171. for i, p in enumerate(report['params']):
  172. report['filename'] = report['filename'].replace('[' + p + ']', '{' + str(i) + '}')
  173. return report
  174. def export_unstubbed(self, report_id):
  175. report = self.get_report(report_id)
  176. if 'spec' not in report:
  177. return False
  178. payload = json.dumps({'reportspec_stubbed': report['spec'], 'storeid': report['id']})
  179. headers = {
  180. 'Content-Type': 'application/json; charset=UTF-8',
  181. 'X-XSRF-TOKEN': self.session.cookies.get('XSRF-TOKEN'),
  182. 'authenticityToken': self.cam,
  183. 'X-UseRsConsumerMode': 'true',
  184. 'cafContextId': self.caf
  185. }
  186. r = self.session.post(self.webservice + 'v1/reports/unstubreport', data=payload, headers=headers)
  187. unstubbed = json.loads(r.text)['reportspec_full']
  188. unstubbed = re.sub(r' iid="[^"]*"', '', unstubbed)
  189. bs = BeautifulSoup(unstubbed, 'xml')
  190. for xa in bs.find_all('XMLAttributes'):
  191. if (
  192. xa.find_all('XMLAttribute', {'name': 'RS_dataType'}) or
  193. xa.find_all('XMLAttribute', {'name': 'RS_CreateExtendedDataItems'}) or
  194. xa.find_all('XMLAttribute', {'name': 'RS_legacyDrillDown'})
  195. ):
  196. continue
  197. if xa.find_all('XMLAttribute', {'name': 'supportsDefaultDataFormatting'}):
  198. for xa2 in xa.find_all('XMLAttribute'):
  199. if xa2.attrs['name'] != 'supportsDefaultDataFormatting':
  200. xa2.decompose()
  201. continue
  202. xa.decompose()
  203. for cti in bs.find_all('crosstabIntersection'):
  204. if len(list(cti.children)) == 0:
  205. cti.decompose()
  206. unstubbed_report = str(bs).replace("'", ''')
  207. unstubbed_report = prettify_xml(unstubbed_report)
  208. filename = self.log_dir + f"/config/{report['path']}/{report['name']}.xml"
  209. os.makedirs(os.path.dirname(filename), exist_ok=True)
  210. with open(filename, "w") as f:
  211. f.write(unstubbed_report)
  212. return unstubbed_report
  213. def get_report_headers(self, report_id):
  214. return {
  215. 'Content-Type': 'text/xml; charset=UTF-8',
  216. 'X-XSRF-TOKEN': self.headers['X-XSRF-TOKEN'],
  217. 'X-RsCMStoreID': report_id,
  218. 'X-UseRsConsumerMode': 'true',
  219. 'SOAPAction': 'http://www.ibm.com/xmlns/prod/cognos/reportService/202004/'
  220. }
  221. def request_file(self, report_id, params, format='PDF'):
  222. report = self.get_report(report_id)
  223. headers = self.get_report_headers(report_id)
  224. soap = self.template.render({"caf": self.caf, "cam": self.cam,
  225. "report": report, "format": format,
  226. "prompt": 'false', "tracking": "", "params": params}).encode("utf-8")
  227. r = self.session.post(self.webservice + 'v1/reports', data=soap, headers=headers)
  228. bs = BeautifulSoup(r.text, 'xml')
  229. if r.status_code == 200:
  230. try:
  231. parts = decoder.MultipartDecoder.from_response(r).parts
  232. except decoder.NonMultipartContentTypeException:
  233. return 500, 'Timeout'
  234. return 200, parts[1].content
  235. error = bs.find_all('messageString')[0].string
  236. logging.debug(error)
  237. return r.status_code, error
  238. if __name__ == '__main__':
  239. api = c11_api()
  240. api.login()
  241. folders = api.get_folders()
  242. filename = 'C:/GlobalCube/Tasks/gctools/logs/config/folders.json'
  243. json.dump(folders, open(filename, 'w'), indent=2)
  244. filename = 'C:/GlobalCube/Tasks/gctools/logs/config/reports.json'
  245. json.dump(api.reports, open(filename, 'w'), indent=2)
  246. filename = 'C:/GlobalCube/Tasks/gctools/logs/config/jobs.json'
  247. json.dump(api.jobs, open(filename, 'w'), indent=2)