Skip to content

Commit

Permalink
feat: use NTLM for authentication in EWS
Browse files Browse the repository at this point in the history
  • Loading branch information
ArtemSBulgakov committed Oct 23, 2024
1 parent 84830d3 commit 4e5316a
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 68 deletions.
116 changes: 77 additions & 39 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ exchangelib = "^5.4.3"
fastapi = "^0.115.0"
fastapi-swagger = "^0.2.3"
gunicorn = "^23.0.0"
httpx = {version = "^0.27.2", extras = ["http2"]}
httpx = {version = "^0.27.2"}
pre-commit = "^4.0.0"
pydantic = "^2.9.2"
pytz = "^2024.2"
pyyaml = "^6.0.2"
requests-curl = {git = "https://github.com/paivett/requests-curl.git", rev = "82a7fc1"}
ruff = "^0.6.9"
uvicorn = "0.30.6"

Expand Down
21 changes: 8 additions & 13 deletions src/modules/bookings/exchange_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,20 @@ class Booking(BaseModel):

class ExchangeBookingRepository:
ews_endpoint: str
username: str
password: str
account_email: str
account: exchangelib.Account

def __init__(self, ews_endpoint: str, username: str, password: str):
def __init__(self, ews_endpoint: str, account_email: str):
self.ews_endpoint = ews_endpoint
self.username = username
self.password = password
self.account_email = account_email

credentials = exchangelib.Credentials(self.username, self.password)
config = exchangelib.Configuration(
credentials=credentials,
auth_type=exchangelib.BASIC,
auth_type=exchangelib.transport.NOAUTH,
service_endpoint=self.ews_endpoint,
)
self.account = exchangelib.Account(
self.username,
credentials=credentials,
self.account_email,
access_type=exchangelib.DELEGATE,
config=config,
autodiscover=False,
)
Expand All @@ -59,7 +55,7 @@ def fetch_bookings(self, room_ids: list[str], start: datetime.datetime, end: dat
merged_free_busy_interval=5,
)
):
if isinstance(busy_info, ErrorMailRecipientNotFound):
if isinstance(busy_info, ErrorMailRecipientNotFound) or busy_info.calendar_events is None:
continue
for calendar_event in busy_info.calendar_events:
bookings.append(
Expand Down Expand Up @@ -88,6 +84,5 @@ def to_msk(dt: datetime.datetime) -> datetime.datetime:

exchange_booking_repository = ExchangeBookingRepository(
ews_endpoint=settings.exchange.ews_endpoint,
username=settings.exchange.username,
password=settings.exchange.password.get_secret_value(),
account_email=settings.exchange.username,
)
53 changes: 38 additions & 15 deletions src/modules/bookings/patch_exchangelib.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
import exchangelib
import httpx
import pycurl
import requests
from requests_curl import CURLAdapter
from requests_curl.request import CURLRequest

from src.config import settings

USERPWD = f"{settings.exchange.username}:{settings.exchange.password.get_secret_value()}"

original_build_curl_options = CURLRequest._build_curl_options


def build_curl_options(self):
curl_options = original_build_curl_options(self)

# Authentication using NTLM
curl_options.update(
{
pycurl.HTTPAUTH: pycurl.HTTPAUTH_NTLM,
pycurl.USERPWD: USERPWD,
}
)

# Let curl handle the POSTFIELDS instead of using READFUNCTION
curl_options.update(
{
pycurl.POSTFIELDS: curl_options[pycurl.READFUNCTION]().decode(),
}
)
curl_options.pop(pycurl.READFUNCTION)
curl_options.pop(pycurl.UPLOAD)
return curl_options


CURLRequest._build_curl_options = build_curl_options


def raw_session(cls, prefix, oauth2_client=None, oauth2_session_params=None, oauth2_token_endpoint=None):
# Use httpx with http2. Return a session that is somewhat compatible with requests.Session
session = httpx.Client(http2=True)
session = requests.Session()
session.mount("http://", CURLAdapter())
session.mount("https://", CURLAdapter())
session.headers.update(exchangelib.protocol.DEFAULT_HEADERS)
session.headers["User-Agent"] = cls.USERAGENT
session.get_adapter = lambda _: None
_post = session.post

def post(url, data, *args, **kwargs):
kwargs.pop("allow_redirects", None)
kwargs.pop("stream", None)
response = _post(url, content=data, *args, **kwargs)
response.iter_content = response.iter_bytes
return response

session.post = post
return session


# Patch exchangelib to use httpx with http2 instead of requests
exchangelib.protocol.BaseProtocol.raw_session = raw_session

0 comments on commit 4e5316a

Please sign in to comment.