1
0
mirror of https://github.com/linkedin/oncall.git synced 2025-11-27 23:18:38 +02:00

CRUD operations for ical_key (#299)

* CRUD operations for ical_key

All HTTP endpoints around ical_key should be authenticated so that we
can easily track what keys are created by this logged-in user.

* add missing semicolon

* store unix_timestamp as BIGINT
This commit is contained in:
Colin Yang
2020-03-06 13:55:45 -08:00
committed by GitHub
parent 5b9520837f
commit 651ca21fd2
5 changed files with 233 additions and 0 deletions

View File

@@ -434,6 +434,19 @@ CREATE TABLE IF NOT EXISTS `application` (
PRIMARY KEY (`id`)
);
-- -----------------------------------------------------
-- Table `ical_key`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `ical_key` (
`key` CHAR(36) CHARACTER SET ascii NOT NULL,
`requester` CHAR(255) NOT NULL,
`name` CHAR(255) NOT NULL,
`type` ENUM('team', 'user') NOT NULL,
`time_created` BIGINT(20) NOT NULL,
PRIMARY KEY (`requester`, `name`, `type`),
INDEX `key_idx` (`KEY`)
);
CREATE TABLE IF NOT EXISTS `team_subscription` (
`team_id` BIGINT(20) UNSIGNED NOT NULL,
`subscription_id` BIGINT(20) UNSIGNED NOT NULL,

View File

@@ -102,6 +102,10 @@ def init(application, config):
application.add_route('/api/v0/users/{user_name}/ical', user_ical)
application.add_route('/api/v0/teams/{team}/ical', team_ical)
from . import ical_key_user, ical_key_team
application.add_route('/api/v0/ical_key/user/{user_name}', ical_key_user)
application.add_route('/api/v0/ical_key/team/{team}', ical_key_team)
# Optional Iris integration
from . import iris_settings
application.add_route('/api/v0/iris_settings', iris_settings)

View File

@@ -0,0 +1,64 @@
# Copyright (c) LinkedIn Corporation. All rights reserved. Licensed under the BSD-2 Clause license.
# See LICENSE in the project root for license information.
from ... import db
def get_ical_key(requester, name, type):
connection = db.connect()
cursor = connection.cursor()
cursor.execute(
'''
SELECT `key`
FROM `ical_key`
WHERE
`requester` = %s AND
`name` = %s AND
`type` = %s
''',
(requester, name, type))
if cursor.rowcount == 0:
key = None
else:
key = cursor.fetchone()[0]
cursor.close()
connection.close()
return key
def update_ical_key(requester, name, type, key):
connection = db.connect()
cursor = connection.cursor()
cursor.execute(
'''
INSERT INTO `ical_key` (`key`, `requester`, `name`, `type`, `time_created`)
VALUES (%s, %s, %s, %s, UNIX_TIMESTAMP())
ON DUPLICATE KEY UPDATE `key` = %s, `time_created` = UNIX_TIMESTAMP()
''',
(key, requester, name, type, key))
connection.commit()
cursor.close()
connection.close()
def delete_ical_key(requester, name, type):
connection = db.connect()
cursor = connection.cursor()
cursor.execute(
'''
DELETE FROM `ical_key`
WHERE
`requester` = %s AND
`name` = %s AND
`type` = %s
''',
(requester, name, type))
connection.commit()
cursor.close()
connection.close()

View File

@@ -0,0 +1,69 @@
# Copyright (c) LinkedIn Corporation. All rights reserved. Licensed under the BSD-2 Clause license.
# See LICENSE in the project root for license information.
import uuid
from falcon import HTTPNotFound, HTTP_201
from ...auth import login_required, check_calendar_auth
from .ical_key import get_ical_key, update_ical_key, delete_ical_key
@login_required
def on_get(req, resp, team):
"""Get the secret key that grants public access to team's oncall
calendar for the logged-in user.
Current policy only allows access to the team that the logged-in
user is part of.
**Example request:**
.. sourcecode:: http
GET /api/v0/ical_key/team/jteam HTTP/1.1
Content-Type: text/plain
ef895425-5f49-11ea-8eee-10e7c6352aff
"""
challenger = req.context['user']
check_calendar_auth(team, req)
key = get_ical_key(challenger, team, 'team')
if key is None:
raise HTTPNotFound()
resp.body = key
resp.set_header('Content-Type', 'text/plain')
@login_required
def on_post(req, resp, team):
"""Update or create the secret key that grants public access to team's
oncall calendar for the logged-in user.
Current policy only allows access to the team that the logged-in
user is part of.
"""
challenger = req.context['user']
check_calendar_auth(team, req)
update_ical_key(challenger, team, 'team', str(uuid.uuid4()))
resp.status = HTTP_201
@login_required
def on_delete(req, resp, team):
"""Delete the secret key that grants public access to team's oncall
calendar for the logged-in user.
Current policy only allows access to the team that the logged-in
user is part of.
"""
challenger = req.context['user']
check_calendar_auth(team, req)
delete_ical_key(challenger, team, 'team')

View File

@@ -0,0 +1,83 @@
# Copyright (c) LinkedIn Corporation. All rights reserved. Licensed under the BSD-2 Clause license.
# See LICENSE in the project root for license information.
import uuid
from falcon import HTTPNotFound, HTTPForbidden, HTTP_201
from ...auth import login_required
from .ical_key import get_ical_key, update_ical_key, delete_ical_key
@login_required
def on_get(req, resp, user_name):
"""Get the secret key that grants public access to user_name's oncall
calendar for the logged-in user.
Current policy only allows the logged-in user to get its own key,
so user_name parameter must be the same as the logged-in user.
**Example request:**
.. sourcecode:: http
GET /api/v0/ical_key/user/jdoe HTTP/1.1
Content-Type: text/plain
ef895425-5f49-11ea-8eee-10e7c6352aff
"""
challenger = req.context['user']
if challenger != user_name:
raise HTTPForbidden(
'Unauthorized',
'Action not allowed: "%s" is not allowed to view ical_key of "%s"' % (challenger, user_name)
)
key = get_ical_key(challenger, user_name, 'user')
if key is None:
raise HTTPNotFound()
resp.body = key
resp.set_header('Content-Type', 'text/plain')
@login_required
def on_post(req, resp, user_name):
"""Update or create the secret key that grants public access to
user_name's oncall calendar for the logged-in user. Updating the
secret key will automatically invalidate existing secret keys. A
subsequent GET will get the secret key.
Current policy only allows the logged-in user to get its own key,
so user_name parameter must be the same as the logged-in user.
"""
challenger = req.context['user']
if challenger != user_name:
raise HTTPForbidden(
'Unauthorized',
'Action not allowed: "%s" is not allowed to update ical_key of "%s"' % (challenger, user_name)
)
update_ical_key(challenger, user_name, 'user', str(uuid.uuid4()))
resp.status = HTTP_201
@login_required
def on_delete(req, resp, user_name):
"""Delete the secret key that grants public access to user_name's
oncall calendar for the logged-in user.
Current policy only allows the logged-in user to get its own key,
so user_name parameter must be the same as the logged-in user.
"""
challenger = req.context['user']
if challenger != user_name:
raise HTTPForbidden(
'Unauthorized',
'Action not allowed: "%s" is not allowed to delete ical_key of "%s"' % (challenger, user_name)
)
delete_ical_key(challenger, user_name, 'user')