You've already forked oncall
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:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
64
src/oncall/api/v0/ical_key.py
Normal file
64
src/oncall/api/v0/ical_key.py
Normal 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()
|
||||
69
src/oncall/api/v0/ical_key_team.py
Normal file
69
src/oncall/api/v0/ical_key_team.py
Normal 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')
|
||||
83
src/oncall/api/v0/ical_key_user.py
Normal file
83
src/oncall/api/v0/ical_key_user.py
Normal 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')
|
||||
Reference in New Issue
Block a user