Revert "Redirect Browsers from API to Client"

This reverts commit 447ae50497.

Change-Id: I972ff1ebd616ae3eb9eb375082393ff5ce1e942b
This commit is contained in:
Monty Taylor 2015-05-11 13:17:19 +00:00
parent 5834af13d7
commit b0e82d23cd
4 changed files with 4 additions and 207 deletions

View File

@ -37,12 +37,6 @@ lock_path = $state_path/lock
# Port the bind the API server to
# bind_port = 8080
# The default web client. This is the URL to which a client, presenting an
# Accepts: text/html header, will be redirected to when browsing the API. It
# is also used for email URL resolution, so we highly recommend that you set
# this to the host and protocol of your own storyboard server.
# default_client_url = https://storyboard.openstack.org/#!
# Enable notifications. This feature drives deferred processing, reporting,
# and subscriptions.
# enable_notifications = True

View File

@ -4,7 +4,7 @@
# 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
# 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,
@ -23,8 +23,6 @@ from wsgiref import simple_server
from storyboard.api import config as api_config
from storyboard.api.middleware.cors_middleware import CORSMiddleware
from storyboard.api.middleware import redirect_middleware
from storyboard.api.middleware import session_hook
from storyboard.api.middleware import token_middleware
from storyboard.api.middleware import user_id_hook
@ -53,11 +51,8 @@ API_OPTS = [
default=8080,
help='API port'),
cfg.BoolOpt('enable_notifications',
default=False,
help='Enable Notifications'),
cfg.StrOpt('default_client_url',
default='https://storyboard.openstack.org/#!',
help='The URL of the default web client.')
default=False,
help='Enable Notifications')
]
CORS_OPTS = [
cfg.ListOpt('allowed_origins',
@ -120,8 +115,6 @@ def setup_app(pecan_config=None):
)
app = token_middleware.AuthTokenMiddleware(app)
app = redirect_middleware. \
BrowserRedirectMiddleware(app, client_root_url=CONF.default_client_url)
# Setup CORS
if CONF.cors.allowed_origins:
@ -157,7 +150,7 @@ def start():
% ({'port': port}))
else:
LOG.info(_LI("serving on http://%(host)s:%(port)s") % (
{'host': host, 'port': port}))
{'host': host, 'port': port}))
srv.serve_forever()

View File

@ -1,70 +0,0 @@
# Copyright (c) 2015 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.
import re
from webob.acceptparse import Accept
class BrowserRedirectMiddleware(object):
# A list of HTML Headers that may come from browsers.
html_headers = [
'text/html',
'application/xhtml+xml'
]
def __init__(self, app, client_root_url='/'):
"""Build an HTTP redirector, with the initial assumption that the
client is installed on the same host as this wsgi app.
:param app The WSGI app to wrap.
:param client_root_url The root URL of the redirect target's path.
"""
self.app = app
self.client_root_url = client_root_url
def __call__(self, env, start_response):
# We only care about GET methods.
if env['REQUEST_METHOD'] == 'GET' and 'HTTP_ACCEPT' in env:
# Iterate over the headers.
for type, quality in Accept.parse(env['HTTP_ACCEPT']):
# Only accept quality 1 headers, anything less
# implies that the client prefers something else.
if quality == 1 and type in self.html_headers:
# Build the redirect URL and redirect if successful
redirect_to = self._build_redirect_url(env['PATH_INFO'])
if redirect_to:
start_response("303 See Other",
[('Location', redirect_to)])
return []
# Otherwise, break out of the whole loop and let the
# default handler deal with it.
break
return self.app(env, start_response)
def _build_redirect_url(self, path):
# To map to the client, we are assuming that the API adheres to a URL
# pattern of "/superfluous_prefix/v1/other_things. We strip out
# anything up to and including /v1, and use the rest as our redirect
# fragment. Note that this middleware makes no assumption about #!
# navigation, as it is feasible that true HTML5 history support is
# available on the client.
match = re.search('\/v1(\/.*$)', path)
if match:
return self.client_root_url + match.group(1)
else:
return None

View File

@ -1,120 +0,0 @@
# Copyright (c) 2015 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.
import six
from oslo_config import cfg
from storyboard.tests import base
CONF = cfg.CONF
class TestRedirectMiddleware(base.FunctionalTest):
# Map of API -> Client urls that we're expecting.
uri_mappings = {
'/v1/projects': 'https://storyboard.openstack.org/#!/projects',
'/v1/stories/22': 'https://storyboard.openstack.org/#!/stories/22',
'/v1/project_groups/2': 'https://storyboard.openstack.org/'
'#!/project_groups/2'
}
def test_valid_results(self):
"""Assert that the expected URI mappings are returned."""
headers = {
'Accept': 'text/html;q=1'
}
for request_uri, redirect_uri in six.iteritems(self.uri_mappings):
response = self.app.get(request_uri,
headers=headers,
expect_errors=True)
self.assertEqual(303, response.status_code)
self.assertEqual(redirect_uri, response.headers['Location'])
def test_valid_results_as_post_put_delete(self):
"""Assert that POST, PUT, and DELETE methods are passed through to
the API.
"""
headers = {
'Accept': 'text/html;q=1'
}
for request_uri, redirect_uri in six.iteritems(self.uri_mappings):
response = self.app.post(request_uri, headers=headers,
expect_errors=True)
self.assertNotEqual(303, response.status_code)
self.assertNotIn('Location', response.headers)
response = self.app.put(request_uri, headers=headers,
expect_errors=True)
self.assertNotEqual(303, response.status_code)
self.assertNotIn('Location', response.headers)
response = self.app.delete(request_uri, headers=headers,
expect_errors=True)
self.assertNotEqual(303, response.status_code)
self.assertNotIn('Location', response.headers)
def test_graceful_accepts_header(self):
"""If the client prefers some other content type, make sure we
respect that.
"""
headers = {
'Accept': 'text/html;q=.9,application/json;q=1'
}
for request_uri, redirect_uri in six.iteritems(self.uri_mappings):
response = self.app.get(request_uri,
headers=headers,
expect_errors=True)
self.assertNotEqual(303, response.status_code)
self.assertNotIn('Location', response.headers)
def test_with_browser_useragent(self):
"""Future protection test. Make sure that no other code accidentally
gets in the way of browsers being redirected (such as search engine
bot response handling). This is intended to be a canary for
unexpected changes, rather than a comprehensive test for all possible
browsers.
"""
user_agents = [
# Chrome 41
'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML,'
' like Gecko) Chrome/41.0.2228.0 Safari/537.36',
# Firefox 36
'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101'
' Firefox/36.0',
# IE10
'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0)'
' like Gecko'
]
for request_uri, redirect_uri in six.iteritems(self.uri_mappings):
for user_agent in user_agents:
headers = {
'User-Agent': user_agent,
'Accept': 'text/html;q=1'
}
response = self.app.get(request_uri,
headers=headers,
expect_errors=True)
self.assertEqual(303, response.status_code)
self.assertEqual(redirect_uri, response.headers['Location'])