-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Billing #2667
Billing #2667
Changes from 21 commits
ab9e548
d015a02
30559b0
9efd372
8a726fb
7859d97
85f8fff
9809295
cdafb32
518e0ef
c356812
2f0b625
cf14be7
3f13dd6
516af73
18f7a8f
a1289bd
e0f4e1f
36c31eb
a91b534
afae460
6faad9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
FROM python:3.11.7-slim-bookworm | ||
|
||
LABEL com.danswer.maintainer="[email protected]" | ||
LABEL com.danswer.description="This image is the web/frontend container of Danswer which \ | ||
contains code for both the Community and Enterprise editions of Danswer. If you do not \ | ||
have a contract or agreement with DanswerAI, you are not permitted to use the Enterprise \ | ||
Edition features outside of personal development or testing purposes. Please reach out to \ | ||
[email protected] for more information. Please visit https://github.com/danswer-ai/danswer" | ||
|
||
# Default DANSWER_VERSION, typically overriden during builds by GitHub Actions. | ||
ARG DANSWER_VERSION=0.3-dev | ||
ENV DANSWER_VERSION=${DANSWER_VERSION} \ | ||
DANSWER_RUNNING_IN_DOCKER="true" | ||
|
||
RUN echo "DANSWER_VERSION: ${DANSWER_VERSION}" | ||
# Install system dependencies | ||
# cmake needed for psycopg (postgres) | ||
# libpq-dev needed for psycopg (postgres) | ||
# curl included just for users' convenience | ||
# zip for Vespa step futher down | ||
# ca-certificates for HTTPS | ||
RUN apt-get update && \ | ||
apt-get install -y \ | ||
cmake \ | ||
curl \ | ||
zip \ | ||
ca-certificates \ | ||
libgnutls30=3.7.9-2+deb12u3 \ | ||
libblkid1=2.38.1-5+deb12u1 \ | ||
libmount1=2.38.1-5+deb12u1 \ | ||
libsmartcols1=2.38.1-5+deb12u1 \ | ||
libuuid1=2.38.1-5+deb12u1 \ | ||
libxmlsec1-dev \ | ||
pkg-config \ | ||
gcc && \ | ||
rm -rf /var/lib/apt/lists/* && \ | ||
apt-get clean | ||
|
||
# Install Python dependencies | ||
# Remove py which is pulled in by retry, py is not needed and is a CVE | ||
COPY ./requirements/default.txt /tmp/requirements.txt | ||
COPY ./requirements/ee.txt /tmp/ee-requirements.txt | ||
RUN pip install --no-cache-dir --upgrade \ | ||
--retries 5 \ | ||
--timeout 30 \ | ||
-r /tmp/requirements.txt \ | ||
-r /tmp/ee-requirements.txt && \ | ||
pip uninstall -y py && \ | ||
playwright install chromium && \ | ||
playwright install-deps chromium && \ | ||
ln -s /usr/local/bin/supervisord /usr/bin/supervisord | ||
|
||
# Cleanup for CVEs and size reduction | ||
# https://github.com/tornadoweb/tornado/issues/3107 | ||
# xserver-common and xvfb included by playwright installation but not needed after | ||
# perl-base is part of the base Python Debian image but not needed for Danswer functionality | ||
# perl-base could only be removed with --allow-remove-essential | ||
RUN apt-get update && \ | ||
apt-get remove -y --allow-remove-essential \ | ||
perl-base \ | ||
xserver-common \ | ||
xvfb \ | ||
cmake \ | ||
libldap-2.5-0 \ | ||
libxmlsec1-dev \ | ||
pkg-config \ | ||
gcc && \ | ||
apt-get install -y libxmlsec1-openssl && \ | ||
apt-get autoremove -y && \ | ||
rm -rf /var/lib/apt/lists/* && \ | ||
rm -f /usr/local/lib/python3.11/site-packages/tornado/test/test.key | ||
|
||
# Pre-downloading models for setups with limited egress | ||
RUN python -c "from tokenizers import Tokenizer; \ | ||
Tokenizer.from_pretrained('nomic-ai/nomic-embed-text-v1')" | ||
|
||
|
||
# Pre-downloading NLTK for setups with limited egress | ||
RUN python -c "import nltk; \ | ||
nltk.download('stopwords', quiet=True); \ | ||
nltk.download('punkt', quiet=True);" | ||
# nltk.download('wordnet', quiet=True); introduce this back if lemmatization is needed | ||
|
||
# Set up application files | ||
WORKDIR /app | ||
|
||
# Enterprise Version Files | ||
COPY ./ee /app/ee | ||
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf | ||
|
||
# Set up application files | ||
COPY ./danswer /app/danswer | ||
COPY ./shared_configs /app/shared_configs | ||
COPY ./alembic /app/alembic | ||
COPY ./alembic_tenants /app/alembic_tenants | ||
COPY ./alembic.ini /app/alembic.ini | ||
COPY supervisord.conf /usr/etc/supervisord.conf | ||
|
||
# Escape hatch | ||
COPY ./scripts/force_delete_connector_by_id.py /app/scripts/force_delete_connector_by_id.py | ||
|
||
# Put logo in assets | ||
COPY ./assets /app/assets | ||
|
||
ENV PYTHONPATH=/app | ||
|
||
# Default command which does nothing | ||
# This container is used by api server and background which specify their own CMD | ||
CMD ["tail", "-f", "/dev/null"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,13 +4,13 @@ | |
from fastapi.dependencies.models import Dependant | ||
from starlette.routing import BaseRoute | ||
|
||
from danswer.auth.users import control_plane_dep | ||
from danswer.auth.users import current_admin_user | ||
from danswer.auth.users import current_curator_or_admin_user | ||
from danswer.auth.users import current_user | ||
from danswer.auth.users import current_user_with_expired_token | ||
from danswer.configs.app_configs import APP_API_PREFIX | ||
from danswer.server.danswer_api.ingestion import api_key_dep | ||
from ee.danswer.server.tenants.access import control_plane_dep | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should not import ee modules from the MIT stuff. It confuses the separation between the versions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are other places this is done but we should address those. |
||
|
||
|
||
PUBLIC_ENDPOINT_SPECS = [ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ | |
from datetime import timezone | ||
|
||
import jwt | ||
from email_validator import EmailNotValidError | ||
from email_validator import EmailUndeliverableError | ||
from email_validator import validate_email | ||
from fastapi import APIRouter | ||
from fastapi import Body | ||
|
@@ -35,6 +37,7 @@ | |
from danswer.configs.app_configs import SESSION_EXPIRE_TIME_SECONDS | ||
from danswer.configs.app_configs import VALID_EMAIL_DOMAINS | ||
from danswer.configs.constants import AuthType | ||
from danswer.db.auth import get_total_users | ||
from danswer.db.engine import current_tenant_id | ||
from danswer.db.engine import get_session | ||
from danswer.db.models import AccessToken | ||
|
@@ -60,6 +63,7 @@ | |
from ee.danswer.db.api_key import is_api_key_email_address | ||
from ee.danswer.db.external_perm import delete_user__ext_group_for_user__no_commit | ||
from ee.danswer.db.user_group import remove_curator_status__no_commit | ||
from ee.danswer.server.tenants.billing import register_tenant_users | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here and around this line, it should not import from ee |
||
from ee.danswer.server.tenants.provisioning import add_users_to_tenant | ||
from ee.danswer.server.tenants.provisioning import remove_users_from_tenant | ||
|
||
|
@@ -174,19 +178,29 @@ def list_all_users( | |
def bulk_invite_users( | ||
emails: list[str] = Body(..., embed=True), | ||
current_user: User | None = Depends(current_admin_user), | ||
db_session: Session = Depends(get_session), | ||
) -> int: | ||
"""emails are string validated. If any email fails validation, no emails are | ||
invited and an exception is raised.""" | ||
|
||
if current_user is None: | ||
raise HTTPException( | ||
status_code=400, detail="Auth is disabled, cannot invite users" | ||
) | ||
|
||
tenant_id = current_tenant_id.get() | ||
|
||
normalized_emails = [] | ||
for email in emails: | ||
email_info = validate_email(email) # can raise EmailNotValidError | ||
normalized_emails.append(email_info.normalized) # type: ignore | ||
try: | ||
for email in emails: | ||
email_info = validate_email(email) | ||
normalized_emails.append(email_info.normalized) # type: ignore | ||
|
||
except (EmailUndeliverableError, EmailNotValidError): | ||
raise HTTPException( | ||
status_code=400, | ||
detail="One or more emails in the list are invalid", | ||
) | ||
|
||
if MULTI_TENANT: | ||
try: | ||
|
@@ -199,30 +213,58 @@ def bulk_invite_users( | |
) | ||
raise | ||
|
||
all_emails = list(set(normalized_emails) | set(get_invited_users())) | ||
initial_invited_users = get_invited_users() | ||
|
||
if MULTI_TENANT and ENABLE_EMAIL_INVITES: | ||
try: | ||
for email in all_emails: | ||
send_user_email_invite(email, current_user) | ||
except Exception as e: | ||
logger.error(f"Error sending email invite to invited users: {e}") | ||
all_emails = list(set(normalized_emails) | set(initial_invited_users)) | ||
number_of_invited_users = write_invited_users(all_emails) | ||
|
||
return write_invited_users(all_emails) | ||
if not MULTI_TENANT: | ||
return number_of_invited_users | ||
try: | ||
logger.info("Registering tenant users") | ||
register_tenant_users(current_tenant_id.get(), get_total_users(db_session)) | ||
if ENABLE_EMAIL_INVITES: | ||
try: | ||
for email in all_emails: | ||
send_user_email_invite(email, current_user) | ||
except Exception as e: | ||
logger.error(f"Error sending email invite to invited users: {e}") | ||
|
||
return number_of_invited_users | ||
except Exception as e: | ||
logger.error(f"Failed to register tenant users: {str(e)}") | ||
logger.info( | ||
"Reverting changes: removing users from tenant and resetting invited users" | ||
) | ||
write_invited_users(initial_invited_users) # Reset to original state | ||
remove_users_from_tenant(normalized_emails, tenant_id) | ||
raise e | ||
|
||
|
||
@router.patch("/manage/admin/remove-invited-user") | ||
def remove_invited_user( | ||
user_email: UserByEmail, | ||
_: User | None = Depends(current_admin_user), | ||
db_session: Session = Depends(get_session), | ||
) -> int: | ||
user_emails = get_invited_users() | ||
remaining_users = [user for user in user_emails if user != user_email.user_email] | ||
|
||
tenant_id = current_tenant_id.get() | ||
remove_users_from_tenant([user_email.user_email], tenant_id) | ||
number_of_invited_users = write_invited_users(remaining_users) | ||
|
||
try: | ||
if MULTI_TENANT: | ||
register_tenant_users(current_tenant_id.get(), get_total_users(db_session)) | ||
except Exception: | ||
logger.error( | ||
"Request to update number of seats taken in control plane failed. " | ||
"This may cause synchronization issues/out of date enforcement of seat limits." | ||
) | ||
raise | ||
|
||
return write_invited_users(remaining_users) | ||
return number_of_invited_users | ||
|
||
|
||
@router.patch("/manage/admin/deactivate-user") | ||
|
@@ -421,7 +463,6 @@ def get_current_token_creation( | |
|
||
@router.get("/me") | ||
def verify_user_logged_in( | ||
request: Request, | ||
user: User | None = Depends(optional_user), | ||
db_session: Session = Depends(get_session), | ||
) -> UserInfo: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of the comments and things are not correct. I would say this just should not be used by the community.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's remove this one