oauth_data_source.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. import urllib.parse
  2. import requests
  3. from flask_login import current_user
  4. from extensions.ext_database import db
  5. from models.source import DataSourceOauthBinding
  6. class OAuthDataSource:
  7. def __init__(self, client_id: str, client_secret: str, redirect_uri: str):
  8. self.client_id = client_id
  9. self.client_secret = client_secret
  10. self.redirect_uri = redirect_uri
  11. def get_authorization_url(self):
  12. raise NotImplementedError()
  13. def get_access_token(self, code: str):
  14. raise NotImplementedError()
  15. class NotionOAuth(OAuthDataSource):
  16. _AUTH_URL = 'https://api.notion.com/v1/oauth/authorize'
  17. _TOKEN_URL = 'https://api.notion.com/v1/oauth/token'
  18. _NOTION_PAGE_SEARCH = "https://api.notion.com/v1/search"
  19. _NOTION_BLOCK_SEARCH = "https://api.notion.com/v1/blocks"
  20. _NOTION_BOT_USER = "https://api.notion.com/v1/users/me"
  21. def get_authorization_url(self):
  22. params = {
  23. 'client_id': self.client_id,
  24. 'response_type': 'code',
  25. 'redirect_uri': self.redirect_uri,
  26. 'owner': 'user'
  27. }
  28. return f"{self._AUTH_URL}?{urllib.parse.urlencode(params)}"
  29. def get_access_token(self, code: str):
  30. data = {
  31. 'code': code,
  32. 'grant_type': 'authorization_code',
  33. 'redirect_uri': self.redirect_uri
  34. }
  35. headers = {'Accept': 'application/json'}
  36. auth = (self.client_id, self.client_secret)
  37. response = requests.post(self._TOKEN_URL, data=data, auth=auth, headers=headers)
  38. response_json = response.json()
  39. access_token = response_json.get('access_token')
  40. if not access_token:
  41. raise ValueError(f"Error in Notion OAuth: {response_json}")
  42. workspace_name = response_json.get('workspace_name')
  43. workspace_icon = response_json.get('workspace_icon')
  44. workspace_id = response_json.get('workspace_id')
  45. # get all authorized pages
  46. pages = self.get_authorized_pages(access_token)
  47. source_info = {
  48. 'workspace_name': workspace_name,
  49. 'workspace_icon': workspace_icon,
  50. 'workspace_id': workspace_id,
  51. 'pages': pages,
  52. 'total': len(pages)
  53. }
  54. # save data source binding
  55. data_source_binding = DataSourceOauthBinding.query.filter(
  56. db.and_(
  57. DataSourceOauthBinding.tenant_id == current_user.current_tenant_id,
  58. DataSourceOauthBinding.provider == 'notion',
  59. DataSourceOauthBinding.access_token == access_token
  60. )
  61. ).first()
  62. if data_source_binding:
  63. data_source_binding.source_info = source_info
  64. data_source_binding.disabled = False
  65. db.session.commit()
  66. else:
  67. new_data_source_binding = DataSourceOauthBinding(
  68. tenant_id=current_user.current_tenant_id,
  69. access_token=access_token,
  70. source_info=source_info,
  71. provider='notion'
  72. )
  73. db.session.add(new_data_source_binding)
  74. db.session.commit()
  75. def save_internal_access_token(self, access_token: str):
  76. workspace_name = self.notion_workspace_name(access_token)
  77. workspace_icon = None
  78. workspace_id = current_user.current_tenant_id
  79. # get all authorized pages
  80. pages = self.get_authorized_pages(access_token)
  81. source_info = {
  82. 'workspace_name': workspace_name,
  83. 'workspace_icon': workspace_icon,
  84. 'workspace_id': workspace_id,
  85. 'pages': pages,
  86. 'total': len(pages)
  87. }
  88. # save data source binding
  89. data_source_binding = DataSourceOauthBinding.query.filter(
  90. db.and_(
  91. DataSourceOauthBinding.tenant_id == current_user.current_tenant_id,
  92. DataSourceOauthBinding.provider == 'notion',
  93. DataSourceOauthBinding.access_token == access_token
  94. )
  95. ).first()
  96. if data_source_binding:
  97. data_source_binding.source_info = source_info
  98. data_source_binding.disabled = False
  99. db.session.commit()
  100. else:
  101. new_data_source_binding = DataSourceOauthBinding(
  102. tenant_id=current_user.current_tenant_id,
  103. access_token=access_token,
  104. source_info=source_info,
  105. provider='notion'
  106. )
  107. db.session.add(new_data_source_binding)
  108. db.session.commit()
  109. def sync_data_source(self, binding_id: str):
  110. # save data source binding
  111. data_source_binding = DataSourceOauthBinding.query.filter(
  112. db.and_(
  113. DataSourceOauthBinding.tenant_id == current_user.current_tenant_id,
  114. DataSourceOauthBinding.provider == 'notion',
  115. DataSourceOauthBinding.id == binding_id,
  116. DataSourceOauthBinding.disabled == False
  117. )
  118. ).first()
  119. if data_source_binding:
  120. # get all authorized pages
  121. pages = self.get_authorized_pages(data_source_binding.access_token)
  122. source_info = data_source_binding.source_info
  123. new_source_info = {
  124. 'workspace_name': source_info['workspace_name'],
  125. 'workspace_icon': source_info['workspace_icon'],
  126. 'workspace_id': source_info['workspace_id'],
  127. 'pages': pages,
  128. 'total': len(pages)
  129. }
  130. data_source_binding.source_info = new_source_info
  131. data_source_binding.disabled = False
  132. db.session.commit()
  133. else:
  134. raise ValueError('Data source binding not found')
  135. def get_authorized_pages(self, access_token: str):
  136. pages = []
  137. page_results = self.notion_page_search(access_token)
  138. database_results = self.notion_database_search(access_token)
  139. # get page detail
  140. for page_result in page_results:
  141. page_id = page_result['id']
  142. page_name = 'Untitled'
  143. for key in page_result['properties']:
  144. if 'title' in page_result['properties'][key] and page_result['properties'][key]['title']:
  145. title_list = page_result['properties'][key]['title']
  146. if len(title_list) > 0 and 'plain_text' in title_list[0]:
  147. page_name = title_list[0]['plain_text']
  148. page_icon = page_result['icon']
  149. if page_icon:
  150. icon_type = page_icon['type']
  151. if icon_type == 'external' or icon_type == 'file':
  152. url = page_icon[icon_type]['url']
  153. icon = {
  154. 'type': 'url',
  155. 'url': url if url.startswith('http') else f'https://www.notion.so{url}'
  156. }
  157. else:
  158. icon = {
  159. 'type': 'emoji',
  160. 'emoji': page_icon[icon_type]
  161. }
  162. else:
  163. icon = None
  164. parent = page_result['parent']
  165. parent_type = parent['type']
  166. if parent_type == 'block_id':
  167. parent_id = self.notion_block_parent_page_id(access_token, parent[parent_type])
  168. elif parent_type == 'workspace':
  169. parent_id = 'root'
  170. else:
  171. parent_id = parent[parent_type]
  172. page = {
  173. 'page_id': page_id,
  174. 'page_name': page_name,
  175. 'page_icon': icon,
  176. 'parent_id': parent_id,
  177. 'type': 'page'
  178. }
  179. pages.append(page)
  180. # get database detail
  181. for database_result in database_results:
  182. page_id = database_result['id']
  183. if len(database_result['title']) > 0:
  184. page_name = database_result['title'][0]['plain_text']
  185. else:
  186. page_name = 'Untitled'
  187. page_icon = database_result['icon']
  188. if page_icon:
  189. icon_type = page_icon['type']
  190. if icon_type == 'external' or icon_type == 'file':
  191. url = page_icon[icon_type]['url']
  192. icon = {
  193. 'type': 'url',
  194. 'url': url if url.startswith('http') else f'https://www.notion.so{url}'
  195. }
  196. else:
  197. icon = {
  198. 'type': icon_type,
  199. icon_type: page_icon[icon_type]
  200. }
  201. else:
  202. icon = None
  203. parent = database_result['parent']
  204. parent_type = parent['type']
  205. if parent_type == 'block_id':
  206. parent_id = self.notion_block_parent_page_id(access_token, parent[parent_type])
  207. elif parent_type == 'workspace':
  208. parent_id = 'root'
  209. else:
  210. parent_id = parent[parent_type]
  211. page = {
  212. 'page_id': page_id,
  213. 'page_name': page_name,
  214. 'page_icon': icon,
  215. 'parent_id': parent_id,
  216. 'type': 'database'
  217. }
  218. pages.append(page)
  219. return pages
  220. def notion_page_search(self, access_token: str):
  221. data = {
  222. 'filter': {
  223. "value": "page",
  224. "property": "object"
  225. }
  226. }
  227. headers = {
  228. 'Content-Type': 'application/json',
  229. 'Authorization': f"Bearer {access_token}",
  230. 'Notion-Version': '2022-06-28',
  231. }
  232. response = requests.post(url=self._NOTION_PAGE_SEARCH, json=data, headers=headers)
  233. response_json = response.json()
  234. results = response_json.get('results', [])
  235. return results
  236. def notion_block_parent_page_id(self, access_token: str, block_id: str):
  237. headers = {
  238. 'Authorization': f"Bearer {access_token}",
  239. 'Notion-Version': '2022-06-28',
  240. }
  241. response = requests.get(url=f'{self._NOTION_BLOCK_SEARCH}/{block_id}', headers=headers)
  242. response_json = response.json()
  243. parent = response_json['parent']
  244. parent_type = parent['type']
  245. if parent_type == 'block_id':
  246. return self.notion_block_parent_page_id(access_token, parent[parent_type])
  247. return parent[parent_type]
  248. def notion_workspace_name(self, access_token: str):
  249. headers = {
  250. 'Authorization': f"Bearer {access_token}",
  251. 'Notion-Version': '2022-06-28',
  252. }
  253. response = requests.get(url=self._NOTION_BOT_USER, headers=headers)
  254. response_json = response.json()
  255. if 'object' in response_json and response_json['object'] == 'user':
  256. user_type = response_json['type']
  257. user_info = response_json[user_type]
  258. if 'workspace_name' in user_info:
  259. return user_info['workspace_name']
  260. return 'workspace'
  261. def notion_database_search(self, access_token: str):
  262. data = {
  263. 'filter': {
  264. "value": "database",
  265. "property": "object"
  266. }
  267. }
  268. headers = {
  269. 'Content-Type': 'application/json',
  270. 'Authorization': f"Bearer {access_token}",
  271. 'Notion-Version': '2022-06-28',
  272. }
  273. response = requests.post(url=self._NOTION_PAGE_SEARCH, json=data, headers=headers)
  274. response_json = response.json()
  275. results = response_json.get('results', [])
  276. return results