blob: 8ea4e43ec446b526a318df42c44cb24392e8a8b7 [file] [log] [blame]
/*
* 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.
*/
#include "config.h"
#include <WebCore/LoggedInStatus.h>
#include <WebCore/RegistrableDomain.h>
#include <wtf/Seconds.h>
#include <wtf/text/StringBuilder.h>
using namespace WebCore;
namespace TestWebKitAPI {
// Positive test cases.
TEST(LoggedInStatus, DefaultExpiryWebAuthn)
{
RegistrableDomain loginDomain { URL { "https://login.example.com"_s } };
auto loggedInResult = LoggedInStatus::create(loginDomain, "admin"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::WebAuthn);
auto& loggedIn = loggedInResult.returnValue().get();
auto now = WallTime::now();
ASSERT_EQ(loggedIn.registrableDomain().string(), "example.com"_s);
ASSERT_EQ(loggedIn.username(), "admin"_s);
ASSERT_EQ(loggedIn.credentialTokenType(), LoggedInStatus::CredentialTokenType::HTTPStateToken);
ASSERT_EQ(loggedIn.authenticationType(), LoggedInStatus::AuthenticationType::WebAuthn);
ASSERT_TRUE(loggedIn.loggedInTime() < now + 60_s);
ASSERT_TRUE(loggedIn.loggedInTime() > now - 60_s);
ASSERT_TRUE(loggedIn.expiry() < now + LoggedInStatus::TimeToLiveLong + 60_s);
ASSERT_TRUE(loggedIn.expiry() > now + LoggedInStatus::TimeToLiveLong - 60_s);
ASSERT_FALSE(loggedIn.hasExpired());
}
TEST(LoggedInStatus, DefaultExpiryPasswordManager)
{
RegistrableDomain loginDomain { URL { "https://login.example.com"_s } };
auto loggedInResult = LoggedInStatus::create(loginDomain, "admin"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::PasswordManager);
auto& loggedIn = loggedInResult.returnValue().get();
auto now = WallTime::now();
ASSERT_EQ(loggedIn.authenticationType(), LoggedInStatus::AuthenticationType::PasswordManager);
ASSERT_TRUE(loggedIn.expiry() < now + LoggedInStatus::TimeToLiveLong + 60_s);
ASSERT_TRUE(loggedIn.expiry() > now + LoggedInStatus::TimeToLiveLong - 60_s);
ASSERT_FALSE(loggedIn.hasExpired());
}
TEST(LoggedInStatus, DefaultExpiryUnmanaged)
{
RegistrableDomain loginDomain { URL { "https://login.example.com"_s } };
auto loggedInResult = LoggedInStatus::create(loginDomain, "admin"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::Unmanaged);
auto& loggedIn = loggedInResult.returnValue().get();
auto now = WallTime::now();
ASSERT_EQ(loggedIn.authenticationType(), LoggedInStatus::AuthenticationType::Unmanaged);
ASSERT_TRUE(loggedIn.expiry() < now + LoggedInStatus::TimeToLiveShort + 60_s);
ASSERT_TRUE(loggedIn.expiry() > now + LoggedInStatus::TimeToLiveShort - 60_s);
ASSERT_FALSE(loggedIn.hasExpired());
}
TEST(LoggedInStatus, CustomExpiryBelowLong)
{
RegistrableDomain loginDomain { URL { "https://login.example.com"_s } };
auto customExpiry = LoggedInStatus::TimeToLiveLong - 48_h;
auto loggedInResult = LoggedInStatus::create(loginDomain, "admin"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::WebAuthn, customExpiry);
auto& loggedIn = loggedInResult.returnValue().get();
auto now = WallTime::now();
ASSERT_EQ(loggedIn.authenticationType(), LoggedInStatus::AuthenticationType::WebAuthn);
ASSERT_TRUE(loggedIn.expiry() < now + customExpiry + 60_s);
ASSERT_TRUE(loggedIn.expiry() > now + customExpiry - 60_s);
ASSERT_FALSE(loggedIn.hasExpired());
}
TEST(LoggedInStatus, CustomExpiryBelowShort)
{
RegistrableDomain loginDomain { URL { "https://login.example.com"_s } };
auto customExpiry = LoggedInStatus::TimeToLiveShort - 48_h;
auto loggedInResult = LoggedInStatus::create(loginDomain, "admin"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::PasswordManager, customExpiry);
auto& loggedIn = loggedInResult.returnValue().get();
auto now = WallTime::now();
ASSERT_EQ(loggedIn.authenticationType(), LoggedInStatus::AuthenticationType::PasswordManager);
ASSERT_TRUE(loggedIn.expiry() < now + customExpiry + 60_s);
ASSERT_TRUE(loggedIn.expiry() > now + customExpiry - 60_s);
ASSERT_FALSE(loggedIn.hasExpired());
}
TEST(LoggedInStatus, RenewedExpiry)
{
RegistrableDomain loginDomain { URL { "https://login.example.com"_s } };
auto customExpiry = LoggedInStatus::TimeToLiveShort - 48_h;
auto loggedInResult = LoggedInStatus::create(loginDomain, "admin"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::WebAuthn, customExpiry);
auto loggedIn = loggedInResult.releaseReturnValue();
auto now = WallTime::now();
ASSERT_EQ(loggedIn->authenticationType(), LoggedInStatus::AuthenticationType::WebAuthn);
ASSERT_TRUE(loggedIn->expiry() < now + customExpiry + 60_s);
ASSERT_TRUE(loggedIn->expiry() > now + customExpiry - 60_s);
ASSERT_FALSE(loggedIn->hasExpired());
auto newCustomExpiry = 24_h;
loggedIn->setTimeToLive(newCustomExpiry);
ASSERT_EQ(loggedIn->authenticationType(), LoggedInStatus::AuthenticationType::WebAuthn);
ASSERT_TRUE(loggedIn->expiry() < now + newCustomExpiry + 60_s);
ASSERT_TRUE(loggedIn->expiry() > now + newCustomExpiry - 60_s);
ASSERT_FALSE(loggedIn->hasExpired());
}
// Negative test cases.
TEST(LoggedInStatus, InvalidUsernames)
{
RegistrableDomain loginDomain { URL { "https://login.example.com"_s } };
// Whitespace is not allowed.
auto loggedInResult = LoggedInStatus::create(loginDomain, "I use spaces"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::WebAuthn);
ASSERT_TRUE(loggedInResult.hasException());
// Newlines are not allowed.
loggedInResult = LoggedInStatus::create(loginDomain, "Enter\nreturn"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::WebAuthn);
ASSERT_TRUE(loggedInResult.hasException());
// There is a max length to usernames.
StringBuilder builder;
for (unsigned i = 0; i <= LoggedInStatus::UsernameMaxLength; ++i)
builder.append('a');
loggedInResult = LoggedInStatus::create(loginDomain, builder.toString(), LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::WebAuthn);
ASSERT_TRUE(loggedInResult.hasException());
}
TEST(LoggedInStatus, ClampedExpiryLong)
{
RegistrableDomain loginDomain { URL { "https://login.example.com"_s } };
// Too long expiries for managed auth types should be clamped to LoggedInStatus::TimeToLiveLong.
auto loggedInResult = LoggedInStatus::create(loginDomain, "admin"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::WebAuthn, LoggedInStatus::TimeToLiveLong + 24_h);
auto loggedIn = loggedInResult.releaseReturnValue();
auto now = WallTime::now();
ASSERT_EQ(loggedIn->authenticationType(), LoggedInStatus::AuthenticationType::WebAuthn);
ASSERT_TRUE(loggedIn->expiry() < now + LoggedInStatus::TimeToLiveLong + 60_s);
ASSERT_TRUE(loggedIn->expiry() > now + LoggedInStatus::TimeToLiveLong - 60_s);
ASSERT_FALSE(loggedIn->hasExpired());
// Renewed, too long expiries for managed auth types should also be clamped to LoggedInStatus::TimeToLiveLong.
auto newCustomExpiry = LoggedInStatus::TimeToLiveLong + 24_h;
loggedIn->setTimeToLive(newCustomExpiry);
ASSERT_EQ(loggedIn->authenticationType(), LoggedInStatus::AuthenticationType::WebAuthn);
ASSERT_TRUE(loggedIn->expiry() < now + LoggedInStatus::TimeToLiveLong + 60_s);
ASSERT_TRUE(loggedIn->expiry() > now + LoggedInStatus::TimeToLiveLong - 60_s);
ASSERT_FALSE(loggedIn->hasExpired());
}
TEST(LoggedInStatus, ClampedExpiryShort)
{
RegistrableDomain loginDomain { URL { "https://login.example.com"_s } };
// Too long expiries for unmanaged auth types should be clamped to LoggedInStatus::TimeToLiveShort.
auto loggedInResult = LoggedInStatus::create(loginDomain, "admin"_s, LoggedInStatus::CredentialTokenType::HTTPStateToken, LoggedInStatus::AuthenticationType::Unmanaged, LoggedInStatus::TimeToLiveShort + 24_h);
auto loggedIn = loggedInResult.returnValue().get();
auto now = WallTime::now();
ASSERT_EQ(loggedIn.authenticationType(), LoggedInStatus::AuthenticationType::Unmanaged);
ASSERT_TRUE(loggedIn.expiry() < now + LoggedInStatus::TimeToLiveShort + 60_s);
ASSERT_TRUE(loggedIn.expiry() > now + LoggedInStatus::TimeToLiveShort - 60_s);
ASSERT_FALSE(loggedIn.hasExpired());
// Renewed, too long expiries for unmanaged auth types should also be clamped to LoggedInStatus::TimeToLiveShort.
auto newCustomExpiry = LoggedInStatus::TimeToLiveShort + 24_h;
loggedIn.setTimeToLive(newCustomExpiry);
ASSERT_EQ(loggedIn.authenticationType(), LoggedInStatus::AuthenticationType::Unmanaged);
ASSERT_TRUE(loggedIn.expiry() < now + LoggedInStatus::TimeToLiveShort + 60_s);
ASSERT_TRUE(loggedIn.expiry() > now + LoggedInStatus::TimeToLiveShort - 60_s);
ASSERT_FALSE(loggedIn.hasExpired());
}
} // namespace TestWebKitAPI