Skip to content

Commit c7b58ee

Browse files
author
Jacob Woliver
committed
add demo apps from company meeting
1 parent 7206242 commit c7b58ee

File tree

8 files changed

+675
-0
lines changed

8 files changed

+675
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import re
2+
from datetime import datetime
3+
from functools import cache
4+
from typing import Callable
5+
import requests
6+
import json
7+
8+
import pandas as pd
9+
from htmltools import tags
10+
from packaging.specifiers import InvalidSpecifier, SpecifierSet
11+
from posit.connect import Client
12+
from shiny import reactive
13+
from shiny.express import input, render, ui
14+
15+
client = Client()
16+
packages = reactive.value(pd.DataFrame([]))
17+
18+
with ui.sidebar():
19+
ui.input_text("guid", "Content GUID")
20+
21+
@render.text
22+
def datagrid_label():
23+
ui.busy_indicators.options(spinner_type=None)
24+
if num_packages() > 0:
25+
return f"{num_packages()} packages for content '{content_title()}'"
26+
else:
27+
return "Please enter a content GUID to search for packages."
28+
29+
def update_packages(app_packages):
30+
data = prepare_packages_data(get_packages(app_packages))
31+
packages.set(data)
32+
33+
34+
@render.data_frame
35+
def app_grid():
36+
app_packages = packages_spec()
37+
if not app_packages:
38+
return None
39+
update_packages(app_packages)
40+
return render.DataGrid(
41+
packages.get(),
42+
width="100%",
43+
height="100%",
44+
selection_mode="rows",
45+
styles=dict(style={"white-space": "nowrap"}),
46+
)
47+
48+
49+
@reactive.calc
50+
def num_packages():
51+
return len(packages.get())
52+
53+
@reactive.calc
54+
def content_title():
55+
if input.guid() == "":
56+
return
57+
response = client.get(
58+
f"v1/content/{input.guid()}",
59+
)
60+
if response.status_code != 200:
61+
raise Exception(f"Failed to search for {input.guid()}: {response.text}")
62+
63+
data = response.json()
64+
title = data.get("title")
65+
if title is None:
66+
raise Exception(f"Invalid search response from server: {response.text}")
67+
68+
return title
69+
70+
71+
@reactive.calc
72+
def packages_spec():
73+
if input.guid() == "":
74+
return
75+
return get_app_packages(input.guid())
76+
77+
def get_packages(app_packages) -> pd.DataFrame:
78+
package_strs = []
79+
for package in app_packages:
80+
package_str = f'{package["name"]}=={package["version"]}'
81+
package_strs.append(package_str)
82+
83+
response = stream_packages_info(package_strs)
84+
for line in response.iter_lines():
85+
if line:
86+
try:
87+
package_data = json.loads(line)
88+
for package in app_packages:
89+
if package_data.get('name') == package.get('name') and package_data.get('version') == package.get('version'):
90+
vulns = package_data.get('vulns', [])
91+
cves = []
92+
for vuln in vulns:
93+
cves.append(vuln["id"])
94+
available_versions = package_data.get('available_versions', [])
95+
package["cves"] = cves
96+
package["available_versions"] = ", ".join(available_versions)
97+
package["package"] = f"{package.get('name')} {package.get('version')}"
98+
99+
# The newest version is always the first
100+
# TODO we aren't properly accounting for dev versions and such, need to correctly parse versions
101+
package["up_to_date"] = get_up_to_date(package.get('version'), available_versions[0])
102+
break
103+
104+
except json.JSONDecodeError as e:
105+
print(f"Error parsing JSON: {e}")
106+
107+
print(f"Found {len(app_packages)} packages for content '{content_title()}'")
108+
return pd.DataFrame(app_packages)
109+
110+
# TODO don't hard code this
111+
def stream_packages_info(names):
112+
url = "http://ec2-3-84-118-18.compute-1.amazonaws.com/__api__/filter/packages"
113+
headers = {
114+
"Content-Type": "application/json",
115+
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwYWNrYWdlbWFuYWdlciIsImp0aSI6IjJmMDQ5MGNmLWE5NTgtNDQzNy1hZGRmLWJhNzQ0MWNhM2NiNiIsImlhdCI6MTczNjQzOTgzMiwiaXNzIjoicGFja2FnZW1hbmFnZXIiLCJzY29wZXMiOnsiZ2xvYmFsIjoiYWRtaW4ifX0.Ptu-FXvBjw5vJQE7xqhAJqcy2i5_-CLQsH1HITpjpYQ"
116+
}
117+
118+
data = {
119+
"names": names,
120+
"repo": "pypi",
121+
"snapshot": "latest"
122+
}
123+
124+
return requests.post(url, json=data, headers=headers, stream=True)
125+
126+
def get_app_packages(app_guid: str):
127+
# get the specific version used
128+
package_response = client.get(
129+
f'v1/content/{app_guid}/packages'
130+
)
131+
132+
if package_response.status_code != 200:
133+
raise Exception(f'Failed to get packages for {app_guid}: {package_response.text}')
134+
135+
package_data = package_response.json()
136+
if package_data is None:
137+
raise Exception(f"Invalid packages response from server: {package_response.text}")
138+
139+
return package_data
140+
141+
142+
def get_up_to_date(current_version, latest_version):
143+
if current_version >= latest_version:
144+
return "✅"
145+
return "❌"
146+
147+
def prepare_packages_data(df: pd.DataFrame) -> pd.DataFrame:
148+
if len(df) == 0:
149+
return df
150+
151+
for i, row in df.iterrows():
152+
153+
df.at[i, "package_link"] = tags.a(
154+
tags.span("↗", style="font-size: 1.5em"),
155+
target="_blank",
156+
href=f"http://ec2-3-84-118-18.compute-1.amazonaws.com/client/#/repos/pypi/packages/overview?search={row['name']}",
157+
)
158+
159+
if row.cves:
160+
vulns = []
161+
for cve in row.cves:
162+
link = tags.a(
163+
tags.span(cve, style="font-size: 1em"),
164+
target="_blank",
165+
href=f"https://osv.dev/vulnerability/{cve}"
166+
)
167+
vulns.append(link)
168+
169+
df.at[i, "cves"] = tags.span([
170+
item if i == 0 else [", ", item]
171+
for i, item in enumerate(vulns)
172+
])
173+
174+
columns = {
175+
"package_link": "Link",
176+
"name": "Name",
177+
"version": "Version",
178+
"cves": "Known Vulnerabilities",
179+
"up_to_date": "Up to Date",
180+
"available_versions": "Available Versions at Snapshot",
181+
}
182+
df = df[columns.keys()].rename(columns=columns)
183+
return df
184+
185+
186+
@reactive.calc
187+
def has_selection():
188+
return len(input.app_grid_selected_rows()) > 0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name = "connect-extension-vulnerabilities-by-content"
2+
title = "Find Content Vulnerabilities by Content"
3+
description = "Connect Extension: Find Content Vulnerabilities by Content"
4+
access_type = "logged_in"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"version": 1,
3+
"locale": "en_US.UTF-8",
4+
"metadata": {
5+
"appmode": "python-shiny",
6+
"entrypoint": "shiny.express.app:app_2e_py"
7+
},
8+
"python": {
9+
"version": "3.11.9",
10+
"package_manager": {
11+
"name": "pip",
12+
"version": "24.2",
13+
"package_file": "requirements.txt"
14+
}
15+
},
16+
"files": {
17+
"requirements.txt": {
18+
"checksum": "2ed393d51266e315d6e7b55ac26c1062"
19+
},
20+
"app.py": {
21+
"checksum": "61ddac9f526f0d55ab94e3b02eae4070"
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
htmltools
2+
packaging
3+
pandas
4+
posit-sdk
5+
shiny
6+
mobsf==4.1.3
7+
requests

0 commit comments

Comments
 (0)