Revert "Redirect Browsers from API to Client"
This reverts commit 447ae504978c5a1ec94ba13ea5a62fb4eebc1b6a. Change-Id: I972ff1ebd616ae3eb9eb375082393ff5ce1e942b
This commit is contained in:
parent
5834af13d7
commit
b0e82d23cd
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
@ -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'])
|
Loading…
x
Reference in New Issue
Block a user