const description = formData.get('description');
if (!title) {
- alert('Please fill all the required fields.');
+ alert('Please enter a title for your suggestion.');
return;
}
@@ -322,19 +360,21 @@
Suggest us Features
'X-CSRFToken': formData.get('csrfmiddlewaretoken'),
'Content-Type': 'application/json'
},
- body: JSON.stringify({title: title,description: description}),
+ body: JSON.stringify({
+ title: title,
+ description: description
+ }),
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
form.reset();
+ location.reload();
} else {
- alert('Error: Please fill all the fields.');
+ alert('Error: Please fill all required fields.');
}
})
- .catch((error) => {
- console.error('Error:', error);
- });
+ .catch(error => console.error('Error:', error));
}
{% endblock content %}
diff --git a/website/views/core.py b/website/views/core.py
index 02f8db300..e89f1aa79 100644
--- a/website/views/core.py
+++ b/website/views/core.py
@@ -521,14 +521,13 @@ def set_vote_status(request):
return JsonResponse({"success": False, "error": "Invalid request method"}, status=400)
-@login_required
def add_suggestions(request):
if request.method == "POST":
- user = request.user
+ user = request.user if request.user.is_authenticated else None
data = json.loads(request.body)
title = data.get("title")
description = data.get("description", "")
- if title and description and user:
+ if title and description:
suggestion = Suggestion(user=user, title=title, description=description)
suggestion.save()
messages.success(request, "Suggestion added successfully.")
@@ -536,6 +535,8 @@ def add_suggestions(request):
else:
messages.error(request, "Please fill all the fields.")
return JsonResponse({"status": "error"}, status=400)
+ else:
+ return JsonResponse({"status": "error", "message": "Method not allowed"}, status=405)
class GoogleLogin(SocialLoginView):
From b51339cf64375d90cf5a6677401609b6a4908335 Mon Sep 17 00:00:00 2001
From: Krishna Kaushal <104532938+tsu-ki@users.noreply.github.com>
Date: Sun, 19 Jan 2025 21:26:25 +0530
Subject: [PATCH 02/13] Creating slack-bot for OWASP slack workspace (#3145)
* slackbot initial commit
* slack bot integration
* major changes
* resolving CI/CD
* CI/CD
* fixed pre-commit
* fixing
---
.env.example | 3 +-
blt/urls.py | 4 +-
website/views/company.py | 4 +-
website/views/slackbot.py | 408 ++++++++++++++++++++++++++++++++++++++
4 files changed, 416 insertions(+), 3 deletions(-)
create mode 100644 website/views/slackbot.py
diff --git a/.env.example b/.env.example
index 2dd50371d..4302469c6 100644
--- a/.env.example
+++ b/.env.example
@@ -29,7 +29,8 @@ SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
-
+SLACK_BOT_TOKEN=
+SLACK_SIGNING_SECRET=
#BlueSky User Details
BLUESKY_USERNAME=example.bsky.social
diff --git a/blt/urls.py b/blt/urls.py
index adfa7f302..286b4e691 100644
--- a/blt/urls.py
+++ b/blt/urls.py
@@ -187,6 +187,7 @@
select_contribution,
)
from website.views.slack_handlers import slack_events
+from website.views.slackbot import slack_commands
from website.views.teams import (
TeamOverview,
add_member,
@@ -289,7 +290,8 @@
re_path(r"^auth/github/connect/$", GithubConnect.as_view(), name="github_connect"),
re_path(r"^auth/google/connect/$", GoogleConnect.as_view(), name="google_connect"),
path("auth/github/url/", github_views.oauth2_login),
- path("oauth/slack/callback/", SlackCallbackView.as_view(), name="slack_callback"),
+ path("oauth/slack/callback/", SlackCallbackView.as_view(), name="slack_oauth_callback"),
+ path("slack/commands/", slack_commands, name="slack_commands"),
path("auth/google/url/", google_views.oauth2_login),
path("auth/facebook/url/", facebook_views.oauth2_callback),
path("socialaccounts/", SocialAccountListView.as_view(), name="social_account_list"),
diff --git a/website/views/company.py b/website/views/company.py
index 71122a552..4d94868e7 100644
--- a/website/views/company.py
+++ b/website/views/company.py
@@ -1020,7 +1020,9 @@ def exchange_code_for_token(self, code, request):
client_secret = os.getenv("SLACK_CLIENT_SECRET")
host = request.get_host()
scheme = request.META.get("HTTP_X_FORWARDED_PROTO", request.scheme)
- redirect_uri = f"{scheme}://{host}/oauth/slack/callback"
+ redirect_uri = os.environ.get(
+ "OAUTH_REDIRECT_URL", f"{request.scheme}://{request.get_host()}/oauth/slack/callback"
+ )
url = "https://slack.com/api/oauth.v2.access"
data = {
diff --git a/website/views/slackbot.py b/website/views/slackbot.py
new file mode 100644
index 000000000..5ab7d502e
--- /dev/null
+++ b/website/views/slackbot.py
@@ -0,0 +1,408 @@
+import logging
+import math
+import os
+import re
+import time
+
+import requests
+from django.http import HttpResponse, JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from slack_bolt import App
+from slack_bolt.adapter.django import SlackRequestHandler
+
+if os.getenv("ENV") != "production":
+ from dotenv import load_dotenv
+
+ load_dotenv()
+
+logging.basicConfig(level=logging.DEBUG)
+logger = logging.getLogger(__name__)
+
+SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
+SLACK_SIGNING_SECRET = os.environ.get("SLACK_SIGNING_SECRET")
+
+if not SLACK_BOT_TOKEN or not SLACK_SIGNING_SECRET:
+ logger.warning("Slack environment not set. Slack integration disabled.")
+ app = None
+ handler = None
+else:
+ app = App(token=SLACK_BOT_TOKEN, signing_secret=SLACK_SIGNING_SECRET)
+ handler = SlackRequestHandler(app)
+
+pagination_data = {}
+
+repo_cache = {"timestamp": 0, "data": []}
+CACHE_DURATION = 3600
+
+
+def get_all_owasp_repos():
+ """Fetch ALL repos from the OWASP org by paginating through the results."""
+ current_time = time.time()
+ if repo_cache["data"] and (current_time - repo_cache["timestamp"] < CACHE_DURATION):
+ logger.debug("Using cached OWASP repositories.")
+ return repo_cache["data"]
+
+ all_repos = []
+ page = 1
+ while True:
+ resp = requests.get(
+ f"https://api.github.com/orgs/OWASP/repos?page={page}&per_page=100",
+ headers={"Accept": "application/vnd.github.mercy-preview+json"},
+ )
+ if resp.status_code != 200:
+ logger.error(f"Failed to fetch repos (page {page}): {resp.text}")
+ break
+
+ page_data = resp.json()
+ if not page_data:
+ break # no more repositories
+ all_repos.extend(page_data)
+ page += 1
+
+ repo_cache["data"] = all_repos
+ repo_cache["timestamp"] = current_time
+ logger.debug("Fetched and cached OWASP repositories.")
+
+ return all_repos
+
+
+if app:
+
+ @app.command("/discover")
+ def handle_discover_command(ack, client, command):
+ try:
+ ack()
+
+ # Extract the search term from the command text
+ search_term = command.get("text", "").strip()
+
+ # If search term exists then search for OWASP projects
+ if search_term:
+ repos = get_all_owasp_repos()
+ if not repos:
+ send_dm(client, command["user_id"], "Failed to fetch OWASP repositories.")
+ return
+
+ matched = []
+ url_pattern = re.compile(r"https?://\S+")
+
+ for idx, repo in enumerate(repos, start=1):
+ name_desc = (repo["name"] + " " + (repo["description"] or "")).lower()
+ lang = (repo["language"] or "").lower()
+ topics = [t.lower() for t in repo.get("topics", [])]
+
+ if (
+ search_term.lower() in name_desc
+ or search_term.lower() in lang
+ or search_term.lower() in topics
+ ):
+ desc = repo["description"] or "No description provided."
+
+ found_urls = url_pattern.findall(desc)
+ if found_urls:
+ link = found_urls[0]
+ link_label = "Website"
+ else:
+ link = f"https://owasp.org/www-project-{repo['name'].lower()}"
+ link_label = "Wiki"
+
+ matched.append(
+ {
+ "owner_repo": repo["full_name"],
+ "name": repo["name"],
+ "description": desc,
+ "link_label": link_label,
+ "link": link,
+ "html_url": repo["html_url"],
+ }
+ )
+
+ if not matched:
+ send_dm(
+ client,
+ command["user_id"],
+ f"No OWASP projects found matching '{search_term}'.",
+ )
+ return
+
+ pagination_data[command["user_id"]] = {
+ "matched": matched,
+ "current_page": 0,
+ "page_size": 8,
+ }
+
+ send_paged_results(client, command["user_id"], search_term)
+
+ else:
+ try:
+ client.conversations_join(channel=command["channel_id"])
+ except Exception as channel_error:
+ logger.debug(f"Could not join channel: {channel_error}")
+ pass
+
+ try:
+ gh_response = requests.get("https://api.github.com/orgs/OWASP-BLT/repos")
+ if gh_response.status_code == 200:
+ repos = gh_response.json()
+ if not repos:
+ send_dm(
+ client, command["user_id"], "No repositories found for OWASP-BLT."
+ )
+ else:
+ repo_list = []
+ for idx, repo in enumerate(repos, start=1):
+ desc = (
+ repo["description"]
+ if repo["description"]
+ else "No description provided."
+ )
+ repo_list.append(
+ f"{idx}. <{repo['html_url']}|{repo['name']}> - {desc}"
+ )
+
+ blocks = [
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": "Here are the OWASP BLT project repositories:\n"
+ + "\n".join(repo_list),
+ },
+ },
+ {
+ "type": "actions",
+ "elements": [
+ {
+ "type": "static_select",
+ "placeholder": {
+ "type": "plain_text",
+ "text": "Select a repository to view issues",
+ },
+ "options": [
+ {
+ "text": {
+ "type": "plain_text",
+ "text": f"{repo['name']}",
+ },
+ "value": f"OWASP-BLT/{repo['name']}",
+ }
+ for repo in repos
+ ],
+ "action_id": "select_repository",
+ }
+ ],
+ },
+ ]
+
+ send_dm(
+ client,
+ command["user_id"],
+ "Please select a repository to view its latest issues:",
+ blocks,
+ )
+ else:
+ send_dm(
+ client,
+ command["user_id"],
+ "Failed to fetch repositories from OWASP-BLT.",
+ )
+
+ except Exception as e:
+ logger.error(f"Error processing repositories: {e}")
+ send_dm(
+ client,
+ command["user_id"],
+ "An error occurred while processing your request.",
+ )
+
+ except Exception as e:
+ logger.error(f"Error handling /discover command: {e}")
+
+ app.action("select_repository")
+
+ def handle_repository_selection(ack, body, client):
+ try:
+ ack()
+ user_id = body["user"]["id"]
+ selected_repo = body["actions"][0]["selected_option"]["value"]
+ logger.debug(f"User {user_id} selected repository: {selected_repo}")
+
+ # Fetch latest issues from the selected GitHub repository
+ issues_response = requests.get(f"https://api.github.com/repos/{selected_repo}/issues")
+ if issues_response.status_code == 200:
+ issues = issues_response.json()
+ issues = [issue for issue in issues if "pull_request" not in issue]
+ if not issues:
+ send_dm(client, user_id, "No issues found for this repository.")
+ else:
+ issues_list = [
+ f"- <{issue['html_url']}|{issue['title']}> (#{issue['number']})"
+ for issue in issues[:5]
+ ]
+ issues_text = "Here are the latest issues:\n" + "\n".join(issues_list)
+ send_dm(client, user_id, issues_text)
+
+ else:
+ send_dm(client, user_id, "Failed to fetch issues for the selected repository.")
+
+ except Exception as e:
+ logger.error(f"Error handling repository selection: {e}")
+
+ @app.action("pagination_prev")
+ def handle_pagination_prev(ack, body, client):
+ """Handles the 'Previous' pagination button."""
+ try:
+ ack()
+ user_id = body["user"]["id"]
+ search_term = body.get("state", {}).get("values", {}).get("search_term", "Topic")
+
+ if user_id not in pagination_data:
+ send_dm(client, user_id, "No pagination data found.")
+ return
+
+ data = pagination_data[user_id]
+ data["current_page"] = max(0, data["current_page"] - 1)
+
+ send_paged_results(client, user_id, search_term)
+
+ except Exception as e:
+ logger.error(f"Error handling pagination action: {e}")
+
+ @app.action("pagination_next")
+ def handle_pagination_next(ack, body, client):
+ """Handles the 'Next' pagination button"""
+ try:
+ ack()
+ user_id = body["user"]["id"]
+ search_term = body.get("state", {}).get("values", {}).get("search_term", "Topic")
+
+ if user_id not in pagination_data:
+ send_dm(client, user_id, "No pagination data found.")
+ return
+
+ data = pagination_data[user_id]
+ data["current_page"] += 1
+
+ total_pages = math.ceil(len(data["matched"]) / data["page_size"])
+
+ data["current_page"] = min(data["current_page"], total_pages - 1)
+ send_paged_results(client, user_id, search_term)
+
+ except Exception as e:
+ logger.error(f"Error handling pagination action: {e}")
+
+ def send_paged_results(client, user_id, search_term):
+ """Sends the current page of matched projects to the user with next/prev buttons if needed."""
+ data = pagination_data[user_id]
+ matched = data["matched"]
+ page_size = data["page_size"]
+ total_pages = math.ceil(len(matched) / page_size)
+ current_page = data["current_page"]
+
+ start_idx = current_page * page_size
+ end_idx = start_idx + page_size
+ chunk = matched[start_idx:end_idx]
+
+ text_chunk = "\n".join(
+ [
+ f"{idx + start_idx + 1}. <{project['html_url']}|{project['name']}> - {project['description']}\n {project['link_label']}: <{project['link']}|Link>"
+ for idx, project in enumerate(chunk)
+ ]
+ )
+
+ blocks = [
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": (
+ f"Here are the OWASP Projects matching *{search_term}* "
+ f"(page {current_page + 1}/{total_pages}):\n{text_chunk}"
+ ),
+ },
+ },
+ {"type": "actions", "elements": []},
+ ]
+
+ # Add Prev button if not on the first page already
+ if current_page > 0:
+ blocks[1]["elements"].append(
+ {
+ "type": "button",
+ "text": {"type": "plain_text", "text": "Previous"},
+ "value": "PREV",
+ "action_id": "pagination_prev",
+ }
+ )
+
+ # Next button if not on last page already
+ if current_page < (total_pages - 1):
+ blocks[1]["elements"].append(
+ {
+ "type": "button",
+ "text": {"type": "plain_text", "text": "Next"},
+ "value": "NEXT",
+ "action_id": "pagination_next",
+ }
+ )
+
+ options = [
+ {
+ "text": {"type": "plain_text", "text": project["name"]},
+ "value": project["owner_repo"],
+ }
+ for project in chunk
+ ]
+
+ # Also keep the static_select for issues
+ if options:
+ blocks[1]["elements"].append(
+ {
+ "type": "static_select",
+ "placeholder": {
+ "type": "plain_text",
+ "text": "Select a repository to view its latest issues",
+ },
+ "options": options,
+ "action_id": "select_repository",
+ }
+ )
+ send_dm(client, user_id, f"Found {len(matched)} matching OWASP projects.", blocks)
+
+ def send_dm(client, user_id, text, blocks=None):
+ """Utility function to open a DM channel with user and send them a message."""
+ try:
+ dm_response = client.conversations_open(users=[user_id])
+ if not dm_response["ok"]:
+ logger.error(f"Failed to open DM channel: {dm_response['error']}")
+ return
+
+ dm_channel_id = dm_response["channel"]["id"]
+ message_response = client.chat_postMessage(
+ channel=dm_channel_id,
+ text=text,
+ blocks=blocks,
+ mrkdwn=True,
+ unfurl_links=False,
+ unfurl_media=False,
+ )
+ if not message_response["ok"]:
+ logger.error(f"Failed to send DM: {message_response.get('error', 'Unknown error')}")
+ return
+
+ logger.debug(f"Successfully sent DM to user {user_id} in channel {dm_channel_id}")
+
+ except Exception as e:
+ logger.error(f"Error sending DM to user {user_id}: {e}")
+
+
+@csrf_exempt
+def slack_commands(request):
+ logger.debug(f"Received Slack command with content type: {request.content_type}")
+ if not handler:
+ return JsonResponse({"error": "Slack integration is disabled."}, status=400)
+ if request.method == "POST":
+ if request.content_type != "application/x-www-form-urlencoded":
+ return JsonResponse({"error": "Invalid content type"}, status=415)
+ return HttpResponse(handler.handle(request))
+ return JsonResponse({"error": "Method not allowed"}, status=405)
From ca4368099930d11babb532b838784b4db2114fcd Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 20 Jan 2025 00:22:36 +0000
Subject: [PATCH 03/13] chore(deps): Bump django-simple-captcha from 0.6.0 to
0.6.1
Bumps [django-simple-captcha](https://github.com/mbi/django-simple-captcha) from 0.6.0 to 0.6.1.
- [Release notes](https://github.com/mbi/django-simple-captcha/releases)
- [Changelog](https://github.com/mbi/django-simple-captcha/blob/master/CHANGES)
- [Commits](https://github.com/mbi/django-simple-captcha/compare/v0.6.0...v0.6.1)
---
updated-dependencies:
- dependency-name: django-simple-captcha
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
poetry.lock | 9 +++++----
pyproject.toml | 2 +-
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 87a16f4ca..0e5750448 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1220,18 +1220,19 @@ hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"]
[[package]]
name = "django-simple-captcha"
-version = "0.6.0"
+version = "0.6.1"
description = "A very simple, yet powerful, Django captcha application"
optional = false
python-versions = "*"
files = [
- {file = "django-simple-captcha-0.6.0.tar.gz", hash = "sha256:d188516d326fadd2d5ad076eb89649d55c02cabafe3fdcc2154ac18e9f6d4b97"},
- {file = "django_simple_captcha-0.6.0-py2.py3-none-any.whl", hash = "sha256:3ae9a7e650cb0cdbcfd4a75aa91fdf25dcc523ef541a7b1f004bd4357798fc03"},
+ {file = "django_simple_captcha-0.6.1-py2.py3-none-any.whl", hash = "sha256:75c4a89cf54011be54dd96fc4bfc2286f92bed88c46d247b9e504b7fc4c7cfe5"},
+ {file = "django_simple_captcha-0.6.1.tar.gz", hash = "sha256:9df5cf24e38b91af62d601f8e5189b0f6d28dbaa55a363a8b9d727703f9c35de"},
]
[package.dependencies]
Django = ">=4.2"
django-ranged-response = "0.2.0"
+djangorestframework = ">=3.15.0"
Pillow = ">=6.2.0"
[package.extras]
@@ -5020,4 +5021,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.0"
python-versions = "3.11.2"
-content-hash = "780e52e5dabecef51e218b0095e719e55efd253cd2da92ce4c64c9412a66c18e"
+content-hash = "2e10bc9deac62aa184f495199d5e750dfa93a00cfeed1592cbbc72154ee71306"
diff --git a/pyproject.toml b/pyproject.toml
index 085a622ff..4f1b2b803 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -56,7 +56,7 @@ stripe = "^8.4.0"
django-environ = "^0.12.0"
django-humanize = "^0.1.2"
drf-yasg = "^1.21.8"
-django-simple-captcha = "^0.6.0"
+django-simple-captcha = "^0.6.1"
django-filter = "^24.3"
webdriver-manager = "^4.0.2"
pillow = "^10.4.0"
From 3932b23682ebba304e51756948db17467b3fcbf3 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 21 Jan 2025 00:45:35 +0000
Subject: [PATCH 04/13] chore(deps): Bump unstructured from 0.16.13 to 0.16.14
Bumps [unstructured](https://github.com/Unstructured-IO/unstructured) from 0.16.13 to 0.16.14.
- [Release notes](https://github.com/Unstructured-IO/unstructured/releases)
- [Changelog](https://github.com/Unstructured-IO/unstructured/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Unstructured-IO/unstructured/compare/0.16.13...0.16.14)
---
updated-dependencies:
- dependency-name: unstructured
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
poetry.lock | 8 ++++----
pyproject.toml | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 0e5750448..a67d8e2f5 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -4509,13 +4509,13 @@ files = [
[[package]]
name = "unstructured"
-version = "0.16.13"
+version = "0.16.14"
description = "A library that prepares raw documents for downstream ML tasks."
optional = false
python-versions = "<3.13,>=3.9.0"
files = [
- {file = "unstructured-0.16.13-py3-none-any.whl", hash = "sha256:d578d3ebd78c6bf3ea837a13b7e2942671920f9e7361e8532c5eb00f9cf359e6"},
- {file = "unstructured-0.16.13.tar.gz", hash = "sha256:6195744a203e65bf6b8460cbfccd9bef67a1f5d44e79229a13e7e37f528abbcd"},
+ {file = "unstructured-0.16.14-py3-none-any.whl", hash = "sha256:7b3c2eb21e65d2f61240de7a5241fd7734d97be2c9cfa5f70934e10470318131"},
+ {file = "unstructured-0.16.14.tar.gz", hash = "sha256:cec819461090226cd478429c1e0fda19a66ba49ab9ade1ea1fd9ec79c279d7ac"},
]
[package.dependencies]
@@ -5021,4 +5021,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.0"
python-versions = "3.11.2"
-content-hash = "2e10bc9deac62aa184f495199d5e750dfa93a00cfeed1592cbbc72154ee71306"
+content-hash = "725d9cc170028170f65cbcddc8b14056aa3908bf6eba0560112bdcc66e022c85"
diff --git a/pyproject.toml b/pyproject.toml
index 4f1b2b803..4d7be395b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -65,7 +65,7 @@ sentry-sdk = "^2.20.0"
bitcash = "^1.0.2"
pydantic = "^2.10.5"
pydantic_core = "^2.18.4"
-unstructured = "^0.16.13"
+unstructured = "^0.16.14"
Markdown = "^3.6"
faiss-cpu = "^1.8.0"
psutil = "^5.9.8"
From 492aa7a74fceeeba620e9462a1235bb5a901ed0b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 21 Jan 2025 00:54:44 +0000
Subject: [PATCH 05/13] chore(deps): Bump selenium from 4.27.1 to 4.28.0
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.27.1 to 4.28.0.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/commits/selenium-4.28.0)
---
updated-dependencies:
- dependency-name: selenium
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
poetry.lock | 10 +++++-----
pyproject.toml | 2 +-
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index a67d8e2f5..5badaf308 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -4111,13 +4111,13 @@ wrapt = ">=1.10,<2.0"
[[package]]
name = "selenium"
-version = "4.27.1"
+version = "4.28.0"
description = "Official Python bindings for Selenium WebDriver"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
files = [
- {file = "selenium-4.27.1-py3-none-any.whl", hash = "sha256:b89b1f62b5cfe8025868556fe82360d6b649d464f75d2655cb966c8f8447ea18"},
- {file = "selenium-4.27.1.tar.gz", hash = "sha256:5296c425a75ff1b44d0d5199042b36a6d1ef76c04fb775b97b40be739a9caae2"},
+ {file = "selenium-4.28.0-py3-none-any.whl", hash = "sha256:3d6a2e8e1b850a1078884ea19f4e011ecdc12263434d87a0b78769836fb82dd8"},
+ {file = "selenium-4.28.0.tar.gz", hash = "sha256:a9fae6eef48d470a1b0c6e45185d96f0dafb025e8da4b346cc41e4da3ac54fa0"},
]
[package.dependencies]
@@ -5021,4 +5021,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.0"
python-versions = "3.11.2"
-content-hash = "725d9cc170028170f65cbcddc8b14056aa3908bf6eba0560112bdcc66e022c85"
+content-hash = "7dcea59c7a5133a3d027748f6fd77ef6d250dbf6ba09ff82535ce7606d2b25f7"
diff --git a/pyproject.toml b/pyproject.toml
index 4d7be395b..e6b12dbc5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,7 +25,7 @@ Unidecode = "^1.3.8"
user-agents = "^2.2.0"
whitenoise = "^6.8.2"
django-debug-toolbar = "^4.4.6"
-selenium = "^4.27.1"
+selenium = "^4.28.0"
pylibmc = "^1.6.1"
psycopg2-binary = "^2.9.10"
boto = "^2.49.0"
From e82b168b2434d511d6dfc5e03449a158b109e471 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 23 Jan 2025 00:51:32 +0000
Subject: [PATCH 06/13] chore(deps): Bump tablib from 3.7.0 to 3.8.0
Bumps [tablib](https://github.com/jazzband/tablib) from 3.7.0 to 3.8.0.
- [Release notes](https://github.com/jazzband/tablib/releases)
- [Changelog](https://github.com/jazzband/tablib/blob/master/HISTORY.md)
- [Commits](https://github.com/jazzband/tablib/compare/v3.7.0...v3.8.0)
---
updated-dependencies:
- dependency-name: tablib
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
poetry.lock | 8 ++++----
pyproject.toml | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 5badaf308..0c5da61b2 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -4323,13 +4323,13 @@ files = [
[[package]]
name = "tablib"
-version = "3.7.0"
+version = "3.8.0"
description = "Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)"
optional = false
python-versions = ">=3.9"
files = [
- {file = "tablib-3.7.0-py3-none-any.whl", hash = "sha256:9a6930037cfe0f782377963ca3f2b1dae3fd4cdbf0883848f22f1447e7bb718b"},
- {file = "tablib-3.7.0.tar.gz", hash = "sha256:f9db84ed398df5109bd69c11d46613d16cc572fb9ad3213f10d95e2b5f12c18e"},
+ {file = "tablib-3.8.0-py3-none-any.whl", hash = "sha256:35bdb9d4ec7052232f8803908f9c7a9c3c65807188b70618fa7a7d8ccd560b4d"},
+ {file = "tablib-3.8.0.tar.gz", hash = "sha256:94d8bcdc65a715a0024a6d5b701a5f31e45bd159269e62c73731de79f048db2b"},
]
[package.extras]
@@ -5021,4 +5021,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.0"
python-versions = "3.11.2"
-content-hash = "7dcea59c7a5133a3d027748f6fd77ef6d250dbf6ba09ff82535ce7606d2b25f7"
+content-hash = "6a485f0828d702e51ebfe96f113838203009e972b36edf24b4aef7264d036d02"
diff --git a/pyproject.toml b/pyproject.toml
index e6b12dbc5..3a4dd018d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,7 @@ pytz = "^2024.1"
requests = "^2.32.3"
requests-oauthlib = "^1.3.1"
six = "^1.16.0"
-tablib = "^3.2.0"
+tablib = "^3.8.0"
ua-parser = "^1.0.0"
djangorestframework = "^3.15.2"
cffi = "^1.17.1"
From ea90582dcccef377f2606f7ad500b60f5e6b748f Mon Sep 17 00:00:00 2001
From: Altafur Rahman
Date: Fri, 24 Jan 2025 01:32:19 +0600
Subject: [PATCH 07/13] Enhance Slack integration and configuration (#3256)
* Enhance Slack integration and configuration
* Add welcome message field to SlackIntegration model and update related views and tests
---
.env.example | 8 +-
blt/urls.py | 3 +-
.../0181_slackintegration_welcome_message.py | 21 +
website/models.py | 6 +
.../organization/add_slack_integration.html | 47 +++
website/test_slack.py | 175 ++++----
website/views/company.py | 21 +-
website/views/slack_handlers.py | 380 ++++++++++++------
8 files changed, 457 insertions(+), 204 deletions(-)
create mode 100644 website/migrations/0181_slackintegration_welcome_message.py
diff --git a/.env.example b/.env.example
index 4302469c6..2867dfebe 100644
--- a/.env.example
+++ b/.env.example
@@ -27,10 +27,10 @@ DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGR
#Sentry DSN
SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
-SLACK_CLIENT_ID=
-SLACK_CLIENT_SECRET=
-SLACK_BOT_TOKEN=
-SLACK_SIGNING_SECRET=
+SLACK_ID_CLIENT=your_slack_client_id_here
+SLACK_SECRET_CLIENT=your_slack_client_secret_here
+SLACK_BOT_TOKEN=your_slack_bot_token_here
+SLACK_SIGNING_SECRET=your_slack_signing_secret_here
#BlueSky User Details
BLUESKY_USERNAME=example.bsky.social
diff --git a/blt/urls.py b/blt/urls.py
index 286b4e691..4ede4e748 100644
--- a/blt/urls.py
+++ b/blt/urls.py
@@ -186,8 +186,7 @@
distribute_bacon,
select_contribution,
)
-from website.views.slack_handlers import slack_events
-from website.views.slackbot import slack_commands
+from website.views.slack_handlers import slack_commands, slack_events
from website.views.teams import (
TeamOverview,
add_member,
diff --git a/website/migrations/0181_slackintegration_welcome_message.py b/website/migrations/0181_slackintegration_welcome_message.py
new file mode 100644
index 000000000..a75140843
--- /dev/null
+++ b/website/migrations/0181_slackintegration_welcome_message.py
@@ -0,0 +1,21 @@
+# Generated by Django 5.1.3 on 2025-01-23 18:50
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("website", "0180_rename_project_visit_count_repo_repo_visit_count"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="slackintegration",
+ name="welcome_message",
+ field=models.TextField(
+ blank=True,
+ help_text="Custom welcome message for new members. Use Slack markdown formatting.",
+ null=True,
+ ),
+ ),
+ ]
diff --git a/website/models.py b/website/models.py
index 9172cabc6..fe328d9e7 100644
--- a/website/models.py
+++ b/website/models.py
@@ -106,6 +106,12 @@ class SlackIntegration(models.Model):
validators=[MinValueValidator(0), MaxValueValidator(23)], # Valid hours: 0–23
help_text="The hour of the day (0-23) to send daily updates",
)
+ # Add welcome message field
+ welcome_message = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Custom welcome message for new members. Use Slack markdown formatting.",
+ )
def __str__(self):
return f"Slack Integration for {self.integration.organization.name}"
diff --git a/website/templates/organization/add_slack_integration.html b/website/templates/organization/add_slack_integration.html
index 76dbc247e..aa93549ff 100644
--- a/website/templates/organization/add_slack_integration.html
+++ b/website/templates/organization/add_slack_integration.html
@@ -90,6 +90,23 @@
Configure Slack Bot:
+
+
+
+
+
+
+
+ You can use Slack's markdown formatting:
+ *bold*, _italic_, ~strikethrough~, `code`, and >quote
+
+
+
{% endblock body %}
+{% block extra_head %}
+
+
+{% endblock %}
+{% block extra_js %}
+
+{% endblock %}
diff --git a/website/test_slack.py b/website/test_slack.py
index 5b4dfe8c1..d414589a8 100644
--- a/website/test_slack.py
+++ b/website/test_slack.py
@@ -1,89 +1,128 @@
+import json
from unittest.mock import MagicMock, patch
from django.test import TestCase
-from website.views.slack_handlers import (
- _handle_contribute_message,
- _handle_team_join,
- extract_text_from_blocks,
- handle_message,
-)
+from website.models import Integration, Organization, SlackIntegration
+from website.views.slack_handlers import slack_commands, slack_events
-class SlackFunctionTests(TestCase):
+class SlackHandlerTests(TestCase):
def setUp(self):
- self.mock_client = MagicMock()
-
- def test_extract_text_from_blocks(self):
- """Test extracting text from Slack block format"""
- # Test rich text blocks
- blocks = [
- {
- "type": "rich_text",
- "elements": [
- {
- "type": "rich_text_section",
- "elements": [{"type": "text", "text": "I want to contribute"}],
- }
- ],
- }
- ]
-
- self.assertEqual(extract_text_from_blocks(blocks), "I want to contribute")
-
- # Test empty blocks
- self.assertEqual(extract_text_from_blocks([]), "")
-
- # Test invalid blocks
- self.assertEqual(extract_text_from_blocks(None), "")
-
- @patch("website.views.slack_handlers.client")
- def test_handle_contribute_message(self, mock_client):
- """Test contribute message handler"""
- message = {
- "user": "U123",
- "channel": "C123",
- "text": "How do I contribute?",
- "subtype": None,
+ # Create test organization and integration
+ self.organization = Organization.objects.create(name="Test Org", url="https://test.org")
+ self.integration = Integration.objects.create(
+ organization=self.organization, service_name="slack"
+ )
+ self.slack_integration = SlackIntegration.objects.create(
+ integration=self.integration,
+ bot_access_token="xoxb-test-token",
+ workspace_name="T070JPE5BQQ", # Test workspace ID
+ welcome_message="Welcome {user} to our workspace!",
+ )
+
+ @patch("website.views.slack_handlers.verify_slack_signature", return_value=True)
+ @patch("website.views.slack_handlers.WebClient")
+ def test_team_join_with_custom_message(self, mock_webclient, mock_verify):
+ # Mock the Slack client
+ mock_client = MagicMock()
+ mock_webclient.return_value = mock_client
+ mock_client.conversations_open.return_value = {"ok": True, "channel": {"id": "D123"}}
+ mock_client.chat_postMessage.return_value = {"ok": True}
+
+ # Create test event data
+ event_data = {
+ "token": "test-token",
+ "team_id": "T070JPE5BQQ",
+ "event": {"type": "team_join", "user": {"id": "U123"}},
+ "type": "event_callback",
}
- _handle_contribute_message(message)
+ # Create test request
+ request = MagicMock()
+ request.body = json.dumps(event_data).encode()
+ request.method = "POST"
+ request.headers = {
+ "X-Slack-Request-Timestamp": "1234567890",
+ "X-Slack-Signature": "v0=test",
+ }
- mock_client.chat_postMessage.assert_called_once()
+ # Call the event handler
+ response = slack_events(request)
- # Test message without contribute keyword
- message["text"] = "Hello world"
- mock_client.chat_postMessage.reset_mock()
- _handle_contribute_message(message)
- mock_client.chat_postMessage.assert_not_called()
+ # Verify DM was opened
+ mock_client.conversations_open.assert_called_once_with(users=["U123"])
- @patch("website.views.slack_handlers.client")
- def test_handle_team_join(self, mock_client):
- """Test team join handler"""
+ # Verify welcome message was sent with custom message
+ mock_client.chat_postMessage.assert_called_once()
+ call_args = mock_client.chat_postMessage.call_args[1]
+ self.assertEqual(call_args["text"], "Welcome {user} to our workspace!")
+
+ @patch("website.views.slack_handlers.verify_slack_signature", return_value=True)
+ @patch("website.views.slack_handlers.WebClient")
+ def test_team_join_owasp_workspace(self, mock_webclient, mock_verify):
+ # Mock the Slack client
+ mock_client = MagicMock()
+ mock_webclient.return_value = mock_client
mock_client.conversations_open.return_value = {"ok": True, "channel": {"id": "D123"}}
+ mock_client.chat_postMessage.return_value = {"ok": True}
+
+ # Create test event data for OWASP workspace
+ event_data = {
+ "token": "test-token",
+ "team_id": "T04T40NHX", # OWASP workspace ID
+ "event": {"type": "team_join", "user": {"id": "U123"}},
+ "type": "event_callback",
+ }
- _handle_team_join("U123")
+ # Create test request
+ request = MagicMock()
+ request.body = json.dumps(event_data).encode()
+ request.method = "POST"
+ request.headers = {
+ "X-Slack-Request-Timestamp": "1234567890",
+ "X-Slack-Signature": "v0=test",
+ }
- # Should send welcome message in joins channel
- mock_client.chat_postMessage.assert_called()
+ # Call the event handler
+ response = slack_events(request)
- # Should try to open DM
+ # Verify DM was opened
mock_client.conversations_open.assert_called_once_with(users=["U123"])
- @patch("website.views.slack_handlers.client")
- def test_handle_message(self, mock_client):
- """Test main message handler"""
- # Mock bot user ID
- mock_client.auth_test.return_value = {"user_id": "BOT123"}
+ # Verify default OWASP welcome message was sent
+ mock_client.chat_postMessage.assert_called_once()
+ call_args = mock_client.chat_postMessage.call_args[1]
+ self.assertIn("Welcome to the OWASP Slack Community", call_args["text"])
+
+ @patch("website.views.slack_handlers.verify_slack_signature", return_value=True)
+ @patch("website.views.slack_handlers.WebClient")
+ def test_slack_command_contrib(self, mock_webclient, mock_verify):
+ # Mock the Slack client
+ mock_client = MagicMock()
+ mock_webclient.return_value = mock_client
+ mock_client.conversations_open.return_value = {"ok": True, "channel": {"id": "D123"}}
+ mock_client.chat_postMessage.return_value = {"ok": True}
+
+ # Create test request
+ request = MagicMock()
+ request.method = "POST"
+ request.POST = {
+ "command": "/contrib",
+ "user_id": "U123",
+ "team_id": "T070JPE5BQQ",
+ "team_domain": "test",
+ }
+ request.headers = {
+ "X-Slack-Request-Timestamp": "1234567890",
+ "X-Slack-Signature": "v0=test",
+ }
- # Test normal user message
- payload = {"user": "U123", "text": "contribute", "channel": "C123"}
+ response = slack_commands(request)
- handle_message(payload)
- mock_client.chat_postMessage.assert_called()
+ # Verify DM was opened
+ mock_client.conversations_open.assert_called_once_with(users=["U123"])
- # Test bot message (should be ignored)
- payload["user"] = "BOT123"
- mock_client.chat_postMessage.reset_mock()
- handle_message(payload)
- mock_client.chat_postMessage.assert_not_called()
+ # Verify contribute message was sent
+ mock_client.chat_postMessage.assert_called_once()
+ self.assertEqual(response.status_code, 200)
diff --git a/website/views/company.py b/website/views/company.py
index 4d94868e7..01efe51dc 100644
--- a/website/views/company.py
+++ b/website/views/company.py
@@ -870,12 +870,13 @@ def get(self, request, id, *args, **kwargs):
"slack_integration": slack_integration,
"channels": channels_list,
"hours": hours,
+ "welcome_message": slack_integration.welcome_message,
},
)
# Redirect to Slack OAuth flow if no integration exists
- client_id = os.getenv("SLACK_CLIENT_ID")
- scopes = "channels:read,chat:write,groups:read,channels:join"
+ client_id = os.getenv("SLACK_ID_CLIENT")
+ scopes = "channels:read,chat:write,groups:read,channels:join,im:write,users:read,team:read,commands"
host = request.get_host()
scheme = request.META.get("HTTP_X_FORWARDED_PROTO", request.scheme)
redirect_uri = f"{scheme}://{host}/oauth/slack/callback"
@@ -921,6 +922,7 @@ def post(self, request, id, *args, **kwargs):
"default_channel": request.POST.get("target_channel"),
"daily_sizzle_timelogs_status": request.POST.get("daily_sizzle_timelogs_status"),
"daily_sizzle_timelogs_hour": request.POST.get("daily_sizzle_timelogs_hour"),
+ "welcome_message": request.POST.get("welcome_message"), # Add this
}
slack_integration = (
SlackIntegration.objects.filter(
@@ -940,6 +942,8 @@ def post(self, request, id, *args, **kwargs):
slack_integration.default_channel_name = slack_data["default_channel"]
slack_integration.daily_updates = bool(slack_data["daily_sizzle_timelogs_status"])
slack_integration.daily_update_time = slack_data["daily_sizzle_timelogs_hour"]
+ # Add welcome message
+ slack_integration.welcome_message = slack_data["welcome_message"]
slack_integration.save()
return redirect("organization_manage_integrations", id=id)
@@ -995,8 +999,8 @@ def get(self, request, *args, **kwargs):
organization_id = int(organization_id) # Safely cast to int after validation
- # Exchange code for token
- access_token = self.exchange_code_for_token(code, request)
+ # Exchange code for token and get team info
+ token_data = self.exchange_code_for_token(code, request)
integration = Integration.objects.create(
organization_id=organization_id,
@@ -1004,7 +1008,8 @@ def get(self, request, *args, **kwargs):
)
SlackIntegration.objects.create(
integration=integration,
- bot_access_token=access_token,
+ bot_access_token=token_data["access_token"],
+ workspace_name=token_data["team"]["id"],
)
dashboard_url = reverse("organization_manage_integrations", args=[organization_id])
@@ -1016,8 +1021,8 @@ def get(self, request, *args, **kwargs):
def exchange_code_for_token(self, code, request):
"""Exchanges OAuth code for Slack access token."""
- client_id = os.getenv("SLACK_CLIENT_ID")
- client_secret = os.getenv("SLACK_CLIENT_SECRET")
+ client_id = os.getenv("SLACK_ID_CLIENT")
+ client_secret = os.getenv("SLACK_SECRET_CLIENT")
host = request.get_host()
scheme = request.META.get("HTTP_X_FORWARDED_PROTO", request.scheme)
redirect_uri = os.environ.get(
@@ -1036,7 +1041,7 @@ def exchange_code_for_token(self, code, request):
token_data = response.json()
if token_data.get("ok"):
- return token_data["access_token"]
+ return token_data # Return the full token data instead of just the access token
else:
raise Exception(f"Error exchanging code for token: {token_data.get('error')}")
diff --git a/website/views/slack_handlers.py b/website/views/slack_handlers.py
index 70f125e59..7d2f8e62c 100644
--- a/website/views/slack_handlers.py
+++ b/website/views/slack_handlers.py
@@ -6,18 +6,17 @@
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
-from dotenv import load_dotenv
from slack import WebClient
from slack_sdk.errors import SlackApiError
-load_dotenv()
+from website.models import SlackIntegration
-DEPLOYS_CHANNEL_NAME = "#project-blt-lettuce-deploys"
-JOINS_CHANNEL_ID = "C076DAG65AT"
-CONTRIBUTE_ID = "C077QBBLY1Z"
+if os.getenv("ENV") != "production":
+ from dotenv import load_dotenv
-SLACK_TOKEN = os.getenv("SLACK_TOKEN")
-SIGNING_SECRET = os.getenv("SIGNING_SECRET")
+ load_dotenv()
+SLACK_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
+SIGNING_SECRET = os.environ.get("SLACK_SIGNING_SECRET")
client = WebClient(token=SLACK_TOKEN)
@@ -25,17 +24,34 @@ def verify_slack_signature(request):
timestamp = request.headers.get("X-Slack-Request-Timestamp", "")
signature = request.headers.get("X-Slack-Signature", "")
- # Verify timestamp to prevent replay attacks
- if abs(time.time() - float(timestamp)) > 60 * 5:
+ # Check if required headers are present
+ if not timestamp or not signature:
return False
- sig_basestring = f"v0:{timestamp}:{request.body.decode()}"
- my_signature = (
- "v0="
- + hmac.new(SIGNING_SECRET.encode(), sig_basestring.encode(), hashlib.sha256).hexdigest()
- )
+ try:
+ # Verify timestamp to prevent replay attacks
+ current_time = time.time()
+ request_time = float(timestamp)
+ time_diff = abs(current_time - request_time)
+
+ if time_diff > 60 * 5:
+ return False
+
+ # Create the signature base string
+ sig_basestring = f"v0:{timestamp}:{request.body.decode()}"
+
+ # Calculate our signature
+ my_signature = (
+ "v0="
+ + hmac.new(SIGNING_SECRET.encode(), sig_basestring.encode(), hashlib.sha256).hexdigest()
+ )
- return hmac.compare_digest(my_signature, signature)
+ # Compare signatures
+ is_valid = hmac.compare_digest(my_signature, signature)
+ return is_valid
+
+ except (ValueError, TypeError) as e:
+ return False
@csrf_exempt
@@ -48,6 +64,11 @@ def slack_events(request):
data = json.loads(request.body)
+ # Check if this is a retry event
+ is_retry = request.headers.get("X-Slack-Retry-Num")
+ if is_retry:
+ return HttpResponse(status=200)
+
if "challenge" in data:
return JsonResponse({"challenge": data["challenge"]})
@@ -62,131 +83,246 @@ def slack_events(request):
user_id = event.get("user")
if user_id:
- _handle_team_join(user_id)
-
- elif event_type == "message":
- handle_message(event)
+ _handle_team_join(user_id, request)
return HttpResponse(status=200)
return HttpResponse(status=405)
-def extract_text_from_blocks(blocks):
- """Extracts message text from Slack's 'blocks' format"""
- if not blocks:
- return ""
-
- text_parts = []
- for block in blocks:
- if block.get("type") == "rich_text":
- for element in block.get("elements", []):
- if element.get("type") == "rich_text_section":
- for item in element.get("elements", []):
- if item.get("type") == "text":
- text_parts.append(item.get("text", ""))
+def _handle_team_join(user_id, request):
+ try:
+ event_data = json.loads(request.body)
+ team_id = event_data["team_id"]
+ try:
+ slack_integration = SlackIntegration.objects.get(workspace_name=team_id)
- return " ".join(text_parts).strip()
+ # If integration exists and has welcome message
+ if slack_integration.welcome_message:
+ welcome_message = slack_integration.welcome_message
+ workspace_client = WebClient(token=slack_integration.bot_access_token)
+ else:
+ # If no welcome message but it's OWASP workspace
+ if team_id == "T04T40NHX":
+ workspace_client = WebClient(token=SLACK_TOKEN)
+ welcome_message = (
+ f":tada: *Welcome to the OWASP Slack Community, <@{user_id}>!* :tada:\n\n"
+ "We're thrilled to have you here! Whether you're new to OWASP or a long-time contributor, "
+ "this Slack workspace is the perfect place to connect, collaborate, and stay informed about all things OWASP.\n\n"
+ ":small_blue_diamond: *Get Involved:*\n"
+ "• Check out the *#contribute* channel to find ways to get involved with OWASP projects and initiatives.\n"
+ "• Explore individual project channels, which are named *#project-name*, to dive into specific projects that interest you.\n"
+ "• Join our chapter channels, named *#chapter-name*, to connect with local OWASP members in your area.\n\n"
+ ":small_blue_diamond: *Stay Updated:*\n"
+ "• Visit *#newsroom* for the latest updates and announcements.\n"
+ "• Follow *#external-activities* for news about OWASP's engagement with the wider security community.\n\n"
+ ":small_blue_diamond: *Connect and Learn:*\n"
+ "• *#jobs*: Looking for new opportunities? Check out the latest job postings here.\n"
+ "• *#leaders*: Connect with OWASP leaders and stay informed about leadership activities.\n"
+ "• *#project-committee*: Engage with the committee overseeing OWASP projects.\n"
+ "• *#gsoc*: Stay updated on Google Summer of Code initiatives.\n"
+ "• *#github-admins*: Get support and discuss issues related to OWASP's GitHub repositories.\n"
+ "• *#learning*: Share and find resources to expand your knowledge in the field of application security.\n\n"
+ "We're excited to see the amazing contributions you'll make. If you have any questions or need assistance, don't hesitate to ask. "
+ "Let's work together to make software security visible and improve the security of the software we all rely on.\n\n"
+ "Welcome aboard! :rocket:"
+ )
+ else:
+ workspace_client = WebClient(token=slack_integration.bot_access_token)
+ welcome_message = (
+ f"Welcome <@{user_id}>! 👋\n\n"
+ "Your workspace admin hasn't set up a custom welcome message yet. "
+ "They can configure this in the organization's integration settings."
+ )
+
+ except SlackIntegration.DoesNotExist:
+ # If no integration exists but it's OWASP workspace
+ if team_id == "T04T40NHX":
+ workspace_client = WebClient(token=SLACK_TOKEN)
+ # Use the default OWASP welcome message
+ welcome_message = (
+ f":tada: *Welcome to the OWASP Slack Community, <@{user_id}>!* :tada:\n\n"
+ "We're thrilled to have you here! Whether you're new to OWASP or a long-time contributor, "
+ "this Slack workspace is the perfect place to connect, collaborate, and stay informed about all things OWASP.\n\n"
+ ":small_blue_diamond: *Get Involved:*\n"
+ "• Check out the *#contribute* channel to find ways to get involved with OWASP projects and initiatives.\n"
+ "• Explore individual project channels, which are named *#project-name*, to dive into specific projects that interest you.\n"
+ "• Join our chapter channels, named *#chapter-name*, to connect with local OWASP members in your area.\n\n"
+ ":small_blue_diamond: *Stay Updated:*\n"
+ "• Visit *#newsroom* for the latest updates and announcements.\n"
+ "• Follow *#external-activities* for news about OWASP's engagement with the wider security community.\n\n"
+ ":small_blue_diamond: *Connect and Learn:*\n"
+ "• *#jobs*: Looking for new opportunities? Check out the latest job postings here.\n"
+ "• *#leaders*: Connect with OWASP leaders and stay informed about leadership activities.\n"
+ "• *#project-committee*: Engage with the committee overseeing OWASP projects.\n"
+ "• *#gsoc*: Stay updated on Google Summer of Code initiatives.\n"
+ "• *#github-admins*: Get support and discuss issues related to OWASP's GitHub repositories.\n"
+ "• *#learning*: Share and find resources to expand your knowledge in the field of application security.\n\n"
+ "We're excited to see the amazing contributions you'll make. If you have any questions or need assistance, don't hesitate to ask. "
+ "Let's work together to make software security visible and improve the security of the software we all rely on.\n\n"
+ "Welcome aboard! :rocket:"
+ )
+ else:
+ return
+ # Add delay to ensure user is fully joined
+ time.sleep(2) # Wait 2 seconds before sending message
-def _handle_contribute_message(message):
- text = message.get("text", "").lower()
- user = message.get("user")
- channel = message.get("channel")
+ # Try to open DM first
+ try:
+ dm_response = workspace_client.conversations_open(users=[user_id])
+ if not dm_response["ok"]:
+ return
- if message.get("subtype") is None and any(
- keyword in text for keyword in ["contribute", "contributing", "contributes"]
- ):
- response = client.chat_postMessage(
- channel=channel,
- text=f"Hello <@{user}>! Please check <#{CONTRIBUTE_ID}> for contributing guidelines today!",
- )
+ dm_channel = dm_response["channel"]["id"]
+ welcome_blocks = [
+ {"type": "section", "text": {"type": "mrkdwn", "text": welcome_message}}
+ ]
-def _handle_team_join(user_id):
- # Send message to joins channel
- join_response = client.chat_postMessage(
- channel=JOINS_CHANNEL_ID, text=f"Welcome <@{user_id}> to the team! 🎉"
- )
+ # Send message using appropriate client
+ welcome_response = workspace_client.chat_postMessage(
+ channel=dm_channel, text=welcome_message, blocks=welcome_blocks
+ )
- try:
- # Try to open DM first
- dm_response = client.conversations_open(users=[user_id])
- if not dm_response["ok"]:
+ except SlackApiError as e:
return
- dm_channel = dm_response["channel"]["id"]
-
- # Define welcome message
- welcome_message = (
- f":tada: *Welcome to the OWASP Slack Community, <@{user_id}>!* :tada:\n\n"
- "We're thrilled to have you here! Whether you're new to OWASP or a long-time contributor, "
- "this Slack workspace is the perfect place to connect, collaborate, and stay informed about all things OWASP.\n\n"
- ":small_blue_diamond: *Get Involved:*\n"
- "• Check out the *#contribute* channel to find ways to get involved with OWASP projects and initiatives.\n"
- "• Explore individual project channels, which are named *#project-name*, to dive into specific projects that interest you.\n"
- "• Join our chapter channels, named *#chapter-name*, to connect with local OWASP members in your area.\n\n"
- ":small_blue_diamond: *Stay Updated:*\n"
- "• Visit *#newsroom* for the latest updates and announcements.\n"
- "• Follow *#external-activities* for news about OWASP's engagement with the wider security community.\n\n"
- ":small_blue_diamond: *Connect and Learn:*\n"
- "• *#jobs*: Looking for new opportunities? Check out the latest job postings here.\n"
- "• *#leaders*: Connect with OWASP leaders and stay informed about leadership activities.\n"
- "• *#project-committee*: Engage with the committee overseeing OWASP projects.\n"
- "• *#gsoc*: Stay updated on Google Summer of Code initiatives.\n"
- "• *#github-admins*: Get support and discuss issues related to OWASP's GitHub repositories.\n"
- "• *#learning*: Share and find resources to expand your knowledge in the field of application security.\n\n"
- "We're excited to see the amazing contributions you'll make. If you have any questions or need assistance, don't hesitate to ask. "
- "Let's work together to make software security visible and improve the security of the software we all rely on.\n\n"
- "Welcome aboard! :rocket:"
- )
-
- welcome_blocks = [{"type": "section", "text": {"type": "mrkdwn", "text": welcome_message}}]
-
- welcome_response = client.chat_postMessage(
- channel=dm_channel, text=welcome_message, blocks=welcome_blocks
- )
-
except SlackApiError as e:
- return HttpResponse(status=500)
-
-
-def handle_message(payload):
- # Get bot user ID
- response = client.auth_test()
- bot_user_id = response["user_id"]
-
- # Skip if message is from the bot
- if payload.get("user") == bot_user_id:
return
- # Get message content from both text and blocks
- text = payload.get("text", "")
- blocks_text = extract_text_from_blocks(payload.get("blocks", []))
- # Use text from blocks if direct text is empty
- message_text = text or blocks_text
-
- # Create message object with the extracted text
- message = {
- "user": payload.get("user"),
- "channel": payload.get("channel"),
- "text": message_text,
- "subtype": payload.get("subtype"),
- "channel_type": payload.get("channel_type"),
- }
-
- _handle_contribute_message(message)
- _handle_direct_message(message, bot_user_id)
+@csrf_exempt
+def slack_commands(request):
+ """Handle Slack slash commands"""
+ if request.method == "POST":
+ # Verify the request is from Slack
+ is_valid = verify_slack_signature(request)
+ if not is_valid:
+ return HttpResponse(status=403)
-def _handle_direct_message(message, bot_user_id):
- if message.get("channel_type") == "im":
- user = message["user"]
- text = message.get("text", "")
+ command = request.POST.get("command")
+ user_id = request.POST.get("user_id")
+ team_id = request.POST.get("team_id")
+ team_domain = request.POST.get("team_domain") # Get the team domain
+
+ if command == "/contrib":
+ try:
+ # First try to get custom integration
+ try:
+ slack_integration = SlackIntegration.objects.get(workspace_name=team_id)
+ workspace_client = WebClient(token=slack_integration.bot_access_token)
+ except SlackIntegration.DoesNotExist:
+ # If no custom integration and it's OWASP workspace, use default token
+ if team_domain == "owasp":
+ workspace_client = WebClient(token=SLACK_TOKEN)
+ else:
+ return JsonResponse(
+ {
+ "response_type": "ephemeral",
+ "text": "This workspace is not properly configured. Please contact the workspace admin.",
+ }
+ )
+
+ contribute_message = [
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": ":rocket: *Contributing to OWASP Projects*\n\n"
+ " 🔹 *Join the OWASP Slack Channel:* Find guidance and check pinned posts for projects seeking contributors.\n"
+ " 🔹 *Explore OWASP Projects Page:* Identify projects that align with your skills and interests.",
+ },
+ },
+ {"type": "divider"},
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": ":loudspeaker: *Engaging on Slack*\n\n"
+ " Many projects have dedicated project channels for collaboration.\n\n"
+ " 🔍 *Find and Join a Project Channel:*",
+ },
+ },
+ {
+ "type": "actions",
+ "elements": [
+ {
+ "type": "channels_select",
+ "placeholder": {
+ "type": "plain_text",
+ "text": "🔍 Search and Select a Project Channel",
+ "emoji": True,
+ },
+ "action_id": "select_project_channel",
+ }
+ ],
+ },
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": " 🛠 *GSOC Projects:* View this year's participating GSOC projects .",
+ },
+ },
+ {"type": "divider"},
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": ":busts_in_silhouette: *Identifying Key People and Activity*\n\n"
+ " • Visit the *OWASP Projects* page to find project leaders and contributors.\n"
+ " • Review *GitHub commit history* for active developers.\n"
+ " • Check *Slack activity* for updates on project progress.",
+ },
+ },
+ {"type": "divider"},
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": ":pushpin: *Communication Guidelines*\n\n"
+ " ✅ *Check pinned messages* in project channels for updates.\n"
+ " ✅ *Ask questions* in relevant project channels.\n"
+ " ✅ *Introduce yourself* while keeping personal details private.",
+ },
+ },
+ {"type": "divider"},
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": ":hammer_and_wrench: *How to Contribute*\n\n"
+ " 1️⃣ *Select a project* and review its contribution guidelines.\n"
+ " 2️⃣ *Work on an open GitHub issue* or propose a new one.\n"
+ " 3️⃣ *Coordinate with project leaders* to prevent overlaps.\n"
+ " 4️⃣ *Submit a pull request* and keep the team informed.\n\n"
+ " 💡 *Focus on clear communication and teamwork!* 🚀",
+ },
+ },
+ ]
+
+ # Open DM channel first
+ dm_response = workspace_client.conversations_open(users=[user_id])
+ if not dm_response["ok"]:
+ return HttpResponse(status=500)
+
+ dm_channel = dm_response["channel"]["id"]
+
+ # Send message to DM channel
+ message_response = workspace_client.chat_postMessage(
+ channel=dm_channel, blocks=contribute_message, mrkdwn=True
+ )
+
+ # Send ephemeral message in the channel where command was used
+ return JsonResponse(
+ {
+ "response_type": "ephemeral",
+ "text": "I've sent you a DM with information about contributing! 🚀",
+ }
+ )
+
+ except SlackApiError as e:
+ return HttpResponse(status=500)
- try:
- if message.get("user") != bot_user_id:
- client.chat_postMessage(channel=JOINS_CHANNEL_ID, text=f"<@{user}> said {text}")
- client.chat_postMessage(channel=user, text=f"Hello <@{user}>, you said: {text}")
- except SlackApiError as e:
- return HttpResponse(status=500)
+ return HttpResponse(status=405)
From b7985791553994559fb6bd931873993475058b3b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 24 Jan 2025 00:44:23 +0000
Subject: [PATCH 08/13] chore(deps-dev): Bump ruff from 0.9.2 to 0.9.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.2...0.9.3)
---
updated-dependencies:
- dependency-name: ruff
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
poetry.lock | 40 ++++++++++++++++++++--------------------
pyproject.toml | 2 +-
2 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 0c5da61b2..d0ad219bc 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -4006,29 +4006,29 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
-version = "0.9.2"
+version = "0.9.3"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"},
- {file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"},
- {file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"},
- {file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"},
- {file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"},
- {file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"},
- {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"},
- {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"},
- {file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"},
- {file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"},
- {file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"},
- {file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"},
- {file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"},
- {file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"},
- {file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"},
- {file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"},
- {file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"},
- {file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"},
+ {file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"},
+ {file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"},
+ {file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"},
+ {file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"},
+ {file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"},
+ {file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"},
+ {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"},
+ {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"},
+ {file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"},
+ {file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"},
+ {file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"},
+ {file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"},
+ {file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"},
+ {file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"},
+ {file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"},
+ {file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"},
+ {file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"},
+ {file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"},
]
[[package]]
@@ -5021,4 +5021,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.0"
python-versions = "3.11.2"
-content-hash = "6a485f0828d702e51ebfe96f113838203009e972b36edf24b4aef7264d036d02"
+content-hash = "37c2c20e44336bb3a5c07037884bb7eca9ac4268cd175dfca77be350e9e76da2"
diff --git a/pyproject.toml b/pyproject.toml
index 3a4dd018d..1eb25a0bd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -87,7 +87,7 @@ newrelic = "^10.4.0"
[tool.poetry.group.dev.dependencies]
black = "^24.8.0"
isort = "^5.13.2"
-ruff = "^0.9.2"
+ruff = "^0.9.3"
pre-commit = "^3.8.0"
[tool.isort]
From 1145424780893f38a8ba79b51396d9e07f1698de Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 24 Jan 2025 00:53:16 +0000
Subject: [PATCH 09/13] chore(deps): Bump unstructured from 0.16.14 to 0.16.15
Bumps [unstructured](https://github.com/Unstructured-IO/unstructured) from 0.16.14 to 0.16.15.
- [Release notes](https://github.com/Unstructured-IO/unstructured/releases)
- [Changelog](https://github.com/Unstructured-IO/unstructured/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Unstructured-IO/unstructured/compare/0.16.14...0.16.15)
---
updated-dependencies:
- dependency-name: unstructured
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
poetry.lock | 16 ++++++++--------
pyproject.toml | 2 +-
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index d0ad219bc..6258904fd 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -4509,13 +4509,13 @@ files = [
[[package]]
name = "unstructured"
-version = "0.16.14"
+version = "0.16.15"
description = "A library that prepares raw documents for downstream ML tasks."
optional = false
python-versions = "<3.13,>=3.9.0"
files = [
- {file = "unstructured-0.16.14-py3-none-any.whl", hash = "sha256:7b3c2eb21e65d2f61240de7a5241fd7734d97be2c9cfa5f70934e10470318131"},
- {file = "unstructured-0.16.14.tar.gz", hash = "sha256:cec819461090226cd478429c1e0fda19a66ba49ab9ade1ea1fd9ec79c279d7ac"},
+ {file = "unstructured-0.16.15-py3-none-any.whl", hash = "sha256:5b0931eb92fb858b983fada18111efdf9c2a0c861ef8e9b58c4e05b1daa50e35"},
+ {file = "unstructured-0.16.15.tar.gz", hash = "sha256:18fb850d47b5a2a6ea45b2f7e0eda687f903a2f2e58909b1defd48e2b3126ff4"},
]
[package.dependencies]
@@ -4543,19 +4543,19 @@ unstructured-client = "*"
wrapt = "*"
[package.extras]
-all-docs = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypandoc", "pypdf", "python-docx (>=1.1.2)", "python-pptx (>=1.0.1)", "unstructured-inference (==0.8.1)", "unstructured.pytesseract (>=0.3.12)", "xlrd"]
+all-docs = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypandoc", "pypdf", "python-docx (>=1.1.2)", "python-pptx (>=1.0.1)", "unstructured-inference (>=0.8.6)", "unstructured.pytesseract (>=0.3.12)", "xlrd"]
csv = ["pandas"]
doc = ["python-docx (>=1.1.2)"]
docx = ["python-docx (>=1.1.2)"]
epub = ["pypandoc"]
huggingface = ["langdetect", "sacremoses", "sentencepiece", "torch", "transformers"]
-image = ["effdet", "google-cloud-vision", "onnx", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypdf", "unstructured-inference (==0.8.1)", "unstructured.pytesseract (>=0.3.12)"]
-local-inference = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypandoc", "pypdf", "python-docx (>=1.1.2)", "python-pptx (>=1.0.1)", "unstructured-inference (==0.8.1)", "unstructured.pytesseract (>=0.3.12)", "xlrd"]
+image = ["effdet", "google-cloud-vision", "onnx", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypdf", "unstructured-inference (>=0.8.6)", "unstructured.pytesseract (>=0.3.12)"]
+local-inference = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypandoc", "pypdf", "python-docx (>=1.1.2)", "python-pptx (>=1.0.1)", "unstructured-inference (>=0.8.6)", "unstructured.pytesseract (>=0.3.12)", "xlrd"]
md = ["markdown"]
odt = ["pypandoc", "python-docx (>=1.1.2)"]
org = ["pypandoc"]
paddleocr = ["paddlepaddle (==3.0.0b1)", "unstructured.paddleocr (==2.8.1.0)"]
-pdf = ["effdet", "google-cloud-vision", "onnx", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypdf", "unstructured-inference (==0.8.1)", "unstructured.pytesseract (>=0.3.12)"]
+pdf = ["effdet", "google-cloud-vision", "onnx", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypdf", "unstructured-inference (>=0.8.6)", "unstructured.pytesseract (>=0.3.12)"]
ppt = ["python-pptx (>=1.0.1)"]
pptx = ["python-pptx (>=1.0.1)"]
rst = ["pypandoc"]
@@ -5021,4 +5021,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.0"
python-versions = "3.11.2"
-content-hash = "37c2c20e44336bb3a5c07037884bb7eca9ac4268cd175dfca77be350e9e76da2"
+content-hash = "3be3fe281b04ee7a44701778fe8a0ddbe22d31ee146b0e14607a6fbd1c8cda41"
diff --git a/pyproject.toml b/pyproject.toml
index 1eb25a0bd..dbb44d436 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -65,7 +65,7 @@ sentry-sdk = "^2.20.0"
bitcash = "^1.0.2"
pydantic = "^2.10.5"
pydantic_core = "^2.18.4"
-unstructured = "^0.16.14"
+unstructured = "^0.16.15"
Markdown = "^3.6"
faiss-cpu = "^1.8.0"
psutil = "^5.9.8"
From d22670bc559ec484eab7afd279a59ff98cfb5b79 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 24 Jan 2025 01:01:48 +0000
Subject: [PATCH 10/13] chore(deps): Bump selenium from 4.28.0 to 4.28.1
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.28.0 to 4.28.1.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/commits)
---
updated-dependencies:
- dependency-name: selenium
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
poetry.lock | 8 ++++----
pyproject.toml | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 6258904fd..be1d6b67f 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -4111,13 +4111,13 @@ wrapt = ">=1.10,<2.0"
[[package]]
name = "selenium"
-version = "4.28.0"
+version = "4.28.1"
description = "Official Python bindings for Selenium WebDriver"
optional = false
python-versions = ">=3.9"
files = [
- {file = "selenium-4.28.0-py3-none-any.whl", hash = "sha256:3d6a2e8e1b850a1078884ea19f4e011ecdc12263434d87a0b78769836fb82dd8"},
- {file = "selenium-4.28.0.tar.gz", hash = "sha256:a9fae6eef48d470a1b0c6e45185d96f0dafb025e8da4b346cc41e4da3ac54fa0"},
+ {file = "selenium-4.28.1-py3-none-any.whl", hash = "sha256:4238847e45e24e4472cfcf3554427512c7aab9443396435b1623ef406fff1cc1"},
+ {file = "selenium-4.28.1.tar.gz", hash = "sha256:0072d08670d7ec32db901bd0107695a330cecac9f196e3afb3fa8163026e022a"},
]
[package.dependencies]
@@ -5021,4 +5021,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.0"
python-versions = "3.11.2"
-content-hash = "3be3fe281b04ee7a44701778fe8a0ddbe22d31ee146b0e14607a6fbd1c8cda41"
+content-hash = "9e92c532af81fecec4b0855a320481a779b88c8e456ec7d745c47d4be4790fbb"
diff --git a/pyproject.toml b/pyproject.toml
index dbb44d436..0b5747c78 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,7 +25,7 @@ Unidecode = "^1.3.8"
user-agents = "^2.2.0"
whitenoise = "^6.8.2"
django-debug-toolbar = "^4.4.6"
-selenium = "^4.28.0"
+selenium = "^4.28.1"
pylibmc = "^1.6.1"
psycopg2-binary = "^2.9.10"
boto = "^2.49.0"
From c7f62ef18bf159c231dee8bafe486cf0e4dc7ac3 Mon Sep 17 00:00:00 2001
From: Sahil Omkumar Dhillon
<118592065+SahilDhillon21@users.noreply.github.com>
Date: Fri, 24 Jan 2025 08:43:31 +0530
Subject: [PATCH 11/13] Search fix and enhanced UI (#3252)
* frontend changes
* Search bar changes
* formatting changes
---
website/templates/includes/header.html | 73 ++-
website/templates/search.html | 753 +++++++++++++++++++++----
website/views/core.py | 93 ++-
3 files changed, 793 insertions(+), 126 deletions(-)
diff --git a/website/templates/includes/header.html b/website/templates/includes/header.html
index 86e57dcd1..5752c4012 100644
--- a/website/templates/includes/header.html
+++ b/website/templates/includes/header.html
@@ -149,9 +149,11 @@
action="{% url 'search' %}"
method="get">
+