Added cron plugin to clean old access tokens.

This cron plugin runs every hour and deletes any access tokens whose
expiration date has passed for more than a week. Tests provided.

Change-Id: Ief9b25b684364f3ecdf58b2725fd2bb4228a6720
This commit is contained in:
Michael Krotscheck 2014-12-09 16:26:32 -08:00
parent a4065b7fca
commit 16e11b0beb
6 changed files with 149 additions and 5 deletions

View File

@ -42,6 +42,7 @@ storyboard.worker.task =
storyboard.plugin.user_preferences =
storyboard.plugin.cron =
cron-management = storyboard.plugin.cron.manager:CronManager
token-cleaner = storyboard.plugin.oauth.cleaner:TokenCleaner
[build_sphinx]
source-dir = doc/source

View File

View File

@ -0,0 +1,59 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime
from datetime import timedelta
import storyboard.db.api.base as api_base
from storyboard.db.models import AccessToken
from storyboard.plugin.cron.base import CronPluginBase
class TokenCleaner(CronPluginBase):
"""A Cron Plugin which checks periodically for expired auth tokens and
removes them from the database. By default it only cleans up expired
tokens that are more than a week old, to permit some historical debugging
forensics.
"""
def enabled(self):
"""Indicate whether this plugin is enabled. This indicates whether
this plugin alone is runnable, as opposed to the entire cron system.
"""
return True
def interval(self):
"""This plugin executes on startup, and once every hour after that.
:return: "? * * * *"
"""
return "? * * * *"
def run(self, start_time, end_time):
"""Remove all oauth tokens that are more than a week old.
:param start_time: The last time the plugin was run.
:param end_time: The current timestamp.
"""
# Calculate last week.
lastweek = datetime.utcnow() - timedelta(weeks=1)
# Build the query.
query = api_base.model_query(AccessToken)
# Apply the filter.
query = query.filter(AccessToken.expires_at < lastweek)
# Batch delete.
query.delete()

View File

@ -245,8 +245,8 @@ class TestCronManager(base.TestCase):
manager = cronmanager.CronManager(CONF, tabfile=self.tabfile)
manager.execute()
# We're expecting 1 in-branch plugins.
self.assertCronLength(1, command='storyboard-cron')
# We're expecting 2 in-branch plugins.
self.assertCronLength(2, command='storyboard-cron')
def test_execute_update(self):
"""Test that execute() method updates plugins."""
@ -311,9 +311,8 @@ class TestCronManager(base.TestCase):
manager.execute()
# Check a new crontab to see what we find.
cron = crontab.CronTab(tabfile=self.tabfile)
self.assertCronLength(0, command=plugin_command)
self.assertCronLength(1, command='storyboard-cron')
self.assertCronLength(2, command='storyboard-cron')
# Cleanup after ourselves.
manager.remove()
@ -344,7 +343,7 @@ class TestCronManager(base.TestCase):
manager.execute()
# Check a new crontab to see what we find.
self.assertCronLength(1, command='storyboard-cron')
self.assertCronLength(2, command='storyboard-cron')
# Cleanup after ourselves.
manager.remove()

View File

@ -0,0 +1,85 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime
from datetime import timedelta
from oslo.config import cfg
import storyboard.db.api.base as db_api
from storyboard.db.models import AccessToken
from storyboard.plugin.oauth.cleaner import TokenCleaner
import storyboard.tests.base as base
from storyboard.tests.mock_data import load_data
CONF = cfg.CONF
class TestTokenCleaner(base.FunctionalTest):
"""Test cases for our OAuth Token cleaner plugin."""
def setUp(self):
super(TestTokenCleaner, self).setUp()
def tearDown(self):
super(TestTokenCleaner, self).tearDown()
def test_enabled(self):
"""This plugin must always be enabled. The only time it's not enabled
is when cron has been disabled.
"""
plugin = TokenCleaner(CONF)
self.assertTrue(plugin.enabled())
def test_interval(self):
"""Assert that the cron manager runs every 5 minutes."""
plugin = TokenCleaner(CONF)
self.assertEqual("? * * * *", plugin.interval())
def test_token_removal(self):
"""Assert that the plugin deletes tokens whose expiration date passed
over a week ago.
"""
# Start with a clean database.
db_api.model_query(AccessToken).delete()
self.assertEqual(0, db_api.model_query(AccessToken).count())
# Build 100 tokens, each one day older than the other, with 24 hour
# expiration dates. I subtract 5 seconds here because the time it
# takes to execute the script may, or may not, result in an
# 8-day-old-token to remain valid.
for i in range(0, 100):
created_at = datetime.utcnow() - timedelta(days=i)
expires_in = (60 * 60 * 24) - 5 # Minus five seconds, see above.
expires_at = created_at + timedelta(seconds=expires_in)
load_data([
AccessToken(
user_id=1,
created_at=created_at.strftime('%Y-%m-%d %H:%M:%S'),
expires_in=expires_in,
expires_at=expires_at.strftime('%Y-%m-%d %H:%M:%S'),
access_token='test_token_%s' % (i,))
])
# Make sure we have 100 tokens.
self.assertEqual(100, db_api.model_query(AccessToken).count())
# Run the plugin.
plugin = TokenCleaner(CONF)
plugin.execute()
# Make sure we have 8 tokens left (since one plugin starts today).
self.assertEqual(8, db_api.model_query(AccessToken).count())