Unreviewed, rolling out r251558.
[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 "SOAuthorizationLoadPolicy.h"
36 #import "WKUIDelegatePrivate.h"
37 #import "WebPageProxy.h"
38 #import "WebsiteDataStore.h"
39 #import <WebCore/ResourceResponse.h>
40 #import <WebCore/SecurityOrigin.h>
41 #import <pal/cocoa/AppSSOSoftLink.h>
42 #import <wtf/Vector.h>
43
44 namespace WebKit {
45
46 namespace {
47
48 static Vector<WebCore::Cookie> toCookieVector(NSArray<NSHTTPCookie *> *cookies)
49 {
50     Vector<WebCore::Cookie> result;
51     result.reserveInitialCapacity(cookies.count);
52     for (id cookie in cookies)
53         result.uncheckedAppend(cookie);
54     return result;
55 }
56
57 static bool isSameOrigin(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response)
58 {
59     auto requestOrigin = WebCore::SecurityOrigin::create(request.url());
60     return requestOrigin->isSameOriginAs(WebCore::SecurityOrigin::create(response.url()).get());
61 }
62
63 } // namespace
64
65 SOAuthorizationSession::SOAuthorizationSession(SOAuthorization *soAuthorization, Ref<API::NavigationAction>&& navigationAction, WebPageProxy& page, InitiatingAction action)
66     : m_soAuthorization(soAuthorization)
67     , m_navigationAction(WTFMove(navigationAction))
68     , m_page(makeWeakPtr(page))
69     , m_action(action)
70 {
71 }
72
73 SOAuthorizationSession::~SOAuthorizationSession()
74 {
75     if (m_state == State::Active && !!m_soAuthorization)
76         [m_soAuthorization cancelAuthorization];
77     if (m_state != State::Idle && m_state != State::Completed)
78         becomeCompleted();
79 }
80
81 Ref<API::NavigationAction> SOAuthorizationSession::releaseNavigationAction()
82 {
83     return m_navigationAction.releaseNonNull();
84 }
85
86 void SOAuthorizationSession::becomeCompleted()
87 {
88     ASSERT(m_state == State::Active || m_state == State::Waiting);
89     m_state = State::Completed;
90     if (m_viewController)
91         dismissViewController();
92 }
93
94 void SOAuthorizationSession::shouldStart()
95 {
96     ASSERT(m_state == State::Idle);
97     if (!m_page)
98         return;
99     shouldStartInternal();
100 }
101
102 void SOAuthorizationSession::start()
103 {
104     ASSERT((m_state == State::Idle || m_state == State::Waiting) && m_page && m_navigationAction);
105     m_state = State::Active;
106
107     m_page->decidePolicyForSOAuthorizationLoad(emptyString(), [this, weakThis = makeWeakPtr(*this)] (SOAuthorizationLoadPolicy policy) {
108         if (!weakThis)
109             return;
110
111         if (policy == SOAuthorizationLoadPolicy::Ignore) {
112             fallBackToWebPath();
113             return;
114         }
115
116         if (!m_soAuthorization || !m_page || !m_navigationAction)
117             return;
118
119         // FIXME: <rdar://problem/48909336> Replace the below with AppSSO constants.
120         auto initiatorOrigin = emptyString();
121         if (m_navigationAction->sourceFrame())
122             initiatorOrigin = m_navigationAction->sourceFrame()->securityOrigin().securityOrigin().toString();
123         if (m_action == InitiatingAction::SubFrame && m_page->mainFrame())
124             initiatorOrigin = WebCore::SecurityOrigin::create(m_page->mainFrame()->url())->toString();
125         NSDictionary *authorizationOptions = @{
126             SOAuthorizationOptionUserActionInitiated: @(m_navigationAction->isProcessingUserGesture()),
127             @"initiatorOrigin": (NSString *)initiatorOrigin,
128             @"initiatingAction": @(static_cast<NSInteger>(m_action))
129         };
130         [m_soAuthorization setAuthorizationOptions:authorizationOptions];
131
132 #if PLATFORM(IOS)
133         if (![fromWebPageProxy(*m_page).UIDelegate respondsToSelector:@selector(_presentingViewControllerForWebView:)])
134             [m_soAuthorization setEnableEmbeddedAuthorizationViewController:NO];
135 #endif
136
137         auto *nsRequest = m_navigationAction->request().nsURLRequest(WebCore::HTTPBodyUpdatePolicy::UpdateHTTPBody);
138         [m_soAuthorization beginAuthorizationWithURL:nsRequest.URL httpHeaders:nsRequest.allHTTPHeaderFields httpBody:nsRequest.HTTPBody];
139     });
140 }
141
142 void SOAuthorizationSession::fallBackToWebPath()
143 {
144     if (m_state != State::Active)
145         return;
146     becomeCompleted();
147     fallBackToWebPathInternal();
148 }
149
150 void SOAuthorizationSession::abort()
151 {
152     if (m_state == State::Idle || m_state == State::Completed)
153         return;
154     becomeCompleted();
155     abortInternal();
156 }
157
158 void SOAuthorizationSession::complete(NSHTTPURLResponse *httpResponse, NSData *data)
159 {
160     if (m_state != State::Active)
161         return;
162     ASSERT(m_navigationAction);
163     becomeCompleted();
164
165     auto response = WebCore::ResourceResponse(httpResponse);
166     if (!isSameOrigin(m_navigationAction->request(), response)) {
167         fallBackToWebPathInternal();
168         return;
169     }
170
171     // Set cookies.
172     auto cookies = toCookieVector([NSHTTPCookie cookiesWithResponseHeaderFields:httpResponse.allHeaderFields forURL:response.url()]);
173     if (cookies.isEmpty()) {
174         completeInternal(response, data);
175         return;
176     }
177
178     if (!m_page)
179         return;
180     m_page->websiteDataStore().cookieStore().setCookies(cookies, [weakThis = makeWeakPtr(*this), response = WTFMove(response), data = adoptNS([[NSData alloc] initWithData:data])] () mutable {
181         if (!weakThis)
182             return;
183         weakThis->completeInternal(response, data.get());
184     });
185 }
186
187 void SOAuthorizationSession::presentViewController(SOAuthorizationViewController viewController, UICallback uiCallback)
188 {
189     ASSERT(m_state == State::Active);
190     // Only expect at most one UI session for the whole authorization session.
191     if (!m_page || m_page->isClosed() || m_viewController) {
192         uiCallback(NO, adoptNS([[NSError alloc] initWithDomain:SOErrorDomain code:kSOErrorAuthorizationPresentationFailed userInfo:nil]).get());
193         return;
194     }
195
196     m_viewController = viewController;
197 #if PLATFORM(MAC)
198     ASSERT(!m_sheetWindow);
199     m_sheetWindow = [NSWindow windowWithContentViewController:m_viewController.get()];
200
201     ASSERT(!m_sheetWindowWillCloseObserver);
202     m_sheetWindowWillCloseObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:m_sheetWindow.get() queue:nil usingBlock:[weakThis = makeWeakPtr(*this)] (NSNotification *) {
203         if (!weakThis)
204             return;
205         weakThis->dismissViewController();
206     }];
207
208     NSWindow *presentingWindow = m_page->platformWindow();
209     if (!presentingWindow) {
210         uiCallback(NO, adoptNS([[NSError alloc] initWithDomain:SOErrorDomain code:kSOErrorAuthorizationPresentationFailed userInfo:nil]).get());
211         return;
212     }
213     [presentingWindow beginSheet:m_sheetWindow.get() completionHandler:nil];
214 #elif PLATFORM(IOS)
215     UIViewController *presentingViewController = m_page->uiClient().presentingViewController();
216     if (!presentingViewController) {
217         uiCallback(NO, adoptNS([[NSError alloc] initWithDomain:SOErrorDomain code:kSOErrorAuthorizationPresentationFailed userInfo:nil]).get());
218         return;
219     }
220
221     [presentingViewController presentViewController:m_viewController.get() animated:YES completion:nil];
222 #endif
223
224     uiCallback(YES, nil);
225 }
226
227 void SOAuthorizationSession::dismissViewController()
228 {
229     ASSERT(m_viewController);
230 #if PLATFORM(MAC)
231     ASSERT(m_sheetWindow && m_sheetWindowWillCloseObserver);
232
233     [[NSNotificationCenter defaultCenter] removeObserver:m_sheetWindowWillCloseObserver.get()];
234     m_sheetWindowWillCloseObserver = nullptr;
235
236     [[m_sheetWindow sheetParent] endSheet:m_sheetWindow.get()];
237     m_sheetWindow = nullptr;
238 #elif PLATFORM(IOS)
239     [[m_viewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
240 #endif
241
242     m_viewController = nullptr;
243 }
244
245 } // namespace WebKit
246
247 #endif