oauth_data_source.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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 = {"code": code, "grant_type": "authorization_code", "redirect_uri": self.redirect_uri}
  31. headers = {"Accept": "application/json"}
  32. auth = (self.client_id, self.client_secret)
  33. response = requests.post(self._TOKEN_URL, data=data, auth=auth, headers=headers)
  34. response_json = response.json()
  35. access_token = response_json.get("access_token")
  36. if not access_token:
  37. raise ValueError(f"Error in Notion OAuth: {response_json}")
  38. workspace_name = response_json.get("workspace_name")
  39. workspace_icon = response_json.get("workspace_icon")
  40. workspace_id = response_json.get("workspace_id")
  41. # get all authorized pages
  42. pages = self.get_authorized_pages(access_token)
  43. source_info = {
  44. "workspace_name": workspace_name,
  45. "workspace_icon": workspace_icon,
  46. "workspace_id": workspace_id,
  47. "pages": pages,
  48. "total": len(pages),
  49. }
  50. # save data source binding
  51. data_source_binding = DataSourceOauthBinding.query.filter(
  52. db.and_(
  53. DataSourceOauthBinding.tenant_id == current_user.current_tenant_id,
  54. DataSourceOauthBinding.provider == "notion",
  55. DataSourceOauthBinding.access_token == access_token,
  56. )
  57. ).first()
  58. if data_source_binding:
  59. data_source_binding.source_info = source_info
  60. data_source_binding.disabled = False
  61. db.session.commit()
  62. else:
  63. new_data_source_binding = DataSourceOauthBinding(
  64. tenant_id=current_user.current_tenant_id,
  65. access_token=access_token,
  66. source_info=source_info,
  67. provider="notion",
  68. )
  69. db.session.add(new_data_source_binding)
  70. db.session.commit()
  71. def save_internal_access_token(self, access_token: str):
  72. workspace_name = self.notion_workspace_name(access_token)
  73. workspace_icon = None
  74. workspace_id = current_user.current_tenant_id
  75. # get all authorized pages
  76. pages = self.get_authorized_pages(access_token)
  77. source_info = {
  78. "workspace_name": workspace_name,
  79. "workspace_icon": workspace_icon,
  80. "workspace_id": workspace_id,
  81. "pages": pages,
  82. "total": len(pages),
  83. }
  84. # save data source binding
  85. data_source_binding = DataSourceOauthBinding.query.filter(
  86. db.and_(
  87. DataSourceOauthBinding.tenant_id == current_user.current_tenant_id,
  88. DataSourceOauthBinding.provider == "notion",
  89. DataSourceOauthBinding.access_token == access_token,
  90. )
  91. ).first()
  92. if data_source_binding:
  93. data_source_binding.source_info = source_info
  94. data_source_binding.disabled = False
  95. db.session.commit()
  96. else:
  97. new_data_source_binding = DataSourceOauthBinding(
  98. tenant_id=current_user.current_tenant_id,
  99. access_token=access_token,
  100. source_info=source_info,
  101. provider="notion",
  102. )
  103. db.session.add(new_data_source_binding)
  104. db.session.commit()
  105. def sync_data_source(self, binding_id: str):
  106. # save data source binding
  107. data_source_binding = DataSourceOauthBinding.query.filter(
  108. db.and_(
  109. DataSourceOauthBinding.tenant_id == current_user.current_tenant_id,
  110. DataSourceOauthBinding.provider == "notion",
  111. DataSourceOauthBinding.id == binding_id,
  112. DataSourceOauthBinding.disabled == False,
  113. )
  114. ).first()
  115. if data_source_binding:
  116. # get all authorized pages
  117. pages = self.get_authorized_pages(data_source_binding.access_token)
  118. source_info = data_source_binding.source_info
  119. new_source_info = {
  120. "workspace_name": source_info["workspace_name"],
  121. "workspace_icon": source_info["workspace_icon"],
  122. "workspace_id": source_info["workspace_id"],
  123. "pages": pages,
  124. "total": len(pages),
  125. }
  126. data_source_binding.source_info = new_source_info
  127. data_source_binding.disabled = False
  128. db.session.commit()
  129. else:
  130. raise ValueError("Data source binding not found")
  131. def get_authorized_pages(self, access_token: str):
  132. pages = []
  133. page_results = self.notion_page_search(access_token)
  134. database_results = self.notion_database_search(access_token)
  135. # get page detail
  136. for page_result in page_results:
  137. page_id = page_result["id"]
  138. page_name = "Untitled"
  139. for key in page_result["properties"]:
  140. if "title" in page_result["properties"][key] and page_result["properties"][key]["title"]:
  141. title_list = page_result["properties"][key]["title"]
  142. if len(title_list) > 0 and "plain_text" in title_list[0]:
  143. page_name = title_list[0]["plain_text"]
  144. page_icon = page_result["icon"]
  145. if page_icon:
  146. icon_type = page_icon["type"]
  147. if icon_type in {"external", "file"}:
  148. url = page_icon[icon_type]["url"]
  149. icon = {"type": "url", "url": url if url.startswith("http") else f"https://www.notion.so{url}"}
  150. else:
  151. icon = {"type": "emoji", "emoji": page_icon[icon_type]}
  152. else:
  153. icon = None
  154. parent = page_result["parent"]
  155. parent_type = parent["type"]
  156. if parent_type == "block_id":
  157. parent_id = self.notion_block_parent_page_id(access_token, parent[parent_type])
  158. elif parent_type == "workspace":
  159. parent_id = "root"
  160. else:
  161. parent_id = parent[parent_type]
  162. page = {
  163. "page_id": page_id,
  164. "page_name": page_name,
  165. "page_icon": icon,
  166. "parent_id": parent_id,
  167. "type": "page",
  168. }
  169. pages.append(page)
  170. # get database detail
  171. for database_result in database_results:
  172. page_id = database_result["id"]
  173. if len(database_result["title"]) > 0:
  174. page_name = database_result["title"][0]["plain_text"]
  175. else:
  176. page_name = "Untitled"
  177. page_icon = database_result["icon"]
  178. if page_icon:
  179. icon_type = page_icon["type"]
  180. if icon_type in {"external", "file"}:
  181. url = page_icon[icon_type]["url"]
  182. icon = {"type": "url", "url": url if url.startswith("http") else f"https://www.notion.so{url}"}
  183. else:
  184. icon = {"type": icon_type, icon_type: page_icon[icon_type]}
  185. else:
  186. icon = None
  187. parent = database_result["parent"]
  188. parent_type = parent["type"]
  189. if parent_type == "block_id":
  190. parent_id = self.notion_block_parent_page_id(access_token, parent[parent_type])
  191. elif parent_type == "workspace":
  192. parent_id = "root"
  193. else:
  194. parent_id = parent[parent_type]
  195. page = {
  196. "page_id": page_id,
  197. "page_name": page_name,
  198. "page_icon": icon,
  199. "parent_id": parent_id,
  200. "type": "database",
  201. }
  202. pages.append(page)
  203. return pages
  204. def notion_page_search(self, access_token: str):
  205. data = {"filter": {"value": "page", "property": "object"}}
  206. headers = {
  207. "Content-Type": "application/json",
  208. "Authorization": f"Bearer {access_token}",
  209. "Notion-Version": "2022-06-28",
  210. }
  211. response = requests.post(url=self._NOTION_PAGE_SEARCH, json=data, headers=headers)
  212. response_json = response.json()
  213. results = response_json.get("results", [])
  214. return results
  215. def notion_block_parent_page_id(self, access_token: str, block_id: str):
  216. headers = {
  217. "Authorization": f"Bearer {access_token}",
  218. "Notion-Version": "2022-06-28",
  219. }
  220. response = requests.get(url=f"{self._NOTION_BLOCK_SEARCH}/{block_id}", headers=headers)
  221. response_json = response.json()
  222. parent = response_json["parent"]
  223. parent_type = parent["type"]
  224. if parent_type == "block_id":
  225. return self.notion_block_parent_page_id(access_token, parent[parent_type])
  226. return parent[parent_type]
  227. def notion_workspace_name(self, access_token: str):
  228. headers = {
  229. "Authorization": f"Bearer {access_token}",
  230. "Notion-Version": "2022-06-28",
  231. }
  232. response = requests.get(url=self._NOTION_BOT_USER, headers=headers)
  233. response_json = response.json()
  234. if "object" in response_json and response_json["object"] == "user":
  235. user_type = response_json["type"]
  236. user_info = response_json[user_type]
  237. if "workspace_name" in user_info:
  238. return user_info["workspace_name"]
  239. return "workspace"
  240. def notion_database_search(self, access_token: str):
  241. data = {"filter": {"value": "database", "property": "object"}}
  242. headers = {
  243. "Content-Type": "application/json",
  244. "Authorization": f"Bearer {access_token}",
  245. "Notion-Version": "2022-06-28",
  246. }
  247. response = requests.post(url=self._NOTION_PAGE_SEARCH, json=data, headers=headers)
  248. response_json = response.json()
  249. results = response_json.get("results", [])
  250. return results