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