PopUpSOAuthorizationSession.mm   [plain text]


/*
 * Copyright (C) 2019 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "config.h"
#import "PopUpSOAuthorizationSession.h"

#if HAVE(APP_SSO)

#import "APINavigationAction.h"
#import "WKNavigationDelegatePrivate.h"
#import "WKUIDelegate.h"
#import "WKWebViewConfigurationPrivate.h"
#import "WKWebViewInternal.h"
#import "WebPageProxy.h"
#import <WebCore/ResourceResponse.h>
#import <wtf/BlockPtr.h>

@interface WKSOSecretDelegate : NSObject <WKNavigationDelegate, WKUIDelegate> {
@private
    WeakPtr<WebKit::PopUpSOAuthorizationSession> _session;
    BOOL _isFirstNavigation;
}

- (instancetype)initWithSession:(WebKit::PopUpSOAuthorizationSession *)session;

@end

@implementation WKSOSecretDelegate

- (instancetype)initWithSession:(WebKit::PopUpSOAuthorizationSession *)session
{
    if ((self = [super init])) {
        _session = makeWeakPtr(session);
        _isFirstNavigation = YES;
    }
    return self;
}

// WKUIDelegate
- (void)webViewDidClose:(WKWebView *)webView
{
    if (!_session)
        return;
    _session->close(webView);
}

// WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    // FIXME<rdar://problem/48787839>: We should restrict the load to only substitute data.
    // Use the following heuristic as a workaround right now.
    // Ignore the first load in the secret window, which navigates to the authentication URL.
    if (_isFirstNavigation) {
        _isFirstNavigation = NO;
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    decisionHandler(_WKNavigationActionPolicyAllowWithoutTryingAppLink);
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    if (!_session)
        return;
    _session->close(webView);
}

@end

namespace WebKit {

Ref<SOAuthorizationSession> PopUpSOAuthorizationSession::create(SOAuthorization *soAuthorization, WebPageProxy& page, Ref<API::NavigationAction>&& navigationAction, NewPageCallback&& newPageCallback, UIClientCallback&& uiClientCallback)
{
    return adoptRef(*new PopUpSOAuthorizationSession(soAuthorization, page, WTFMove(navigationAction), WTFMove(newPageCallback), WTFMove(uiClientCallback)));
}

PopUpSOAuthorizationSession::PopUpSOAuthorizationSession(SOAuthorization *soAuthorization, WebPageProxy& page, Ref<API::NavigationAction>&& navigationAction, NewPageCallback&& newPageCallback, UIClientCallback&& uiClientCallback)
    : SOAuthorizationSession(soAuthorization, WTFMove(navigationAction), page, InitiatingAction::PopUp)
    , m_newPageCallback(WTFMove(newPageCallback))
    , m_uiClientCallback(WTFMove(uiClientCallback))
{
}

PopUpSOAuthorizationSession::~PopUpSOAuthorizationSession()
{
    ASSERT(state() != State::Waiting);
    if (m_newPageCallback)
        m_newPageCallback(nullptr);
}

void PopUpSOAuthorizationSession::shouldStartInternal()
{
    ASSERT(page() && page()->isInWindow());
    start();
}

void PopUpSOAuthorizationSession::fallBackToWebPathInternal()
{
    m_uiClientCallback(releaseNavigationAction(), WTFMove(m_newPageCallback));
}

void PopUpSOAuthorizationSession::abortInternal()
{
    if (!page()) {
        m_newPageCallback(nullptr);
        return;
    }

    initSecretWebView();
    m_newPageCallback(m_secretWebView->_page.get());
    [m_secretWebView evaluateJavaScript: @"window.close()" completionHandler:nil];
}

void PopUpSOAuthorizationSession::completeInternal(const WebCore::ResourceResponse& response, NSData *data)
{
    if (response.httpStatusCode() != 200 || !page()) {
        fallBackToWebPathInternal();
        return;
    }

    initSecretWebView();
    m_newPageCallback(m_secretWebView->_page.get());
    [m_secretWebView loadData:data MIMEType:@"text/html" characterEncodingName:@"UTF-8" baseURL:response.url()];
}

void PopUpSOAuthorizationSession::close(WKWebView *webView)
{
    if (!m_secretWebView)
        return;
    if (state() != State::Completed || webView != m_secretWebView.get()) {
        ASSERT_NOT_REACHED();
        return;
    }
    m_secretWebView = nullptr;
    WTFLogAlways("SecretWebView is cleaned.");
}

void PopUpSOAuthorizationSession::initSecretWebView()
{
    ASSERT(page());
    auto initiatorWebView = fromWebPageProxy(*page());
    auto configuration = adoptNS([initiatorWebView.configuration copy]);
    [configuration _setRelatedWebView:initiatorWebView];
    m_secretWebView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);

    m_secretDelegate = adoptNS([[WKSOSecretDelegate alloc] initWithSession:this]);
    [m_secretWebView setUIDelegate:m_secretDelegate.get()];
    [m_secretWebView setNavigationDelegate:m_secretDelegate.get()];

    m_secretWebView->_page->setShouldSuppressSOAuthorizationInAllNavigationPolicyDecision();
    WTFLogAlways("SecretWebView is created.");
}

} // namespace WebKit

#endif