Merge lp:~abreu-alexandre/webbrowser-app/saml-url-persistence into lp:webbrowser-app

Proposed by David Barth on 2015-05-27
Status: Merged
Approved by: Alexandre Abreu on 2015-06-11
Approved revision: 1035
Merged at revision: 1054
Proposed branch: lp:~abreu-alexandre/webbrowser-app/saml-url-persistence
Merge into: lp:webbrowser-app
Diff against target: 289 lines (+181/-3)
5 files modified
src/app/webcontainer/WebApp.qml (+56/-1)
src/app/webcontainer/WebViewImplOxide.qml (+13/-2)
src/app/webcontainer/WebappContainerWebview.qml (+9/-0)
tests/autopilot/webapp_container/tests/fake_servers.py (+39/-0)
tests/autopilot/webapp_container/tests/test_saml_url_patterns.py (+64/-0)
To merge this branch: bzr merge lp:~abreu-alexandre/webbrowser-app/saml-url-persistence
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing on 2015-06-10
Alberto Mardegan (community) 2015-05-27 Approve on 2015-06-10
Review via email: mp+260248@code.launchpad.net

Commit Message

Ensure SAML requests are followed even if the app is interrupted and restarted.

Description of the Change

Ensure SAML requests are followed even if the app is interrupted and restarted.

To post a comment you must log in.
1034. By Alexandre Abreu on 2015-05-27

Persist saml request url patterns

Alberto Mardegan (mardy) wrote :

A few minor comments, mostly personal preferences.

review: Needs Fixing
1035. By Alexandre Abreu on 2015-06-03

Updates

Alexandre Abreu (abreu-alexandre) wrote :

updated

Alberto Mardegan (mardy) wrote :

LGTM, thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/app/webcontainer/WebApp.qml'
2--- src/app/webcontainer/WebApp.qml 2015-05-20 14:14:18 +0000
3+++ src/app/webcontainer/WebApp.qml 2015-06-03 14:50:36 +0000
4@@ -21,6 +21,7 @@
5 import Ubuntu.Components 1.1
6 import Ubuntu.Unity.Action 1.1 as UnityActions
7 import Ubuntu.UnityWebApps 0.1 as UnityWebApps
8+import Qt.labs.settings 1.0
9 import "../actions" as Actions
10 import ".."
11
12@@ -35,7 +36,7 @@
13 property string webappModelSearchPath: ""
14
15 property alias webappName: webview.webappName
16- property alias webappUrlPatterns: webview.webappUrlPatterns
17+ property var webappUrlPatterns
18 property alias popupRedirectionUrlPrefixPattern: webview.popupRedirectionUrlPrefixPattern
19 property alias webviewOverrideFile: webview.webviewOverrideFile
20 property alias blockOpenExternalUrls: webview.blockOpenExternalUrls
21@@ -47,6 +48,11 @@
22 property bool chromeVisible: false
23 readonly property bool chromeless: !chromeVisible && !backForwardButtonsVisible
24
25+ // Used for testing. There is a bug that currently prevents non visual Qt objects
26+ // to be introspectable from AP which makes directly accessing the settings object
27+ // not possible https://bugs.launchpad.net/autopilot-qt/+bug/1273956
28+ property alias generatedUrlPatterns: urlPatternSettings.generatedUrlPatterns
29+
30 actions: [
31 Actions.Back {
32 enabled: webapp.backForwardButtonsVisible && webview.currentWebview && webview.currentWebview.canGoBack
33@@ -61,6 +67,50 @@
34 }
35 ]
36
37+ Settings {
38+ id: urlPatternSettings
39+ property string generatedUrlPatterns
40+ }
41+
42+ function addGeneratedUrlPattern(urlPattern) {
43+ var patterns
44+ try {
45+ patterns = JSON.parse(urlPatternSettings.generatedUrlPatterns)
46+ } catch(e) {
47+ console.error("Invalid JSON content found in url patterns file")
48+ }
49+ if (! (patterns instanceof Array)) {
50+ console.error("Invalid JSON content type found in url patterns file (not an array)")
51+ patterns = []
52+ }
53+ if (patterns.indexOf(urlPattern) < 0) {
54+ patterns.push(urlPattern)
55+
56+ urlPatternSettings.generatedUrlPatterns = JSON.stringify(patterns)
57+ }
58+ }
59+
60+ function mergeUrlPatternSets(p1, p2) {
61+ if ( ! (p1 instanceof Array)) {
62+ return (p2 instanceof Array) ? p2 : []
63+ }
64+ if ( ! (p2 instanceof Array)) {
65+ return (p1 instanceof Array) ? p1 : []
66+ }
67+ var p1hash = {}
68+ var result = []
69+ for (var i1 in p1) {
70+ p1hash[p1[i1]] = 1
71+ result.push(p1[i1])
72+ }
73+ for (var i2 in p2) {
74+ if (! (p2[i2] in p1hash)) {
75+ result.push(p2[i2])
76+ }
77+ }
78+ return result
79+ }
80+
81 Item {
82 id: webviewContainer
83 anchors.fill: parent
84@@ -76,6 +126,11 @@
85 }
86 height: parent.height - osk.height
87 developerExtrasEnabled: webapp.developerExtrasEnabled
88+ onSamlRequestUrlPatternReceived: {
89+ addGeneratedUrlPattern(urlPattern)
90+ }
91+ webappUrlPatterns: mergeUrlPatternSets(urlPatternSettings.generatedUrlPatterns,
92+ webapp.webappUrlPatterns)
93 }
94
95 Loader {
96
97=== modified file 'src/app/webcontainer/WebViewImplOxide.qml'
98--- src/app/webcontainer/WebViewImplOxide.qml 2015-04-29 21:32:06 +0000
99+++ src/app/webcontainer/WebViewImplOxide.qml 2015-06-03 14:50:36 +0000
100@@ -44,6 +44,8 @@
101 // (if any) or navigations resulting in new windows being created.
102 property bool blockOpenExternalUrls: false
103
104+ signal samlRequestUrlPatternReceived(string urlPattern)
105+
106 // Those signals are used for testing purposes to externally
107 // track down the various internal logic & steps of a popup lifecycle.
108 signal openExternalUrlTriggered(string url)
109@@ -78,6 +80,12 @@
110 StateSaver.properties: "url"
111 StateSaver.enabled: !runningLocalApplication
112
113+ function handleSAMLRequestPattern(urlPattern) {
114+ webappUrlPatterns.push(urlPattern)
115+
116+ samlRequestUrlPatternReceived(urlPattern)
117+ }
118+
119 function shouldOpenPopupsInDefaultBrowser() {
120 return formFactor !== "desktop";
121 }
122@@ -163,9 +171,12 @@
123 var match = urlRegExp.exec(url)
124 var host = match[1]
125 var escapeDotsRegExp = new RegExp("\\.", "g")
126- var hostPattern = "https?://" + host.replace(escapeDotsRegExp, "\\.") + "/"
127+ var hostPattern = "https?://" + host.replace(escapeDotsRegExp, "\\.") + "/*"
128+
129 console.log("SAML request detected. Adding host pattern: " + hostPattern)
130- webappUrlPatterns.push(hostPattern)
131+
132+ handleSAMLRequestPattern(hostPattern)
133+
134 request.action = Oxide.NavigationRequest.ActionAccept
135 }
136
137
138=== modified file 'src/app/webcontainer/WebappContainerWebview.qml'
139--- src/app/webcontainer/WebappContainerWebview.qml 2015-04-21 14:01:13 +0000
140+++ src/app/webcontainer/WebappContainerWebview.qml 2015-06-03 14:50:36 +0000
141@@ -40,6 +40,8 @@
142 property bool blockOpenExternalUrls: false
143 property bool runningLocalApplication: false
144
145+ signal samlRequestUrlPatternReceived(string urlPattern)
146+
147 PopupWindowController {
148 id: popupController
149 objectName: "popupController"
150@@ -48,6 +50,13 @@
151 blockOpenExternalUrls: containerWebview.blockOpenExternalUrls
152 }
153
154+ Connections {
155+ target: webappContainerWebViewLoader.item
156+ onSamlRequestUrlPatternReceived: {
157+ samlRequestUrlPatternReceived(urlPattern)
158+ }
159+ }
160+
161 Loader {
162 id: webappContainerWebViewLoader
163 objectName: "containerWebviewLoader"
164
165=== modified file 'tests/autopilot/webapp_container/tests/fake_servers.py'
166--- tests/autopilot/webapp_container/tests/fake_servers.py 2015-03-23 19:58:08 +0000
167+++ tests/autopilot/webapp_container/tests/fake_servers.py 2015-06-03 14:50:36 +0000
168@@ -99,6 +99,24 @@
169 </html>
170 """.format("'"+self.headers['user-agent']+"'")
171
172+ def saml(self, loopcount):
173+ return """
174+ <html>
175+ <head>
176+ <title>open-close</title>
177+ <script>
178+ </script>
179+ </head>
180+ <body>
181+ <a href="/redirect-to-saml/?loopcount={}&SAMLRequest=1">
182+ <div style="height: 100%; width: 100%; background-color: red">
183+ target blank link
184+ </div>
185+ </a>
186+ </body>
187+ </html>
188+ """.format(loopcount)
189+
190 def open_close_content(self):
191 return """
192 <html>
193@@ -142,6 +160,27 @@
194 elif self.path == '/open-close-content':
195 self.send_response(200)
196 self.serve_content(self.open_close_content())
197+ elif self.path.startswith('/saml/'):
198+ args = self.path[len('/saml/'):]
199+ loopCount = 0
200+ if args.startswith('?loopcount='):
201+ loopCount = int(args[len('?loopcount='):].split(';')[0])
202+ self.send_response(200)
203+ self.serve_content(self.saml(loopCount))
204+ elif self.path.startswith('/redirect-to-saml/'):
205+ locationTarget = '/'
206+ args = self.path[len('/redirect-to-saml/'):]
207+ if args.startswith('?loopcount='):
208+ header_size = len('?loopcount=')
209+ loopCount = int(
210+ args[header_size:args.index('&')].split(';')[0])
211+ if loopCount > 0:
212+ loopCount = loopCount - 1
213+ locationTarget += 'redirect-to-saml\
214+/?loopcount=' + str(loopCount) + '&SAMLRequest=1'
215+ self.send_response(302)
216+ self.send_header("Location", locationTarget)
217+ self.end_headers()
218 else:
219 self.send_error(404)
220
221
222=== added file 'tests/autopilot/webapp_container/tests/test_saml_url_patterns.py'
223--- tests/autopilot/webapp_container/tests/test_saml_url_patterns.py 1970-01-01 00:00:00 +0000
224+++ tests/autopilot/webapp_container/tests/test_saml_url_patterns.py 2015-06-03 14:50:36 +0000
225@@ -0,0 +1,64 @@
226+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
227+# Copyright 2014 Canonical
228+#
229+# This program is free software: you can redistribute it and/or modify it
230+# under the terms of the GNU General Public License version 3, as published
231+# by the Free Software Foundation.
232+#
233+# This program is distributed in the hope that it will be useful,
234+# but WITHOUT ANY WARRANTY; without even the implied warranty of
235+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
236+# GNU General Public License for more details.
237+#
238+# You should have received a copy of the GNU General Public License
239+# along with this program. If not, see <http://www.gnu.org/licenses/>.
240+
241+from testtools.matchers import Equals, Contains
242+from autopilot.matchers import Eventually
243+
244+from webapp_container.tests import WebappContainerTestCaseWithLocalContentBase
245+
246+
247+class WebappContainerSAMLUrlPatternsTestCase(
248+ WebappContainerTestCaseWithLocalContentBase):
249+ def test_saml_urls_added(self):
250+ rule = 'MAP *.test.com:80 ' + self.get_base_url_hostname()
251+ args = ["--webappUrlPatterns=\
252+http://www.test.com/saml/*,{}/saml/*".format(self.base_url)]
253+
254+ samlRequestRedirectsCount = 1
255+ target_path = '/saml/?\
256+loopcount={}'.format(str(samlRequestRedirectsCount))
257+
258+ self.launch_webcontainer_app_with_local_http_server(
259+ args,
260+ target_path,
261+ {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1',
262+ 'UBUNTU_WEBVIEW_HOST_MAPPING_RULES': rule})
263+ self.get_webcontainer_window().visible.wait_for(True)
264+ self.assert_page_eventually_loaded(self.base_url+target_path)
265+
266+ webcontainer_webview = self.get_webcontainer_webview()
267+ url_patterns_settings_watcher = webcontainer_webview.watch_signal(
268+ 'generatedUrlPatternsChanged()')
269+
270+ webview = self.get_oxide_webview()
271+
272+ gr = webview.globalRect
273+ self.pointing_device.move(
274+ gr.x + webview.width*0.5,
275+ gr.y + webview.height*0.5)
276+ self.pointing_device.click()
277+
278+ self.assertThat(
279+ lambda: url_patterns_settings_watcher.was_emitted,
280+ Eventually(Equals(True)))
281+ self.assertThat(
282+ lambda: url_patterns_settings_watcher.num_emissions,
283+ Eventually(Equals(samlRequestRedirectsCount)))
284+
285+ saved_patterns = webcontainer_webview.generatedUrlPatterns
286+
287+ self.assertThat(
288+ saved_patterns,
289+ Contains("\"https?://{}/*\"".format(self.get_base_url_hostname())))

Subscribers

People subscribed via source and target branches

to status/vote changes: