Pass a hint from the extension to decidePolicyForSOAuthorizationLoadWithCurrentPolicy
[WebKit-https.git] / Source / WebKit / UIProcess / Cocoa / SOAuthorization / SOAuthorizationSession.mm
1 /*
2  * Copyright (C) 2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "SOAuthorizationSession.h"
28
29 #if HAVE(APP_SSO)
30
31 #import "APIHTTPCookieStore.h"
32 #import "APINavigation.h"
33 #import "APINavigationAction.h"
34 #import "APIUIClient.h"
35 #import "Logging.h"
36 #import "SOAuthorizationLoadPolicy.h"
37 #import "WKUIDelegatePrivate.h"
38 #import "WKWebViewInternal.h"
39 #import "WebPageProxy.h"
40 #import "WebsiteDataStore.h"
41 #import <WebCore/ResourceResponse.h>
42 #import <WebCore/SecurityOrigin.h>
43 #import <pal/cocoa/AppSSOSoftLink.h>
44 #import <wtf/BlockPtr.h>
45 #import <wtf/Vector.h>
46
47 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(m_page && m_page->isAlwaysOnLoggingAllowed(), AppSSO, "%p - [InitiatingAction=%s] SOAuthorizationSession::" fmt, this, toString(m_action), ##__VA_ARGS__)
48
49 namespace WebKit {
50
51 namespace {
52
53 static const char* Redirect = "Redirect";
54 static const char* PopUp = "PopUp";
55 static const char* SubFrame = "SubFrame";
56
57 static const char* toString(const SOAuthorizationSession::InitiatingAction& action)
58 {
59     switch (action) {
60     case SOAuthorizationSession::InitiatingAction::Redirect:
61         return Redirect;
62     case SOAuthorizationSession::InitiatingAction::PopUp:
63         return PopUp;
64     case SOAuthorizationSession::InitiatingAction::SubFrame:
65         return SubFrame;
66     }
67
68     ASSERT_NOT_REACHED();
69     return nullptr;
70 }
71
72 static Vector<WebCore::Cookie> toCookieVector(NSArray<NSHTTPCookie *> *cookies)
73 {
74     Vector<WebCore::Cookie> result;
75     result.reserveInitialCapacity(cookies.count);
76     for (id cookie in cookies)
77         result.uncheckedAppend(cookie);
78     return result;
79 }
80
81 static bool isSameOrigin(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response)
82 {
83     auto requestOrigin = WebCore::SecurityOrigin::create(request.url());
84     return requestOrigin->isSameOriginAs(WebCore::SecurityOrigin::create(response.url()).get());
85 }
86
87 } // namespace
88
89 SOAuthorizationSession::SOAuthorizationSession(SOAuthorization *soAuthorization, Ref<API::NavigationAction>&& navigationAction, WebPageProxy& page, InitiatingAction action)
90     : m_soAuthorization(soAuthorization)
91     , m_navigationAction(WTFMove(navigationAction))
92     , m_page(makeWeakPtr(page))
93     , m_action(action)
94 {
95 }
96
97 SOAuthorizationSession::~SOAuthorizationSession()
98 {
99     if (m_state == State::Active && !!m_soAuthorization)
100         [m_soAuthorization cancelAuthorization];
101     if (m_state != State::Idle && m_state != State::Completed)
102         becomeCompleted();
103 }
104
105 Ref<API::NavigationAction> SOAuthorizationSession::releaseNavigationAction()
106 {
107     return m_navigationAction.releaseNonNull();
108 }
109
110 void SOAuthorizationSession::becomeCompleted()
111 {
112     ASSERT(m_state == State::Active || m_state == State::Waiting);
113     m_state = State::Completed;
114     if (m_viewController)
115         dismissViewController();
116 }
117
118 void SOAuthorizationSession::shouldStart()
119 {
120     RELEASE_LOG_IF_ALLOWED("shouldStart:");
121
122     ASSERT(m_state == State::Idle);
123     if (!m_page)
124         return;
125     shouldStartInternal();
126 }
127
128 void SOAuthorizationSession::start()
129 {
130     RELEASE_LOG_IF_ALLOWED("start:");
131
132     ASSERT((m_state == State::Idle || m_state == State::Waiting) && m_navigationAction);
133     m_state = State::Active;
134     [m_soAuthorization getAuthorizationHintsWithURL:m_navigationAction->request().url() responseCode:0 completion:makeBlockPtr([this, weakThis = makeWeakPtr(*this)] (SOAuthorizationHints *authorizationHints, NSError *error) {
135         RELEASE_LOG_IF_ALLOWED("start: Receive SOAuthorizationHints (error=%ld)", error ? error.code : 0);
136
137         if (!weakThis || error || !authorizationHints)
138             return;
139         continueStartAfterGetAuthorizationHints(authorizationHints.localizedExtensionBundleDisplayName);
140     }).get()];
141 }
142
143 void SOAuthorizationSession::continueStartAfterGetAuthorizationHints(const String& hints)
144 {
145     RELEASE_LOG_IF_ALLOWED("continueStartAfterGetAuthorizationHints: (hints=%s)", hints.utf8().data());
146
147     ASSERT(m_state == State::Active);
148     if (!m_page)
149         return;
150
151     m_page->decidePolicyForSOAuthorizationLoad(hints, [this, weakThis = makeWeakPtr(*this)] (SOAuthorizationLoadPolicy policy) {
152         if (!weakThis)
153             return;
154         continueStartAfterDecidePolicy(policy);
155     });
156 }
157
158 void SOAuthorizationSession::continueStartAfterDecidePolicy(const SOAuthorizationLoadPolicy& policy)
159 {
160     if (policy == SOAuthorizationLoadPolicy::Ignore) {
161         RELEASE_LOG_IF_ALLOWED("continueStartAfterDecidePolicy: Receive SOAuthorizationLoadPolicy::Ignore");
162
163         fallBackToWebPath();
164         return;
165     }
166
167     RELEASE_LOG_IF_ALLOWED("continueStartAfterDecidePolicy: Receive SOAuthorizationLoadPolicy::Allow");
168
169     if (!m_soAuthorization || !m_page || !m_navigationAction)
170         return;
171
172     // FIXME: <rdar://problem/48909336> Replace the below with AppSSO constants.
173     auto initiatorOrigin = emptyString();
174     if (m_navigationAction->sourceFrame())
175         initiatorOrigin = m_navigationAction->sourceFrame()->securityOrigin().securityOrigin().toString();
176     if (m_action == InitiatingAction::SubFrame && m_page->mainFrame())
177         initiatorOrigin = WebCore::SecurityOrigin::create(m_page->mainFrame()->url())->toString();
178     NSDictionary *authorizationOptions = @{
179         SOAuthorizationOptionUserActionInitiated: @(m_navigationAction->isProcessingUserGesture()),
180         @"initiatorOrigin": (NSString *)initiatorOrigin,
181         @"initiatingAction": @(static_cast<NSInteger>(m_action))
182     };
183     [m_soAuthorization setAuthorizationOptions:authorizationOptions];
184
185 #if PLATFORM(IOS)
186     if (![fromWebPageProxy(*m_page).UIDelegate respondsToSelector:@selector(_presentingViewControllerForWebView:)])
187         [m_soAuthorization setEnableEmbeddedAuthorizationViewController:NO];
188 #endif
189
190     auto *nsRequest = m_navigationAction->request().nsURLRequest(WebCore::HTTPBodyUpdatePolicy::UpdateHTTPBody);
191     [m_soAuthorization beginAuthorizationWithURL:nsRequest.URL httpHeaders:nsRequest.allHTTPHeaderFields httpBody:nsRequest.HTTPBody];
192 }
193
194 void SOAuthorizationSession::fallBackToWebPath()
195 {
196     RELEASE_LOG_IF_ALLOWED("fallBackToWebPath:");
197
198     if (m_state != State::Active)
199         return;
200     becomeCompleted();
201     fallBackToWebPathInternal();
202 }
203
204 void SOAuthorizationSession::abort()
205 {
206     RELEASE_LOG_IF_ALLOWED("abort:");
207
208     if (m_state == State::Idle || m_state == State::Completed)
209         return;
210     becomeCompleted();
211     abortInternal();
212 }
213
214 void SOAuthorizationSession::complete(NSHTTPURLResponse *httpResponse, NSData *data)
215 {
216     if (m_state != State::Active)
217         return;
218     ASSERT(m_navigationAction);
219     becomeCompleted();
220
221     auto response = WebCore::ResourceResponse(httpResponse);
222     if (!isSameOrigin(m_navigationAction->request(), response)) {
223         fallBackToWebPathInternal();
224         return;
225     }
226
227     // Set cookies.
228     auto cookies = toCookieVector([NSHTTPCookie cookiesWithResponseHeaderFields:httpResponse.allHeaderFields forURL:response.url()]);
229
230     RELEASE_LOG_IF_ALLOWED("complete: (httpStatusCode=%d, hasCookies=%d, hasData=%d)", response.httpStatusCode(), !cookies.isEmpty(), !!data.length);
231
232     if (cookies.isEmpty()) {
233         completeInternal(response, data);
234         return;
235     }
236
237     if (!m_page)
238         return;
239     m_page->websiteDataStore().cookieStore().setCookies(cookies, [this, weakThis = makeWeakPtr(*this), response = WTFMove(response), data = adoptNS([[NSData alloc] initWithData:data])] () mutable {
240         if (!weakThis)
241             return;
242
243         RELEASE_LOG_IF_ALLOWED("complete: Cookies are set.");
244
245         completeInternal(response, data.get());
246     });
247 }
248
249 void SOAuthorizationSession::presentViewController(SOAuthorizationViewController viewController, UICallback uiCallback)
250 {
251     RELEASE_LOG_IF_ALLOWED("presentViewController:");
252
253     ASSERT(m_state == State::Active);
254     // Only expect at most one UI session for the whole authorization session.
255     if (!m_page || m_page->isClosed() || m_viewController) {
256         uiCallback(NO, adoptNS([[NSError alloc] initWithDomain:SOErrorDomain code:kSOErrorAuthorizationPresentationFailed userInfo:nil]).get());
257         return;
258     }
259
260     m_viewController = viewController;
261 #if PLATFORM(MAC)
262     ASSERT(!m_sheetWindow);
263     m_sheetWindow = [NSWindow windowWithContentViewController:m_viewController.get()];
264
265     ASSERT(!m_sheetWindowWillCloseObserver);
266     m_sheetWindowWillCloseObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:m_sheetWindow.get() queue:nil usingBlock:[weakThis = makeWeakPtr(*this)] (NSNotification *) {
267         if (!weakThis)
268             return;
269         weakThis->dismissViewController();
270     }];
271
272     NSWindow *presentingWindow = m_page->platformWindow();
273     if (!presentingWindow) {
274         uiCallback(NO, adoptNS([[NSError alloc] initWithDomain:SOErrorDomain code:kSOErrorAuthorizationPresentationFailed userInfo:nil]).get());
275         return;
276     }
277     [presentingWindow beginSheet:m_sheetWindow.get() completionHandler:nil];
278 #elif PLATFORM(IOS)
279     UIViewController *presentingViewController = m_page->uiClient().presentingViewController();
280     if (!presentingViewController) {
281         uiCallback(NO, adoptNS([[NSError alloc] initWithDomain:SOErrorDomain code:kSOErrorAuthorizationPresentationFailed userInfo:nil]).get());
282         return;
283     }
284
285     [presentingViewController presentViewController:m_viewController.get() animated:YES completion:nil];
286 #endif
287
288     uiCallback(YES, nil);
289 }
290
291 void SOAuthorizationSession::dismissViewController()
292 {
293     RELEASE_LOG_IF_ALLOWED("dismissViewController:");
294
295     ASSERT(m_viewController);
296 #if PLATFORM(MAC)
297     ASSERT(m_sheetWindow && m_sheetWindowWillCloseObserver);
298
299     [[NSNotificationCenter defaultCenter] removeObserver:m_sheetWindowWillCloseObserver.get()];
300     m_sheetWindowWillCloseObserver = nullptr;
301
302     [[m_sheetWindow sheetParent] endSheet:m_sheetWindow.get()];
303     m_sheetWindow = nullptr;
304 #elif PLATFORM(IOS)
305     [[m_viewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
306 #endif
307
308     m_viewController = nullptr;
309 }
310
311 } // namespace WebKit
312
313 #undef RELEASE_LOG_IF_ALLOWED
314
315 #endif // HAVE(APP_SSO)