diff --git a/project_channels.csv b/project_channels.csv new file mode 100644 index 000000000..25dd13bd0 --- /dev/null +++ b/project_channels.csv @@ -0,0 +1,114 @@ +slack_channel,slack_id,slack_url +project-zap,C04SX2GAS,https://OWASP.slack.com/archives/C04SX2GAS +project-xenotix,C04T4HY7U,https://OWASP.slack.com/archives/C04T4HY7U +project-railsgoat,C04THC44W,https://OWASP.slack.com/archives/C04THC44W +project-o2,C04TJNC8M,https://OWASP.slack.com/archives/C04TJNC8M +project-nodegoat,C04TQK9UF,https://OWASP.slack.com/archives/C04TQK9UF +project-hackademic,C050BRC9M,https://OWASP.slack.com/archives/C050BRC9M +project-scg,C050V7CNL,https://OWASP.slack.com/archives/C050V7CNL +project-sec-shepherd,C051M1G3A,https://OWASP.slack.com/archives/C051M1G3A +project-dotnet,C053H58SK,https://OWASP.slack.com/archives/C053H58SK +project-zap-notify,C061VMC87,https://OWASP.slack.com/archives/C061VMC87 +project-asvs,C06MNF14M,https://OWASP.slack.com/archives/C06MNF14M +project-webgoat,C0948GVLM,https://OWASP.slack.com/archives/C0948GVLM +project-webgoat-notif,C09H06VFA,https://OWASP.slack.com/archives/C09H06VFA +project-zsc,C09HKQ0D7,https://OWASP.slack.com/archives/C09HKQ0D7 +project-devops,C09MLAY8P,https://OWASP.slack.com/archives/C09MLAY8P +project-wafec,C0BBA9FM0,https://OWASP.slack.com/archives/C0BBA9FM0 +project-hacakdemic,C0BR2NMUG,https://OWASP.slack.com/archives/C0BR2NMUG +project-skf,C0F7L9X6V,https://OWASP.slack.com/archives/C0F7L9X6V +project-csrfguard,C0H1KR347,https://OWASP.slack.com/archives/C0H1KR347 +project-glue,C0HVCFDP0,https://OWASP.slack.com/archives/C0HVCFDP0 +project-appsensor,C0KJ7JMCJ,https://OWASP.slack.com/archives/C0KJ7JMCJ +project-samm,C0VF1EJGH,https://OWASP.slack.com/archives/C0VF1EJGH +project-virtualvillag,C18A8EGKH,https://OWASP.slack.com/archives/C18A8EGKH +project-mobile-app-security,C1M6ZVC6S,https://OWASP.slack.com/archives/C1M6ZVC6S +project-vicnum,C1MAN1B08,https://OWASP.slack.com/archives/C1MAN1B08 +project-top-10,C1QBMGU69,https://OWASP.slack.com/archives/C1QBMGU69 +project-embeddedappsec,C1TJMUNG3,https://OWASP.slack.com/archives/C1TJMUNG3 +project-juiceshop,C255XSY04,https://OWASP.slack.com/archives/C255XSY04 +project-igoat,C2BKNP7DZ,https://OWASP.slack.com/archives/C2BKNP7DZ +project-blt,C2FF0UVHU,https://OWASP.slack.com/archives/C2FF0UVHU +project-olg-github,C3F2F9TMY,https://OWASP.slack.com/archives/C3F2F9TMY +project-riskrating,C56GPPD6Z,https://OWASP.slack.com/archives/C56GPPD6Z +project-riskrating_mp,C56GQ0ZHT,https://OWASP.slack.com/archives/C56GQ0ZHT +project-owtf,C5M114999,https://OWASP.slack.com/archives/C5M114999 +project-blt-github,C5QAK3Q9G,https://OWASP.slack.com/archives/C5QAK3Q9G +project-securityrat,C76U4TNFJ,https://OWASP.slack.com/archives/C76U4TNFJ +project-malware,C9G489878,https://OWASP.slack.com/archives/C9G489878 +project-securetea,C9GAF53NK,https://OWASP.slack.com/archives/C9GAF53NK +project-devslop,CA1PNFZSR,https://OWASP.slack.com/archives/CA1PNFZSR +project-mobile-app-security-dev,CCBAP0CGN,https://OWASP.slack.com/archives/CCBAP0CGN +project-sls-top-10,CD9D8J41E,https://OWASP.slack.com/archives/CD9D8J41E +project-scvs,CGH5X9NQ0,https://OWASP.slack.com/archives/CGH5X9NQ0 +project-packman,CHKT6HKTK,https://OWASP.slack.com/archives/CHKT6HKTK +project-security-bot,CLMA4F01J,https://OWASP.slack.com/archives/CLMA4F01J +project-mobile_tm,CLW9F9F0X,https://OWASP.slack.com/archives/CLW9F9F0X +project-integration,CPMEWT342,https://OWASP.slack.com/archives/CPMEWT342 +project-nettacker,CQZGG24FQ,https://OWASP.slack.com/archives/CQZGG24FQ +project-threat-dragon,CURE8PQ68,https://OWASP.slack.com/archives/CURE8PQ68 +project-pygoat,C013HSLMTFE,https://OWASP.slack.com/archives/C013HSLMTFE +project-blt-gsoc-rehndndup,C0145BH2P70,https://OWASP.slack.com/archives/C0145BH2P70 +project-samuraiwtf,C01524KH43G,https://OWASP.slack.com/archives/C01524KH43G +project-isvs,C01600RMP9P,https://OWASP.slack.com/archives/C01600RMP9P +project-off,C016U8XQ95H,https://OWASP.slack.com/archives/C016U8XQ95H +project-curriculum,C017AC06QV7,https://OWASP.slack.com/archives/C017AC06QV7 +project-sponsorship,C018P1JUPUH,https://OWASP.slack.com/archives/C018P1JUPUH +project-committee,C01930CGW23,https://OWASP.slack.com/archives/C01930CGW23 +project-how-to-get-into-appsec,C01KF26B1UH,https://OWASP.slack.com/archives/C01KF26B1UH +project-purpleteam,C01LARX6WP8,https://OWASP.slack.com/archives/C01LARX6WP8 +project-html-sanitizer,C0250DKTFCP,https://OWASP.slack.com/archives/C0250DKTFCP +project-developeroutreach,C02CXL4USFM,https://OWASP.slack.com/archives/C02CXL4USFM +project-cre,C02EAS3MY84,https://OWASP.slack.com/archives/C02EAS3MY84 +project-snow,C02EX68P1UJ,https://OWASP.slack.com/archives/C02EX68P1UJ +project-wrongsecrets,C02KQ7D9XHR,https://OWASP.slack.com/archives/C02KQ7D9XHR +project-pytm,C02KRQ0CATB,https://OWASP.slack.com/archives/C02KRQ0CATB +project-secure-code-review-guide,C02QDREE0M7,https://OWASP.slack.com/archives/C02QDREE0M7 +project-podcast,C02U3MTA13K,https://OWASP.slack.com/archives/C02U3MTA13K +project-iot-top10,C034JK2BFGW,https://OWASP.slack.com/archives/C034JK2BFGW +project-wrongsecrets-dev,C039L78LSER,https://OWASP.slack.com/archives/C039L78LSER +project-wrongsecrets-callback,C03BCJ1BXNK,https://OWASP.slack.com/archives/C03BCJ1BXNK +project-security-culture,C03CHLJ1YLR,https://OWASP.slack.com/archives/C03CHLJ1YLR +project-k8s-top10,C03FV6MSRCM,https://OWASP.slack.com/archives/C03FV6MSRCM +project-safetypes,C0432Q430Q3,https://OWASP.slack.com/archives/C0432Q430Q3 +project-continuous-penetration-testing-framework,C0484CAPBE0,https://OWASP.slack.com/archives/C0484CAPBE0 +project-domain-protect,C04BPJ5B2P4,https://OWASP.slack.com/archives/C04BPJ5B2P4 +project-secure-coding-practices,C04DZ254HFG,https://OWASP.slack.com/archives/C04DZ254HFG +project-go-scp,C04FG14MN5B,https://OWASP.slack.com/archives/C04FG14MN5B +project-ai-community,C04FV0D1GES,https://OWASP.slack.com/archives/C04FV0D1GES +project-devsecops-verification-standard,C04HD8ES72M,https://OWASP.slack.com/archives/C04HD8ES72M +project-mlsec-top-10,C04PESBUWRZ,https://OWASP.slack.com/archives/C04PESBUWRZ +project-developer-guide,C04QN6CMNAC,https://OWASP.slack.com/archives/C04QN6CMNAC +project-vulnerability-maturity-sig,C04QWA7R3C7,https://OWASP.slack.com/archives/C04QWA7R3C7 +project-blt-flutter-github,C04SCC5Q3RT,https://OWASP.slack.com/archives/C04SCC5Q3RT +project-committee-github,C0506NPJ2EM,https://OWASP.slack.com/archives/C0506NPJ2EM +project-asvs-nuclei,C052939BZ43,https://OWASP.slack.com/archives/C052939BZ43 +project-blt-codemagic,C052AAELH3P,https://OWASP.slack.com/archives/C052AAELH3P +project-new-projects,C052TF4AA84,https://OWASP.slack.com/archives/C052TF4AA84 +project-raider,C053YNZNEFP,https://OWASP.slack.com/archives/C053YNZNEFP +project-api-top10,C0558AF1QQM,https://OWASP.slack.com/archives/C0558AF1QQM +project-top10-for-llm,C05956H7R8R,https://OWASP.slack.com/archives/C05956H7R8R +project-osib,C05DPB4M1Q8,https://OWASP.slack.com/archives/C05DPB4M1Q8 +project-blt-prs,C05FBSPALLS,https://OWASP.slack.com/archives/C05FBSPALLS +project-nightingale,C05JPRM5GP8,https://OWASP.slack.com/archives/C05JPRM5GP8 +project-sweeper,C0607RP8MS8,https://OWASP.slack.com/archives/C0607RP8MS8 +project-securecodebox,C062TQANH3N,https://OWASP.slack.com/archives/C062TQANH3N +project-modsecurity,C069PCXSW12,https://OWASP.slack.com/archives/C069PCXSW12 +project-blockchain-appsec-standard,C06A53BF0QY,https://OWASP.slack.com/archives/C06A53BF0QY +project-security-c4po,C06ECA5U8SY,https://OWASP.slack.com/archives/C06ECA5U8SY +project-common-lifecycle-enumeration,C06GUKY03NC,https://OWASP.slack.com/archives/C06GUKY03NC +project-pscf,C06HQQF04CU,https://OWASP.slack.com/archives/C06HQQF04CU +project-sdrf,C06J07ZG7DE,https://OWASP.slack.com/archives/C06J07ZG7DE +project-llmvs,C06MDJG0KBK,https://OWASP.slack.com/archives/C06MDJG0KBK +project-blt-lettuce,C06R1H90JKV,https://OWASP.slack.com/archives/C06R1H90JKV +project-blt-lettuce-deploys,C06RBJ779CH,https://OWASP.slack.com/archives/C06RBJ779CH +project-blt-bacon,C06RNAENB4P,https://OWASP.slack.com/archives/C06RNAENB4P +project-flop-10,C072N37N82Z,https://OWASP.slack.com/archives/C072N37N82Z +project-ai-masteraisecurity,C077YSV1D7C,https://OWASP.slack.com/archives/C077YSV1D7C +project-netryx,C07D6R13URM,https://OWASP.slack.com/archives/C07D6R13URM +project-ot-top-10,C07HDTYRA6R,https://OWASP.slack.com/archives/C07HDTYRA6R +project-nest,C07JLLG2GFQ,https://OWASP.slack.com/archives/C07JLLG2GFQ +project-top10-proactive-controls,C07KNHZAN1H,https://OWASP.slack.com/archives/C07KNHZAN1H +project-actions,C07PMR5RV1A,https://OWASP.slack.com/archives/C07PMR5RV1A +project-aibom-community,C07UZUAJTL4,https://OWASP.slack.com/archives/C07UZUAJTL4 +project-scstg,C083UNMMVMH,https://OWASP.slack.com/archives/C083UNMMVMH diff --git a/website/management/commands/import_slack_channel.py b/website/management/commands/import_slack_channel.py new file mode 100644 index 000000000..3c79202d2 --- /dev/null +++ b/website/management/commands/import_slack_channel.py @@ -0,0 +1,60 @@ +import requests +from django.conf import settings +from django.core.management.base import BaseCommand + +from website.models import Project # Replace `website` with the actual app name + +SLACK_TOKEN = settings.SLACK_TOKEN +SLACK_API_URL = "https://slack.com/api/conversations.list" +HEADERS = {"Authorization": f"Bearer {SLACK_TOKEN}"} + + +class Command(BaseCommand): + help = "Fetch Slack channels and associate them with projects" + + def fetch_channels(self): + url = SLACK_API_URL + params = {"limit": 1000, "types": "public_channel"} # Fetch only public channels + channels = [] + + while url: + response = requests.get(url, headers=HEADERS, params=params) + data = response.json() + + if not data.get("ok"): + self.stdout.write(f"Error: {data.get('error')}") + break + + channels.extend(data.get("channels", [])) + cursor = data.get("response_metadata", {}).get("next_cursor") + if cursor: + url = SLACK_API_URL + f"?cursor={cursor}" + else: + url = None + + return channels + + def handle(self, *args, **kwargs): + self.stdout.write("Fetching Slack channels...") + + channels = self.fetch_channels() + for channel in channels: + if channel["name"].startswith("project-"): + project_name = channel["name"].replace("project-", "").capitalize() + + # Update or create project with Slack details + project, created = Project.objects.update_or_create( + name=project_name, + defaults={ + "slack_channel": channel["name"], + "slack_id": channel["id"], + "slack_url": f"https://OWASP.slack.com/archives/{channel['id']}", + }, + ) + + if created: + self.stdout.write(f"Created new project: {project_name}") + else: + self.stdout.write(f"Updated existing project: {project_name}") + + self.stdout.write(f"Processed {len(channels)} Slack channels.") diff --git a/website/migrations/0181_alter_project_slack_squashed_0184_project_slack_channel_project_slack_id.py b/website/migrations/0181_alter_project_slack_squashed_0184_project_slack_channel_project_slack_id.py new file mode 100644 index 000000000..05cbab625 --- /dev/null +++ b/website/migrations/0181_alter_project_slack_squashed_0184_project_slack_channel_project_slack_id.py @@ -0,0 +1,32 @@ +# Generated by Django 5.1.4 on 2025-01-30 22:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + replaces = [ + ("website", "0181_alter_project_slack"), + ("website", "0184_project_slack_channel_project_slack_id"), + ] + + dependencies = [ + ("website", "0179_contributorstats"), + ] + + operations = [ + migrations.AlterField( + model_name="project", + name="slack", + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name="project", + name="slack_channel", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name="project", + name="slack_id", + field=models.CharField(blank=True, max_length=255, null=True, unique=True), + ), + ] diff --git a/website/migrations/0189_merge_20250130_2249.py b/website/migrations/0189_merge_20250130_2249.py new file mode 100644 index 000000000..926af2b00 --- /dev/null +++ b/website/migrations/0189_merge_20250130_2249.py @@ -0,0 +1,15 @@ +# Generated by Django 5.1.4 on 2025-01-30 22:49 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ( + "website", + "0181_alter_project_slack_squashed_0184_project_slack_channel_project_slack_id", + ), + ("website", "0186_userprofile_contribution_rank_and_more"), + ] + + operations = [] diff --git a/website/models.py b/website/models.py index 5b944636c..387aeb9eb 100644 --- a/website/models.py +++ b/website/models.py @@ -931,6 +931,10 @@ class Project(models.Model): url = models.URLField(unique=True, null=True, blank=True) # Made url nullable in case of no website project_visit_count = models.IntegerField(default=0) twitter = models.CharField(max_length=30, null=True, blank=True) + slack = models.URLField(null=True, blank=True) + slack_channel = models.CharField(max_length=255, blank=True, null=True) + slack_id = models.CharField(max_length=255, unique=True, blank=True, null=True) + facebook = models.URLField(null=True, blank=True) logo = models.ImageField(upload_to="project_logos", null=True, blank=True) created = models.DateTimeField(auto_now_add=True) # Standardized field name diff --git a/website/templates/projects/project_detail.html b/website/templates/projects/project_detail.html index d9b0126da..6afc78a64 100644 --- a/website/templates/projects/project_detail.html +++ b/website/templates/projects/project_detail.html @@ -125,6 +125,17 @@

{{ project.name }}

Facebook {% endif %} + {% if project.slack %} + + + + + Slack + + {% endif %} diff --git a/website/templates/projects/project_list.html b/website/templates/projects/project_list.html index 20707bc16..9bb44279c 100644 --- a/website/templates/projects/project_list.html +++ b/website/templates/projects/project_list.html @@ -358,6 +358,14 @@

Add New Project

class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500" placeholder="https://facebook.com/..." /> + +
+ + +
diff --git a/website/views/project.py b/website/views/project.py index f0c02a8a9..4e75a24a7 100644 --- a/website/views/project.py +++ b/website/views/project.py @@ -405,6 +405,17 @@ def validate_url(url): }, status=400, ) + slack = request.POST.get("slack") + if slack: + if slack.startswith(("http://", "https://")): + if not validate_url(slack): + return JsonResponse( + { + "error": "Slack URL is not accessible", + "code": "INVALID_SLACK_URL", + }, + status=400, + ) # Validate repository URLs repo_urls = request.POST.getlist("repo_urls[]") @@ -484,6 +495,7 @@ def validate_url(url): "url": project_url, "twitter": request.POST.get("twitter"), "facebook": request.POST.get("facebook"), + "slack": request.POST.get("slack"), } # Handle logo file