Skip to content

Commit

Permalink
Merge pull request #146 from hcrudolph/improved-search
Browse files Browse the repository at this point in the history
Improves search, filtering, and sorting functionality
  • Loading branch information
hcrudolph committed May 27, 2023
2 parents 4d2346b + c6aee81 commit ee4f725
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 134 deletions.
113 changes: 58 additions & 55 deletions directory/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,77 +16,80 @@ def paginate(result_list, current_page, elements_per_page):
result = paginator.page(paginator.num_pages)
return result

def get_cs_by_security_level(sec_level):
"""Returns all CipherSuites of a certain security level."""

if sec_level == 'recommended':
return CipherSuite.objects.filter(security=0)
elif sec_level == 'secure':
return CipherSuite.objects.filter(security__lte=1)
elif sec_level == 'weak':
return CipherSuite.objects.filter(security=2)
elif sec_level == 'insecure':
return CipherSuite.objects.filter(security=3)
def filter_ciphersuites(ciphersuites, sec, tls, lib):
"""Wrapper function for filter_cs_sec, filter_cs_tls, filter_cs_lib."""
ciphersuites = filter_cs_sec(ciphersuites, sec)
ciphersuites = filter_cs_tls(ciphersuites, tls)
ciphersuites = filter_cs_lib(ciphersuites, lib)
return ciphersuites


def filter_cs_sec(ciphersuites, security_level):
"""Filters the given list of ciphersuites by a specified security_level."""

if security_level == 'recommended':
return ciphersuites.filter(security=0)
elif security_level == 'secure':
return ciphersuites.filter(security=1)
elif security_level == 'weak':
return ciphersuites.filter(security=2)
elif security_level == 'insecure':
return ciphersuites.filter(security=3)
else:
return CipherSuite.objects.all()
return ciphersuites

def get_cs_by_tls_version(version):
"""Returns a list of CipherSuite instances filtered by their TLS version."""

if version == 'tls10':
return CipherSuite.objects.filter(tls_version__short='11')
elif version == 'tls12':
return CipherSuite.objects.filter(tls_version__short='12')
elif version == 'tls13':
return CipherSuite.objects.filter(tls_version__short='13')
def filter_cs_tls(ciphersuites, tls_version):
"""Filters the given list of ciphersuites by a specified tls_version."""

if tls_version == 'tls10':
return ciphersuites.filter(tls_version__major=1, tls_version__minor=0)
elif tls_version == 'tls11':
return ciphersuites.filter(tls_version__major=1, tls_version__minor=1)
elif tls_version == 'tls12':
return ciphersuites.filter(tls_version__major=1, tls_version__minor=2)
elif tls_version == 'tls13':
return ciphersuites.filter(tls_version__major=1, tls_version__minor=3)
else:
return CipherSuite.objects.all()
return ciphersuites

def get_cs_by_software(software):
"""Returns a list of CipherSuite instances filtered by their available implementations."""

if software == 'gnutls':
return CipherSuite.objects.exclude(gnutls_name='')
elif software == 'openssl':
return CipherSuite.objects.exclude(openssl_name='')
else:
return CipherSuite.objects.all()

def filter_cs_by_sec_level(cipher_suites, sec_level):
"""Returns a list of CipherSuite instances filtered by their algorithm's vulnerabilities."""

if sec_level == 'insecure':
return cipher_suites.intersection(CipherSuite.objects.filter(security=3))
elif sec_level == 'weak':
return cipher_suites.intersection(CipherSuite.objects.filter(security=2))
elif sec_level == 'secure':
return cipher_suites.intersection(CipherSuite.objects.filter(security__lte=1))
elif sec_level == 'recommended':
return cipher_suites.intersection(CipherSuite.objects.filter(security=0))
def filter_cs_lib(ciphersuites, software_library):
"""Filters the given list of ciphersuites by a specified software_library."""

if software_library == 'openssl':
return ciphersuites.exclude(openssl_name='')
elif software_library == 'gnutls':
return ciphersuites.exclude(gnutls_name='')
else:
return cipher_suites
return ciphersuites


def sort_cipher_suites(cipher_suites, ordering):
"""Sorts the given list of CipherSuite instances in a specific order."""
def sort_ciphersuites(ciphersuites, order):
"""Sorts the given list of ciphersuites in a specified order."""

if ordering == 'asc':
return cipher_suites.order_by('name')
elif ordering == 'desc':
return cipher_suites.order_by('-name')
if order == 'name-asc':
return ciphersuites.order_by('name')
elif order == 'name-desc':
return ciphersuites.order_by('-name')
elif order == 'sec-asc':
return ciphersuites.order_by('-security')
elif order == 'sec-desc':
return ciphersuites.order_by('security')
else:
return cipher_suites
return ciphersuites


def sort_rfcs(rfcs, ordering):
"""Sorts the given list of Rfc instances in a specific order."""
def sort_rfcs(rfcs, order):
"""Sorts the given list of rfcs instances in a specified order."""

if ordering == 'number-asc':
if order == 'number-asc':
return rfcs.order_by('number')
elif ordering == 'number-desc':
elif order == 'number-desc':
return rfcs.order_by('-number')
elif ordering == 'title-asc':
elif order == 'title-asc':
return rfcs.order_by('title')
elif ordering == 'title-desc':
elif order == 'title-desc':
return rfcs.order_by('-title')
else:
return rfcs
Expand Down
40 changes: 14 additions & 26 deletions directory/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,19 @@ def insecure(self):

def search(self, search_term):
# create query and vector object needed for ranking results
query = SearchQuery(search_term)
vector = SearchVector(
'name',
'openssl_name',
'gnutls_name',
'auth_algorithm__long_name',
'enc_algorithm__long_name',
'kex_algorithm__long_name',
'hash_algorithm__long_name',
'protocol_version__vulnerabilities__name',
'auth_algorithm__vulnerabilities__name',
'enc_algorithm__vulnerabilities__name',
'kex_algorithm__vulnerabilities__name',
'hash_algorithm__vulnerabilities__name',
'protocol_version__vulnerabilities__description',
'auth_algorithm__vulnerabilities__description',
'enc_algorithm__vulnerabilities__description',
'kex_algorithm__vulnerabilities__description',
'hash_algorithm__vulnerabilities__description'
)
query = SearchQuery(search_term.strip())
vector = SearchVector('name', weight='A') + \
SearchVector('openssl_name', weight='A') +\
SearchVector('gnutls_name', weight='A') + \
SearchVector('auth_algorithm__long_name', weight='B') + \
SearchVector('enc_algorithm__long_name', weight='B') + \
SearchVector('kex_algorithm__long_name', weight='B') + \
SearchVector('hash_algorithm__long_name', weight='B') + \
SearchVector('protocol_version__vulnerabilities__name', weight='C') + \
SearchVector('auth_algorithm__vulnerabilities__name', weight='C') + \
SearchVector('enc_algorithm__vulnerabilities__name', weight='C') + \
SearchVector('kex_algorithm__vulnerabilities__name', weight='C') + \
SearchVector('hash_algorithm__vulnerabilities__name', weight='C')

# retrieve list of all results ordered by decreasing relevancy
ranked_results = CipherSuite.objects.annotate(
Expand All @@ -114,12 +107,7 @@ def search(self, search_term):
Q(auth_algorithm__vulnerabilities__name__icontains=search_term)|
Q(enc_algorithm__vulnerabilities__name__icontains=search_term)|
Q(kex_algorithm__vulnerabilities__name__icontains=search_term)|
Q(hash_algorithm__vulnerabilities__name__icontains=search_term)|
Q(protocol_version__vulnerabilities__description__icontains=search_term)|
Q(auth_algorithm__vulnerabilities__description__icontains=search_term)|
Q(enc_algorithm__vulnerabilities__description__icontains=search_term)|
Q(kex_algorithm__vulnerabilities__description__icontains=search_term)|
Q(hash_algorithm__vulnerabilities__description__icontains=search_term)
Q(hash_algorithm__vulnerabilities__name__icontains=search_term)
)
).distinct()

Expand Down
2 changes: 1 addition & 1 deletion directory/templates/directory/index_cs.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ <h1>{{ count }} Cipher Suites</h1>
{% elif cipher_suite.secure %}<span class="badge bg-fixed-width bg-secure">Secure</span>
{% else %}<span class="badge bg-fixed-width bg-success">Recommended</span>
{% endif %}
{% if search_type == 'openssl' %} {{ cipher_suite.openssl_name }} {% else %} {{ cipher_suite.name }} {% endif %}</a></li>
{{ cipher_suite.name }}</a></li>
{% endfor %}
</ul>
{% else %}
Expand Down
6 changes: 4 additions & 2 deletions directory/templates/directory/list_filters.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
</button>
<ul class="dropdown-menu" aria-labelledby="sortDropdownButton">
{% block list_filters_search %}{% endblock list_filters_search %}
<li><a class="dropdown-item {% if sorting == 'asc' %}active{% endif %}" href="{% relative_url 'asc' 'sort' params %}">Ascending</a></li>
<li><a class="dropdown-item {% if sorting == 'desc' %}active{% endif %}" href="{% relative_url 'desc' 'sort' params %}">Descending</a></li>
<li><a class="dropdown-item {% if sorting == 'sec-asc' %}active{% endif %}" href="{% relative_url 'sec-asc' 'sort' params %}">Security (ascending)</a></li>
<li><a class="dropdown-item {% if sorting == 'sec-desc' %}active{% endif %}" href="{% relative_url 'sec-desc' 'sort' params %}">Security (descending)</a></li>
<li><a class="dropdown-item {% if sorting == 'name-asc' %}active{% endif %}" href="{% relative_url 'name-asc' 'sort' params %}">Name (ascending)</a></li>
<li><a class="dropdown-item {% if sorting == 'name-desc' %}active{% endif %}" href="{% relative_url 'name-desc' 'sort' params %}">Name (descending)</a></li>
</ul>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion directory/templates/directory/list_filters_search.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{% with params=request.GET.urlencode %}

{% block list_filters_search %}
<li><a class="dropdown-item {% if sorting == 'rel' or sorting == '' %}active{% endif %}" href="{% relative_url 'rel' 'sort' params %}">Relevancy</a></li>
<li><a class="dropdown-item {% if sorting == 'rel' or sorting == '' %}active{% endif %}" href="{% relative_url 'rel' 'sort' params %}">Search relevancy</a></li>
<li><hr class="dropdown-divider"></li>
{% endblock %}

Expand Down
18 changes: 6 additions & 12 deletions directory/templates/directory/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
<div class="container">
<div class="row flex-row justify-content-center">
<div class="col-lg-8 col-lg-offset-2">
<h1>Search results</h1>
<h4 class="long-string text-muted">"{{ search_term }}"</h4>
<h1>Search results for "{{ search_term }}"</h1>
</div><!-- end column -->
<div class="col-lg-8 col-lg-offset-2">
{% include "directory/list_filters_search.html" %}
Expand All @@ -18,17 +17,17 @@ <h4 class="long-string text-muted">"{{ search_term }}"</h4>
<ul class="nav nav-tabs nav-justified">
{% if cs_tab_active %}
<li class="nav-item">
<a class="nav-link active" href="#">Cipher Suites <span class="badge badge-dark">{{ result_count_cs }}</span></a>
<a class="nav-link active" href="#">Cipher Suites <span class="badge bg-secondary">{{ result_count_cs }}</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/search/?q={{ search_term }}&cat=rfc">RFCs <span class="badge badge-secondary">{{ result_count_rfc }}</span></a>
<a class="nav-link" href="/search/?q={{ search_term }}&cat=rfc">RFCs <span class="badge bg-secondary">{{ result_count_rfc }}</span></a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="/search/?q={{ search_term }}&cat=cs">Cipher Suites <span class="badge badge-secondary">{{ result_count_cs }}</span></a>
<a class="nav-link" href="/search/?q={{ search_term }}&cat=cs">Cipher Suites <span class="badge bg-secondary">{{ result_count_cs }}</span></a>
</li>
<li class="nav-item">
<a class="active nav-link" href="#">RFCs <span class="badge badge-dark">{{ result_count_rfc }}</span></a>
<a class="active nav-link" href="#">RFCs <span class="badge bg-secondary">{{ result_count_rfc }}</span></a>
</li>
{% endif %}
</ul>
Expand All @@ -53,12 +52,7 @@ <h4 class="long-string text-muted">"{{ search_term }}"</h4>
{% else %}
<span class="badge bg-fixed-width bg-success">Recommended</span>
{% endif %}

{% if search_type == 'openssl' and element.openssl_name != '' %}
{{ element.openssl_name }}
{% else %}
{{ element.name }}
{% endif %}
{{ element.name }}
</a>
</li>
{% else %}
Expand Down
62 changes: 25 additions & 37 deletions directory/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,37 +48,34 @@ def index_cs(request):
single_page = request.GET.get('singlepage', 'false')
page = request.GET.get('page', '1')

# get subsets based on list filters
cs_by_sl = get_cs_by_security_level(sec_level)
cs_by_sw = get_cs_by_software(software)
cs_by_tv = get_cs_by_tls_version(tls_version)
ciphersuites = CipherSuite.objects.all()

# create intersection of all subsets
cipher_suites = cs_by_sl.intersection(cs_by_sw, cs_by_tv)
# Filtering
ciphersuites = filter_ciphersuites(
ciphersuites, sec_level, tls_version, software)

if len(cipher_suites) > 0:
cipher_suites = sort_cipher_suites(cipher_suites, sorting)
# Sorting
ciphersuites = sort_ciphersuites(ciphersuites, sorting)

if len(ciphersuites) > 0:
ciphersuites = sort_ciphersuites(ciphersuites, sorting)

# paginate depending on GET parameter
if single_page == 'true' and len(cipher_suites) > 0:
cipher_suites_paginated = paginate(
cipher_suites, page, len(cipher_suites))
if single_page == 'true' and len(ciphersuites) > 0:
ciphersuites_paginated = paginate(
ciphersuites, page, len(ciphersuites))
else:
cipher_suites_paginated = paginate(
cipher_suites, page, 15)

# display CS name format according to search query
search_type = 'openssl' if software == 'openssl' else 'iana'
ciphersuites_paginated = paginate(
ciphersuites, page, 15)

sponsor = Sponsor.objects.first()

context = {
'count': cipher_suites_paginated.paginator.count,
'count': ciphersuites_paginated.paginator.count,
'navbar_context': 'cs',
'page_number_range': cipher_suites_paginated.paginator.page_range,
'results': cipher_suites_paginated,
'page_number_range': ciphersuites_paginated.paginator.page_range,
'results': ciphersuites_paginated,
'search_form': NavbarSearchForm(),
'search_type': search_type,
'sec_level': sec_level,
'singlepage': single_page,
'software': software,
Expand Down Expand Up @@ -193,24 +190,16 @@ def search(request):
category = request.GET.get('cat', 'cs')
page = request.GET.get('page', '1')

# display cs name format according to search query
search_type = 'openssl' if ('-' in search_term) or (software == 'openssl') else 'iana'

# get subsets based on search term
ranked_list = search_cipher_suites(search_term)
search_result = CipherSuite.objects.filter(pk__in=ranked_list.values_list('name', flat=True))

# get subsets based on list filters
cs_by_sl = get_cs_by_security_level(sec_level)
cs_by_sw = get_cs_by_software(software)
cs_by_tv = get_cs_by_tls_version(tls_version)

# create intersection of all subsets
cipher_suites = search_result.intersection(cs_by_sl, cs_by_sw, cs_by_tv)
# Searching
ciphersuites = search_cipher_suites(search_term)
rfcs = search_rfcs(search_term)

# Query list returned from db is already sorted by relevancy
result_list_cs = sort_cipher_suites(cipher_suites, sorting)
# Filtering
ciphersuites = filter_ciphersuites(
ciphersuites, sec_level, tls_version, software)

# Sorting
result_list_cs = sort_ciphersuites(ciphersuites, sorting)
result_list_rfc = sort_rfcs(rfcs, sorting)

# distinguish results to display by category
Expand Down Expand Up @@ -238,7 +227,6 @@ def search(request):
'results': result_list_paginated,
'search_form': NavbarSearchForm(),
'search_term': search_term,
'search_type': search_type,
'sec_level': sec_level,
'singlepage': single_page,
'software': software,
Expand Down

0 comments on commit ee4f725

Please sign in to comment.