Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/bornhack/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,22 +221,18 @@
"SCOPES": {
# required
"openid": "OpenID Connect scope",

# deprecated api scope, remove after 2025 camp
"profile:read": "Allow the remote site to read your bornhack.dk username (uuid), user id, profile public credit name, profile description, and a list of team memberships using the profile API endpoint (scope profile:read). NOTE: This scope is being deprecated soon! Ask the BornHack website team for more info.",

# standard OIDC claim scopes
"profile": "Allow the remote site to read your profile public_credit_name, description, and update time (scope: profile)",
"email": "Allow the remote site to read your email address (scope: email)",
"address": "Allow the remote site to read your profile location (scope: address)",
"phone": "Allow the remote site to read your profile phonenumber (scope: phone)",

# custom bornhack user claim scopes
"groups:read": "Allow the remote site to read a list of your group memberships (scope: groups:read).",
"location:read": "Allow the remote site to read your profile location (scope: loocation:read)",
"permissions:read": "Allow the remote site to read a list of your assigned permissions (scope: permissions:read).",
"teams:read": "Allow the remote site to read a list of your team memberships and team lead status (scope: teams:read)",

# api scopes
"phonebook:admin": "Allow the remote site to read the camp phonebook, including service numbers and unlisted numbers. Also allow the remote site to use to the POC API. This scope is only relevant for POC team leads (scope: phonebook:admin).",
"phonebook:read": "Allow the remote site to read the camp phonebook (scope: phonebook:read).",
Expand Down
8 changes: 7 additions & 1 deletion src/profiles/forms.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
from django import forms
from bornhack.oauth_validators import BornhackOAuth2Validator


def get_scopes() -> list[str]:
validator = BornhackOAuth2Validator()
return ((scope, scope) for scope in sorted(set(validator.oidc_claim_scope.values())) if scope!="openid")
return (
(scope, scope)
for scope in sorted(set(validator.oidc_claim_scope.values()))
if scope != "openid"
)


class OIDCForm(forms.Form):
scopes = forms.MultipleChoiceField(
Expand Down
68 changes: 34 additions & 34 deletions src/profiles/templates/oidc.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,53 +11,53 @@
<h4>OIDC Claims</h4>
</div>
<div class="card-body">
<p class="lead">When using BornHack as an IDP (logging into other sites using your BornHack account) you can control which <i>user claims</i> are shared with the remote site by asking for one or more of the following <i>claim scopes</i>:</p>
<p class="lead">When using BornHack as an IDP (logging into other sites using your BornHack account) you can control which <i>user claims</i> are shared with the remote site by asking for one or more of the following <i>claim scopes</i>:</p>
<p><ul>
{% for scope in all_scopes %}
<li><code>{{ scope }}</code></li>
<li><code>{{ scope }}</code></li>
{% endfor %}
</ul></p>
<p>Note: In addition to this list the default <code>openid</code> scope is available (it is part of the standard) and must always be included when asking for a jwt.</p>
<p class="lead">This form allows you to see which OIDC user claims are returned for your user with any combination of scopes.</p>
<form method="GET">
{% bootstrap_form form %}
<button class="btn btn-primary" type="submit">Submit</button>
{% bootstrap_form form %}
<button class="btn btn-primary" type="submit">Submit</button>
</form>
<hr>
{% if not active_scopes %}
<p class="lead">Select scopes in the form to see user claims</p>
<p class="lead">Select scopes in the form to see user claims</p>
{% else %}
<p class="lead">The following user claims will be returned in a jwt with these scopes:</p>
<p>
<ul>
{% for scope in active_scopes %}
<li><code>{{ scope }}</code></li>
{% endfor %}
</ul>
</p>
<table class="table table-striped">
<tr>
<th>Claim Name</th>
<th>Required Scope</th>
<th>Claim Value (JSON)</th>
</tr>
<tr>
<td><code>sub</code></td>
<td><code>openid</code></td>
<td>{{ request.user.username }}</td>
</tr>
{% for claim, value in claims.items %}
{% for claimname, scope in scopes.items %}
{% if claimname == claim %}
<p class="lead">The following user claims will be returned in a jwt with these scopes:</p>
<p>
<ul>
{% for scope in active_scopes %}
<li><code>{{ scope }}</code></li>
{% endfor %}
</ul>
</p>
<table class="table table-striped">
<tr>
<td><code>{{ claim }}</code></td>
<td><code>{{ scope }}</code></td>
<td>{{ value }}</td>
<th>Claim Name</th>
<th>Required Scope</th>
<th>Claim Value (JSON)</th>
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</table>
<tr>
<td><code>sub</code></td>
<td><code>openid</code></td>
<td>{{ request.user.username }}</td>
</tr>
{% for claim, value in claims.items %}
{% for claimname, scope in scopes.items %}
{% if claimname == claim %}
<tr>
<td><code>{{ claim }}</code></td>
<td><code>{{ scope }}</code></td>
<td>{{ value }}</td>
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</table>
{% endif %}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/profiles/templates/profile_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ <h2>Your BornHack Account</h2>
{% url 'profiles:oidc' as profile_oidc_url %}
<li class="nav-item">
<a class="nav-link{% if request.path == profile_oidc_url %} active{% endif %}" href="{{ profile_oidc_url }}">
OIDC Scope<i class="fas fa-arrow-right"></i>Claim
OIDC Scope<i class="fas fa-arrow-right"></i>Claim
</a>
</li>

Expand Down
8 changes: 5 additions & 3 deletions src/profiles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
scopes = self.request.GET.getlist(key="scopes")
self.initial['scopes'] = scopes
self.initial["scopes"] = scopes
return form_class(**self.get_form_kwargs())

def get_context_data(self, **kwargs):
Expand All @@ -133,7 +133,9 @@ def get_context_data(self, **kwargs):
if scope in self.request.GET.getlist(key="scopes"):
context["claims"][claim] = value
context["scopes"] = self.scopes
context["active_scopes"] = ["openid"] + sorted(set(self.request.GET.getlist(key="scopes")))
context["active_scopes"] = ["openid"] + sorted(
set(self.request.GET.getlist(key="scopes")),
)
context["all_scopes"] = sorted(set(self.scopes.values()))
del(context["all_scopes"][context["all_scopes"].index("openid")])
del context["all_scopes"][context["all_scopes"].index("openid")]
return context
9 changes: 8 additions & 1 deletion src/program/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ class Meta:
"title",
"abstract",
"allow_video_recording",
"allow_video_streaming",
"duration",
"tags",
"slides_url",
Expand Down Expand Up @@ -313,8 +314,9 @@ def __init__(self, camp, event_type=None, matrix=None, *args, **kwargs):
self.fields["track"].empty_label = None
self.fields["track"].queryset = EventTrack.objects.filter(camp=camp)

# make sure video_recording checkbox defaults to checked
# make sure video_recording and streaming checkbox defaults to checked
self.fields["allow_video_recording"].initial = True
self.fields["allow_video_streaming"].initial = True

if event_type.name not in [TALK, LIGHTNING_TALK]:
# Only talk or lightning talk should show the slides_url field
Expand Down Expand Up @@ -374,6 +376,7 @@ def __init__(self, camp, event_type=None, matrix=None, *args, **kwargs):

# no video recording for music acts
del self.fields["allow_video_recording"]
del self.fields["allow_video_streaming"]

elif event_type.name == RECREATIONAL_EVENT:
# fix label and help_text for the title field
Expand All @@ -394,6 +397,7 @@ def __init__(self, camp, event_type=None, matrix=None, *args, **kwargs):

# no video recording for music acts
del self.fields["allow_video_recording"]
del self.fields["allow_video_streaming"]

elif event_type.name in [TALK, LIGHTNING_TALK]:
# fix label and help_text for the title field
Expand Down Expand Up @@ -439,6 +443,7 @@ def __init__(self, camp, event_type=None, matrix=None, *args, **kwargs):

# no video recording for workshops
del self.fields["allow_video_recording"]
del self.fields["allow_video_streaming"]

elif event_type.name == RECREATIONAL_EVENT:
# fix label and help_text for the title field
Expand All @@ -459,6 +464,7 @@ def __init__(self, camp, event_type=None, matrix=None, *args, **kwargs):

# no video recording for recreational events
del self.fields["allow_video_recording"]
del self.fields["allow_video_streaming"]

elif event_type.name == MEETUP:
# fix label and help_text for the title field
Expand All @@ -479,6 +485,7 @@ def __init__(self, camp, event_type=None, matrix=None, *args, **kwargs):

# no video recording for meetups
del self.fields["allow_video_recording"]
del self.fields["allow_video_streaming"]

else:
raise ImproperlyConfigured(
Expand Down
34 changes: 34 additions & 0 deletions src/program/migrations/0106_event_video_streaming_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 4.2.20 on 2025-04-21 17:36

from django.db import migrations, models

def update_streaming(apps, schema_editor):
# We can't import the models directly as it may be a newer
# version than this migration expects. We use the historical version.
Event = apps.get_model("program", "Event")
EventProposal = apps.get_model("program", "EventProposal")

Event.objects.filter(video_recording=True).update(video_streaming=True)
Event.objects.filter(video_recording=False).update(video_streaming=False)
EventProposal.objects.filter(allow_video_recording=False).update(allow_video_streaming=False)
EventProposal.objects.filter(allow_video_recording=True).update(allow_video_streaming=True)

class Migration(migrations.Migration):

dependencies = [
('program', '0105_cascade_delete_event_urls'),
]

operations = [
migrations.AddField(
model_name='event',
name='video_streaming',
field=models.BooleanField(default=True, help_text='Do we intend to stream video of this event?'),
),
migrations.AddField(
model_name='eventproposal',
name='allow_video_streaming',
field=models.BooleanField(default=False, help_text='Uncheck if you do not want the event live streamed.'),
),
migrations.RunPython(update_streaming),
]
33 changes: 27 additions & 6 deletions src/program/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,11 @@ class EventProposal(ExportModelOperationsMixin("event_proposal"), UserSubmittedM
help_text="Recordings are made available under the <b>CC BY-SA 4.0</b> license. Uncheck if you do not want the event recorded, or if you cannot accept the license.",
)

allow_video_streaming = models.BooleanField(
default=False,
help_text="Uncheck if you do not want the event live streamed.",
)

duration = models.IntegerField(
blank=True,
help_text="How much time (in minutes) should we set aside for this event?",
Expand Down Expand Up @@ -609,6 +614,7 @@ def mark_as_approved(self, request=None):
event.event_type = self.event_type
event.proposal = self
event.video_recording = self.allow_video_recording
event.video_streaming = self.allow_video_streaming
event.save()
# loop through the speaker_proposals linked to this event_proposal and associate any related speaker objects with this event
for sp in self.speakers.all():
Expand Down Expand Up @@ -1207,10 +1213,12 @@ def get_ics_event(self):
domain = Site.objects.get_current().domain
speakers = ", ".join(self.event.speakers.all().values_list("name", flat=True))
recorded = "Yes" if self.event.video_recording else "No"
streamed = "Yes" if self.event.video_streaming else "No"
ievent["description"] = (
f"URL: https://{domain}{self.event.get_absolute_url()}\n\n"
f"Speaker(s): {speakers}\n\n"
f"Recorded: {recorded}\n\n"
f"Streamed: {streamed}\n\n"
f"{self.event.abstract}"
)
ievent["dtstart"] = icalendar.vDatetime(self.when.lower).to_ical()
Expand Down Expand Up @@ -1308,6 +1316,11 @@ class Event(ExportModelOperationsMixin("event"), CampRelatedModel):
help_text="Do we intend to record video of this event?",
)

video_streaming = models.BooleanField(
default=True,
help_text="Do we intend to stream video of this event?",
)

proposal = models.OneToOneField(
"program.EventProposal",
null=True,
Expand Down Expand Up @@ -1378,10 +1391,14 @@ def serialize(self):
"event_type": self.event_type.name,
}

if self.video_recording:
video_state = "to-be-recorded"
if self.video_recording and self.video_streaming:
video_state = "to-be-streamed-to-be-recorded"
elif self.video_recording:
video_state = "to-be-recorded-not-to-be-streamed"
elif self.video_streaming:
video_state = "to-be-streamed-not-to-be-recorded"
Comment thread
tykling marked this conversation as resolved.
else:
video_state = "not-to-be-recorded"
video_state = "not-to-be-recorded-not-to-be-streamed"

data["video_state"] = video_state

Expand Down Expand Up @@ -1513,10 +1530,14 @@ def serialize(self, user=None):
"timeslots": self.timeslots,
}

if self.event.video_recording:
video_state = "to-be-recorded"
if self.event.video_recording and self.event.video_streaming:
video_state = "to-be-recorded-to-be-streamed"
elif self.event.video_recording:
video_state = "to-be-recorded-not-to-be-streamed"
elif self.event.video_streaming:
video_state = "to-be-streamed-not-to-be-recorded"
Comment thread
tykling marked this conversation as resolved.
else:
video_state = "not-to-be-recorded"
video_state = "not-to-be-recorded-not-to-be-streamed"

data["video_state"] = video_state

Expand Down
15 changes: 13 additions & 2 deletions src/program/templates/event_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,25 @@ <h4>
<h4>Metadata for <i>{{ event.title }}</i></h4>
<strong>To be recorded</strong>:
<span class="fa-stack">
<i class="fas fa-video fa-stack-1x"></i>
{% if event.video_recording %}
<i class="far fa-circle fa-stack-2x"></i>
<i class="fas fa-floppy-disk fa-stack-1x"></i>
{% else %}
<i class="fas fa-floppy-disk fa-stack-1x"></i>
<i class="fas fa-ban fa-stack-2x text-danger"></i>
{% endif %}
</span>
{{ event.video_recording|yesno:"Yes,No" }}
<br>
<strong>To be streamed</strong>:
<span class="fa-stack">
{% if event.video_streaming %}
<i class="far fa-broadcast-tower fa-stack-1x"></i>
{% else %}
<i class="fas fa-broadcast-tower fa-stack-1x"></i>
<i class="fas fa-ban fa-stack-2x text-danger"></i>
{% endif %}
</span>
{{ event.video_streaming|yesno:"Yes,No" }}
<hr>

<h4>URLs for <i>{{ event.title }}</i></h4>
Expand Down
24 changes: 14 additions & 10 deletions src/program/templates/includes/event_list_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
<th data-priority="2">Event Type</th>
<th data-priority="100" class="text-center">Tags</th>
<th data-priority="3">Speakers</th>
<th data-priority="100"><i class="fas fa-video"></i></th>
<th data-priority="100"><i class="fas fa-video" title="Recording" alt="Recording"></i></th>
<th data-priority="100"><i class="fas fa-floppy-disk" title="Recording" alt="Recording"></i></th>
<th data-priority="100"><i class="fas fa-broadcast-tower" title="Streaming" alt="Streaming"></i></th>
<th data-priority="1">Scheduled</th>
</tr>
</thead>
Expand Down Expand Up @@ -37,15 +39,17 @@
N/A
{% endfor %}
</td>
<td class="text-center"><span class="hidden">{{ event.video_recording }}</span>{{ event.video_recording|truefalseicon }}</td>
<td data-order="{{ event.event_slots.all.0.when.lower|date:"YmdHis" }}">
{% for slot in event.event_slots.all %}
{{ slot.event_location.icon_html }} {{ slot.event_location.name }} at {{ slot.when.lower }}<br>
{% empty %}
<i>Not scheduled yet</i>
{% endfor %}
</td>
</tr>
<td class="text-center"><span class="hidden">{% if event.video_recording or event.video_streaming %}True</span>{{ True|truefalseicon }}{% else %}False</span>{{ False|truefalseicon }}{% endif %}</td>
<td class="text-center" title="Recording: {{ event.video_recording|yesno }}"><span class="hidden">{{ event.video_recording }}</span>{{ event.video_recording|truefalseicon }}</td>
<td class="text-center" title="Streaming: {{ event.video_streaming|yesno }}"><span class="hidden">{{ event.video_streaming }}</span>{{ event.video_streaming|truefalseicon }}</td>
<td data-order="{{ event.event_slots.all.0.when.lower|date:"YmdHis" }}">
{% for slot in event.event_slots.all %}
{{ slot.event_location.icon_html }} {{ slot.event_location.name }} at {{ slot.when.lower }}<br>
{% empty %}
<i>Not scheduled yet</i>
{% endfor %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
Expand Down
Loading