Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup attendee role and Configure video settings for talks #285

Open
wants to merge 19 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 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
139 changes: 139 additions & 0 deletions server/venueless/api/task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import datetime
import datetime as dt
import logging
import uuid

import jwt
import requests
from celery import shared_task
from django.conf import settings

from venueless.core.models.auth import ShortToken
from venueless.core.models.world import World

logger = logging.getLogger(__name__)


def generate_video_token(world, days, number, traits, long=False):
"""
Generate video token
:param world: World object
:param days: A integer representing the number of days the token is valid
:param number: A integer representing the number of tokens to generate
:param traits: A dictionary representing the traits of the token
:param long: A boolean representing if the token is long or short
:return: A list of tokens
"""
jwt_secrets = world.config.get("JWT_secrets", [])
if not jwt_secrets:
logger.error("JWT_secrets is missing or empty in the configuration")
return
jwt_config = jwt_secrets[0]
secret = jwt_config.get("secret")
audience = jwt_config.get("audience")
issuer = jwt_config.get("issuer")
iat = datetime.datetime.utcnow()
exp = iat + datetime.timedelta(days=days)
result = []
bulk_create = []
for _ in range(number):
payload = {
"iss": issuer,
"aud": audience,
"exp": exp,
"iat": iat,
"uid": str(uuid.uuid4()),
"traits": traits,
}
token = jwt.encode(payload, secret, algorithm="HS256")
if long:
result.append(token)
else:
st = ShortToken(world=world, long_token=token, expires=exp)
result.append(st.short_token)
bulk_create.append(st)

if not long:
ShortToken.objects.bulk_create(bulk_create)
return result


def generate_talk_token(video_settings, video_tokens, event_slug):
"""
Generate talk token
:param video_settings: A dictionary representing the video settings
:param video_tokens: A list of video tokens
:param event_slug: A string representing the event slug
:return: A token
"""
iat = dt.datetime.utcnow()
exp = iat + dt.timedelta(days=30)
payload = {
"exp": exp,
"iat": iat,
"video_tokens": video_tokens,
"slug": event_slug,
}
token = jwt.encode(payload, video_settings.get("secret"), algorithm="HS256")
return token


@shared_task(bind=True, max_retries=5, default_retry_delay=60)
def configure_video_settings_for_talks(
self, world_id, days, number, traits, long=False
):
"""
Configure video settings for talks
:param self: instance of the task
:param world_id: A integer representing the world id
:param days: A integer representing the number of days the token is valid
:param number: A integer representing the number of tokens to generate
:param traits: A dictionary representing the traits of the token
:param long: A boolean representing if the token is long or short
"""
try:
if not isinstance(world_id, str) or not world_id.isalnum():
raise ValueError("Invalid world_id format")
world = World.objects.get(id=world_id)
event_slug = world_id
jwt_secrets = world.config.get("JWT_secrets", [])
if not jwt_secrets:
logger.error("JWT_secrets is missing or empty in the configuration")
return
jwt_config = jwt_secrets[0]
video_tokens = generate_video_token(world, days, number, traits, long)
talk_token = generate_talk_token(jwt_config, video_tokens, event_slug)
header = {
"Content-Type": "application/json",
"Authorization": f"Bearer {talk_token}",
}
payload = {
"video_settings": {
"audience": jwt_config.get("audience"),
"issuer": jwt_config.get("issuer"),
"secret": jwt_config.get("secret"),
}
}
requests.post(
"{}/api/configure-video-settings/".format(settings.EVENTYAY_TALK_BASE_PATH),
json=payload,
headers=header,
)
world.config["pretalx"] = {
"event": event_slug,
"domain": "{}".format(settings.EVENTYAY_TALK_BASE_PATH),
"pushed": datetime.datetime.now().isoformat(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please always use timezone-aware datetime. Calling now without tz will create naive datetime.

"connected": True,
}
world.save()
except requests.exceptions.ConnectionError as e:
logger.error("Connection error: %s", str(e))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to wrap e with str. When the log message is to be built, the % formatter will tell logging to "stringify" the passed arguments.
Wrapping str here is even undesired, it makes "stringify" operation run before we need to build the log message.

self.retry(exc=e)
except requests.exceptions.HTTPError as e:
if e.response.status_code in (401, 403, 404):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use enum from http module, don't use hardcode 401, etc...

logger.error("Non-retryable error: %s", str(e))
raise
logger.error("HTTP error: %s", str(e))
self.retry(exc=e)
except ValueError as e:
logger.error("Error configuring video settings: %s", e)
19 changes: 18 additions & 1 deletion server/venueless/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from venueless.core.services.world import notify_schedule_change, notify_world_change

from ..core.models import Room, World
from .task import configure_video_settings_for_talks
from .utils import get_protocol

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -149,6 +150,18 @@ def post(request, *args, **kwargs) -> JsonResponse:

title = titles.get(locale) or titles.get("en") or title_default

attendee_trait_grants = request.data.get("traits", {}).get("attendee", "")
odkhang marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(attendee_trait_grants, str):
raise ValidationError("Attendee traits must be a string")

trait_grants = {
"admin": ["admin"],
"attendee": (
[attendee_trait_grants] if attendee_trait_grants else ["attendee"]
),
"scheduleuser": ["schedule-update"],
}

# if world already exists, update it, otherwise create a new world
world_id = request.data.get("id")
domain_path = "{}{}/{}".format(
Expand All @@ -165,6 +178,7 @@ def post(request, *args, **kwargs) -> JsonResponse:
world.domain = domain_path
world.locale = request.data.get("locale") or "en"
world.timezone = request.data.get("timezone") or "UTC"
world.trait_grants = trait_grants
world.save()
else:
world = World.objects.create(
Expand All @@ -174,8 +188,11 @@ def post(request, *args, **kwargs) -> JsonResponse:
locale=request.data.get("locale") or "en",
timezone=request.data.get("timezone") or "UTC",
config=config,
trait_grants=trait_grants,
)

configure_video_settings_for_talks.delay(
world_id, days=30, number=1, traits=["schedule-update"], long=True
)
site_url = settings.SITE_URL
protocol = get_protocol(site_url)
world.domain = "{}://{}".format(protocol, domain_path)
Expand Down
3 changes: 3 additions & 0 deletions server/venueless/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@
MEDIA_URL = os.getenv(
"VENUELESS_MEDIA_URL", config.get("urls", "media", fallback="/media/")
)
EVENTYAY_TALK_BASE_PATH = config.get(
"urls", "eventyay-talk", fallback="https://app-test.eventyay.com/talk"
)

WEBSOCKET_PROTOCOL = os.getenv(
"VENUELESS_WEBSOCKET_PROTOCOL",
Expand Down
Loading