text: # Copyright 2018 Contributors to Hyperledger Sawtooth
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  # ------------------------------------------------------------------------------
  
  import logging
  import os
  import time
  from datetime import datetime as dt
  
  import requests
  import rethinkdb as r
  
  from rbac.providers.azure.aad_auth import AadAuth
  from rbac.providers.common.inbound_filters import (
      inbound_user_filter,
      inbound_group_filter,
  )
  from rbac.providers.common.common import save_sync_time, check_last_sync
  from rbac.providers.common.rethink_db import connect_to_db
  
  
  logging.basicConfig(level=logging.INFO)
  logging.getLogger("urllib3").setLevel(logging.WARNING)
  LOGGER = logging.getLogger(__name__)
  
  DEFAULT_CONFIG = {"DB_HOST": "rethink", "DB_PORT": "28015", "DB_NAME": "rbac"}
  DB_HOST = os.getenv("DB_HOST", DEFAULT_CONFIG["DB_HOST"])
  DB_PORT = os.getenv("DB_PORT", DEFAULT_CONFIG["DB_PORT"])
  DB_NAME = os.getenv("DB_NAME", DEFAULT_CONFIG["DB_NAME"])
  GRAPH_URL = "https://graph.microsoft.com/beta/"
  
  TENANT_ID = os.getenv("TENANT_ID")
  AUTH = AadAuth()
  
  DELAY = 1
  
  
  def fetch_groups_with_members():
      """Call to get JSON payload for all Groups with membership in Azure Active Directory."""
      headers = AUTH.check_token("GET")
      if headers:
          groups_payload = requests.get(
              url=GRAPH_URL + "groups/?$expand=members", headers=headers
          )
          if groups_payload.status_code == 429:
              return fetch_retry(groups_payload.headers, fetch_groups_with_members)
          if groups_payload.status_code == 200:
              return groups_payload.json()
          LOGGER.error(
              "A %s error has occurred when getting the groups: %s",
              groups_payload.status_code,
              groups_payload,
          )
  
  
  def fetch_group_owner(group_id):
      """Call to get JSON payload for a group's owner in Azure Active Directory."""
      headers = AUTH.check_token("GET")
      if headers:
          owner_payload = requests.get(
              url=GRAPH_URL + "groups/" + group_id + "/owners", headers=headers
          )
          if owner_payload.status_code == 429:
              return fetch_retry(owner_payload.headers, fetch_group_owner, group_id)
          if owner_payload.status_code == 200:
              return owner_payload.json()
          LOGGER.error(
              "A %s error has occurred when getting the groups's owner: %s",
              owner_payload.status_code,
              owner_payload,
          )
  
  
  def fetch_users():
      """Call to get JSON payload for all Users in Azure Active Directory."""
      headers = AUTH.check_token("GET")
      if headers:
          users_payload = requests.get(url=GRAPH_URL + "users", headers=headers)
          if users_payload.status_code == 429:
              return fetch_retry(users_payload.headers, fetch_users)
          if users_payload.status_code == 200:
              return users_payload.json()
          LOGGER.error(
              "A %s error has occurred when getting the users: %s",
              users_payload.status_code,
              users_payload,
          )
  
  
  def fetch_user_manager(user_id):
      """Call to get JSON payload for a user's manager in Azure Active Directory."""
      headers = AUTH.check_token("GET")
      if headers:
          manager_payload = requests.get(
              url=GRAPH_URL + "users/" + user_id + "/manager", headers=headers
          )
          if manager_payload.status_code == 200:
              return manager_payload.json()
          if manager_payload.status_code == 404:
              return None
          if manager_payload.status_code == 429:
              return fetch_retry(manager_payload.headers, fetch_user_manager, user_id)
          LOGGER.error(
              "A %s error has occurred when getting the user's manger: %s",
              manager_payload.status_code,
              manager_payload,
          )
  
  
  def fetch_next_payload(next_url):
      """Get the next payload from the redirect url for large payload pagination"""
      headers = AUTH.check_token("GET")
      if headers:
          payload = requests.get(url=next_url, headers=headers)
          if payload.status_code == 429:
              return fetch_retry(payload.headers, fetch_next_payload, next_url)
          if payload.status_code == 200:
              return payload.json()
          LOGGER.error(
              "A %s error has occurred when getting the paginated payload: %s",
              payload.status_code,
              payload,
          )
  
  
  def fetch_retry(headers, func, *args):
      """Error 429 from Azure is from throttling.  This will wait the allotted time and retry the fetch."""
      if "Retry-After" in headers:
          time.sleep(headers["Retry-After"])
          return func(*args)
      else:
          time.sleep(30)
          return func(*args)
  
  
  def get_ids_from_list_of_dicts(lst):
      """Get all ids out of a list of objects and return a list of string ids."""
      id_list = []
      for item_dict in lst:
          if "id" in item_dict:
              id_list.append(item_dict["id"])
      return id_list
  
  
  def insert_group_to_db(groups_dict):
      """Insert groups individually to rethinkdb from dict of groups"""
      for group in groups_dict["value"]:
          owner = fetch_group_owner(group["id"])
          if owner and "error" not in owner:
              group["owners"] = get_ids_from_list_of_dicts(owner["value"])
          else:
              group["owners"] = []
          group["members"] = get_ids_from_list_of_dicts(group["members"])
          standardized_group = inbound_group_filter(group, "azure")
          inbound_entry = {
              "data": standardized_group,
              "data_type": "group",
              "sync_type": "initial",
              "timestamp": dt.now().isoformat(),
              "provider_id": TENANT_ID,
          }
          r.table("inbound_queue").insert(inbound_entry).run()
  
  
  def insert_user_to_db(users_dict):
      """Insert users individually to rethinkdb from dict of users.  This will also look up a user's manager."""
      for user in users_dict["value"]:
          manager = fetch_user_manager(user["id"])
          if manager:
              user["manager"] = manager["id"]
          else:
              user["manager"] = ""
          standardized_user = inbound_user_filter(user, "azure")
          inbound_entry = {
              "data": standardized_user,
              "data_type": "user",
              "sync_type": "initial",
              "timestamp": dt.now().isoformat(),
              "provider_id": TENANT_ID,
          }
          r.table("inbound_queue").insert(inbound_entry).run()
  
  
  def initialize_aad_sync():
      """Initialize a sync with Azure Active Directory."""
      LOGGER.info("connecting to RethinkDB...")
      connect_to_db()
      LOGGER.info("Successfully connected to RethinkDB!")
      provider_id = TENANT_ID
  
      db_user_payload = check_last_sync("azure-user", "initial")
      if not db_user_payload:
          LOGGER.info(
              "No initial AAD user sync was found. Starting initial AAD user sync now."
          )
  
          LOGGER.info("Getting Users...")
          users = fetch_users()
          if users:
              insert_user_to_db(users)
              while "@odata.nextLink" in users:
                  users = fetch_next_payload(users["@odata.nextLink"])
                  if users:
                      insert_user_to_db(users)
                  else:
                      break
              save_sync_time(provider_id, "azure-user", "initial")
              LOGGER.info("Initial user upload complete :)")
          else:
              LOGGER.info(
                  "An error occurred when uploading users.  Please check the logs."
              )
  
      db_group_payload = check_last_sync("azure-group", "initial")
      if not db_group_payload:
          LOGGER.info(
              "No initial AAD group sync was found. Starting initial AAD group sync now."
          )
          LOGGER.info("Getting Groups with Members...")
          groups = fetch_groups_with_members()
          if groups:
              insert_group_to_db(groups)
              while "@odata.nextLink" in groups:
                  groups = fetch_next_payload(groups["@odata.nextLink"])
                  if groups:
                      insert_group_to_db(groups)
                  else:
                      break
              save_sync_time(provider_id, "azure-group", "initial")
              LOGGER.info("Initial group upload complete :)")
          else:
              LOGGER.info(
                  "An error occurred when uploading groups.  Please check the logs."
              )
  
      if db_group_payload and db_user_payload:
          LOGGER.info("The initial sync has already been run.")
  