Implement DeviceFamilyLinkAccountsAllowed policy

This policy allows adding Family Link accounts additionally to
accounts listed in DeviceUserAllowlist policy and is dedicated
for school use.

(cherry picked from commit aa95dfdff6b4f1105c99ef2217a5edc5a9363fa0)

Bug: 1126155
Change-Id: Ia0f34b97b31dd143db3a0c2d839e2b4a314ac086
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2448671
Reviewed-by: Roman Sorokin [CET] <[email protected]>
Commit-Queue: Aga Wronska <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#815535}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2465748
Reviewed-by: Aga Wronska <[email protected]>
Cr-Commit-Position: refs/branch-heads/4280@{#284}
Cr-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852}
diff --git a/chrome/browser/chromeos/login/auth/chrome_login_performer.cc b/chrome/browser/chromeos/login/auth/chrome_login_performer.cc
index d25118a3..61fd3f8 100644
--- a/chrome/browser/chromeos/login/auth/chrome_login_performer.cc
+++ b/chrome/browser/chromeos/login/auth/chrome_login_performer.cc
@@ -82,10 +82,12 @@
   }
 }
 
-bool ChromeLoginPerformer::IsUserAllowlisted(const AccountId& account_id,
-                                             bool* wildcard_match) {
+bool ChromeLoginPerformer::IsUserAllowlisted(
+    const AccountId& account_id,
+    bool* wildcard_match,
+    const base::Optional<user_manager::UserType>& user_type) {
   return CrosSettings::Get()->IsUserAllowlisted(account_id.GetUserEmail(),
-                                                wildcard_match);
+                                                wildcard_match, user_type);
 }
 
 void ChromeLoginPerformer::RunOnlineAllowlistCheck(
diff --git a/chrome/browser/chromeos/login/auth/chrome_login_performer.h b/chrome/browser/chromeos/login/auth/chrome_login_performer.h
index 5ae87d6..d1c31e7 100644
--- a/chrome/browser/chromeos/login/auth/chrome_login_performer.h
+++ b/chrome/browser/chromeos/login/auth/chrome_login_performer.h
@@ -10,12 +10,14 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "chrome/browser/chromeos/policy/wildcard_login_checker.h"
 #include "chromeos/login/auth/auth_status_consumer.h"
 #include "chromeos/login/auth/authenticator.h"
 #include "chromeos/login/auth/extended_authenticator.h"
 #include "chromeos/login/auth/login_performer.h"
 #include "chromeos/login/auth/user_context.h"
+#include "components/user_manager/user_type.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -35,8 +37,11 @@
   explicit ChromeLoginPerformer(Delegate* delegate);
   ~ChromeLoginPerformer() override;
 
-  bool IsUserAllowlisted(const AccountId& account_id,
-                         bool* wildcard_match) override;
+  // LoginPerformer:
+  bool IsUserAllowlisted(
+      const AccountId& account_id,
+      bool* wildcard_match,
+      const base::Optional<user_manager::UserType>& user_type) override;
 
  protected:
   bool RunTrustedCheck(base::OnceClosure callback) override;
diff --git a/chrome/browser/chromeos/login/existing_user_controller.cc b/chrome/browser/chromeos/login/existing_user_controller.cc
index a0f34c3d..95e10a0 100644
--- a/chrome/browser/chromeos/login/existing_user_controller.cc
+++ b/chrome/browser/chromeos/login/existing_user_controller.cc
@@ -848,13 +848,17 @@
   given_name_ = base::UTF8ToUTF16(given_name);
 }
 
-bool ExistingUserController::IsUserAllowlisted(const AccountId& account_id) {
+bool ExistingUserController::IsUserAllowlisted(
+    const AccountId& account_id,
+    const base::Optional<user_manager::UserType>& user_type) {
   bool wildcard_match = false;
-  if (login_performer_.get())
-    return login_performer_->IsUserAllowlisted(account_id, &wildcard_match);
+  if (login_performer_.get()) {
+    return login_performer_->IsUserAllowlisted(account_id, &wildcard_match,
+                                               user_type);
+  }
 
   return cros_settings_->IsUserAllowlisted(account_id.GetUserEmail(),
-                                           &wildcard_match);
+                                           &wildcard_match, user_type);
 }
 
 void ExistingUserController::LocalStateChanged(
diff --git a/chrome/browser/chromeos/login/existing_user_controller.h b/chrome/browser/chromeos/login/existing_user_controller.h
index 1f65c480..19bbfba 100644
--- a/chrome/browser/chromeos/login/existing_user_controller.h
+++ b/chrome/browser/chromeos/login/existing_user_controller.h
@@ -16,6 +16,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "base/optional.h"
 #include "base/scoped_observer.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
@@ -34,6 +35,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
+#include "components/user_manager/user_type.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h"
@@ -117,7 +119,9 @@
   void SetDisplayEmail(const std::string& email);
   void SetDisplayAndGivenName(const std::string& display_name,
                               const std::string& given_name);
-  bool IsUserAllowlisted(const AccountId& account_id);
+  bool IsUserAllowlisted(
+      const AccountId& account_id,
+      const base::Optional<user_manager::UserType>& user_type);
 
   // user_manager::UserManager::Observer:
   void LocalStateChanged(user_manager::UserManager* user_manager) override;
diff --git a/chrome/browser/chromeos/login/ui/fake_login_display_host.cc b/chrome/browser/chromeos/login/ui/fake_login_display_host.cc
index 20ec078..6db9628 100644
--- a/chrome/browser/chromeos/login/ui/fake_login_display_host.cc
+++ b/chrome/browser/chromeos/login/ui/fake_login_display_host.cc
@@ -106,7 +106,9 @@
 
 void FakeLoginDisplayHost::LoadSigninWallpaper() {}
 
-bool FakeLoginDisplayHost::IsUserAllowlisted(const AccountId& account_id) {
+bool FakeLoginDisplayHost::IsUserAllowlisted(
+    const AccountId& account_id,
+    const base::Optional<user_manager::UserType>& user_type) {
   return false;
 }
 
diff --git a/chrome/browser/chromeos/login/ui/fake_login_display_host.h b/chrome/browser/chromeos/login/ui/fake_login_display_host.h
index 73d552f..4f00e6d 100644
--- a/chrome/browser/chromeos/login/ui/fake_login_display_host.h
+++ b/chrome/browser/chromeos/login/ui/fake_login_display_host.h
@@ -9,7 +9,9 @@
 #include <string>
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
+#include "components/user_manager/user_type.h"
 
 namespace session_manager {
 class SessionManager;
@@ -50,7 +52,9 @@
                               const std::string& given_name) override;
   void LoadWallpaper(const AccountId& account_id) override;
   void LoadSigninWallpaper() override;
-  bool IsUserAllowlisted(const AccountId& account_id) override;
+  bool IsUserAllowlisted(
+      const AccountId& account_id,
+      const base::Optional<user_manager::UserType>& user_type) override;
   void ShowGaiaDialog(const AccountId& prefilled_account) override;
   void HideOobeDialog() override;
   void UpdateOobeDialogState(ash::OobeDialogState state) override;
diff --git a/chrome/browser/chromeos/login/ui/login_display_host.h b/chrome/browser/chromeos/login/ui/login_display_host.h
index 9b42633..ae3de75 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host.h
@@ -16,6 +16,8 @@
 #include "chrome/browser/chromeos/login/auth/auth_prewarmer.h"
 #include "chrome/browser/chromeos/login/oobe_screen.h"
 #include "chrome/browser/chromeos/login/ui/login_display.h"
+#include "components/user_manager/user_type.h"
+
 #include "ui/gfx/native_widget_types.h"
 
 class AccountId;
@@ -166,7 +168,9 @@
   virtual void LoadSigninWallpaper() = 0;
 
   // Returns true if user is allowed to log in by domain policy.
-  virtual bool IsUserAllowlisted(const AccountId& account_id) = 0;
+  virtual bool IsUserAllowlisted(
+      const AccountId& account_id,
+      const base::Optional<user_manager::UserType>& user_type) = 0;
 
   // ----- Password change flow methods -----
   // Cancels current password changed flow.
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_common.cc b/chrome/browser/chromeos/login/ui/login_display_host_common.cc
index 26c78937..196083ca 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_common.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_common.cc
@@ -229,10 +229,12 @@
   WallpaperControllerClient::Get()->ShowSigninWallpaper();
 }
 
-bool LoginDisplayHostCommon::IsUserAllowlisted(const AccountId& account_id) {
+bool LoginDisplayHostCommon::IsUserAllowlisted(
+    const AccountId& account_id,
+    const base::Optional<user_manager::UserType>& user_type) {
   if (!GetExistingUserController())
     return true;
-  return GetExistingUserController()->IsUserAllowlisted(account_id);
+  return GetExistingUserController()->IsUserAllowlisted(account_id, user_type);
 }
 
 void LoginDisplayHostCommon::CancelPasswordChangedFlow() {
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_common.h b/chrome/browser/chromeos/login/ui/login_display_host_common.h
index c99e0b5..93c5485a 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_common.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_common.h
@@ -10,10 +10,12 @@
 #include <vector>
 
 #include "ash/public/cpp/login_accelerators.h"
+#include "base/optional.h"
 #include "chrome/browser/chromeos/login/ui/kiosk_app_menu_controller.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/ui/browser_list_observer.h"
 #include "components/keep_alive_registry/scoped_keep_alive.h"
+#include "components/user_manager/user_type.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 
@@ -50,7 +52,9 @@
                               const std::string& given_name) final;
   void LoadWallpaper(const AccountId& account_id) final;
   void LoadSigninWallpaper() final;
-  bool IsUserAllowlisted(const AccountId& account_id) final;
+  bool IsUserAllowlisted(
+      const AccountId& account_id,
+      const base::Optional<user_manager::UserType>& user_type) final;
   void CancelPasswordChangedFlow() final;
   void MigrateUserData(const std::string& old_password) final;
   void ResyncUserData() final;
diff --git a/chrome/browser/chromeos/login/ui/login_display_mojo.cc b/chrome/browser/chromeos/login/ui/login_display_mojo.cc
index 544d4a9..5042d3a 100644
--- a/chrome/browser/chromeos/login/ui/login_display_mojo.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_mojo.cc
@@ -173,6 +173,7 @@
   // related.
   if (error_msg_id != IDS_LOGIN_ERROR_ALLOWLIST &&
       error_msg_id != IDS_ENTERPRISE_LOGIN_ERROR_ALLOWLIST &&
+      error_msg_id != IDS_ENTERPRISE_AND_FAMILY_LINK_LOGIN_ERROR_ALLOWLIST &&
       error_msg_id != IDS_LOGIN_ERROR_OWNER_KEY_LOST &&
       error_msg_id != IDS_LOGIN_ERROR_OWNER_REQUIRED &&
       error_msg_id != IDS_LOGIN_ERROR_GOOGLE_ACCOUNT_NOT_ALLOWED &&
diff --git a/chrome/browser/chromeos/login/ui/login_display_webui.cc b/chrome/browser/chromeos/login/ui/login_display_webui.cc
index a7c57e84..e91b50ac 100644
--- a/chrome/browser/chromeos/login/ui/login_display_webui.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_webui.cc
@@ -144,6 +144,7 @@
   // related.
   if (error_msg_id != IDS_LOGIN_ERROR_ALLOWLIST &&
       error_msg_id != IDS_ENTERPRISE_LOGIN_ERROR_ALLOWLIST &&
+      error_msg_id != IDS_ENTERPRISE_AND_FAMILY_LINK_LOGIN_ERROR_ALLOWLIST &&
       error_msg_id != IDS_LOGIN_ERROR_OWNER_KEY_LOST &&
       error_msg_id != IDS_LOGIN_ERROR_OWNER_REQUIRED &&
       error_msg_id != IDS_LOGIN_ERROR_GOOGLE_ACCOUNT_NOT_ALLOWED &&
diff --git a/chrome/browser/chromeos/login/ui/mock_login_display_host.h b/chrome/browser/chromeos/login/ui/mock_login_display_host.h
index bd53615..4c59012 100644
--- a/chrome/browser/chromeos/login/ui/mock_login_display_host.h
+++ b/chrome/browser/chromeos/login/ui/mock_login_display_host.h
@@ -9,9 +9,11 @@
 
 #include "ash/public/cpp/login_accelerators.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_types.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
+#include "components/user_manager/user_type.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace chromeos {
@@ -72,7 +74,10 @@
               (override));
   MOCK_METHOD(void, LoadWallpaper, (const AccountId&), (override));
   MOCK_METHOD(void, LoadSigninWallpaper, (), (override));
-  MOCK_METHOD(bool, IsUserAllowlisted, (const AccountId&), (override));
+  MOCK_METHOD(bool,
+              IsUserAllowlisted,
+              (const AccountId&, const base::Optional<user_manager::UserType>&),
+              (override));
   MOCK_METHOD(void, CancelPasswordChangedFlow, (), (override));
   MOCK_METHOD(void, MigrateUserData, (const std::string&), (override));
   MOCK_METHOD(void, ResyncUserData, (), (override));
diff --git a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
index 3023d66f..ee91f6c0 100644
--- a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
@@ -1227,7 +1227,7 @@
     const user_manager::User& user) const {
   DCHECK(user.HasGaiaAccount());
   return cros_settings_->IsUserAllowlisted(user.GetAccountId().GetUserEmail(),
-                                           nullptr);
+                                           nullptr, user.GetType());
 }
 
 void ChromeUserManagerImpl::OnMinimumVersionStateChanged() {
diff --git a/chrome/browser/chromeos/login/users/fake_chrome_user_manager.cc b/chrome/browser/chromeos/login/users/fake_chrome_user_manager.cc
index f8b4f2100..c6f36405 100644
--- a/chrome/browser/chromeos/login/users/fake_chrome_user_manager.cc
+++ b/chrome/browser/chromeos/login/users/fake_chrome_user_manager.cc
@@ -623,7 +623,7 @@
     const user_manager::User& user) const {
   DCHECK(user.HasGaiaAccount());
   return CrosSettings::Get()->IsUserAllowlisted(
-      user.GetAccountId().GetUserEmail(), nullptr);
+      user.GetAccountId().GetUserEmail(), nullptr, user.GetType());
 }
 
 bool FakeChromeUserManager::IsUserAllowed(
diff --git a/chrome/browser/chromeos/policy/user_policy_manager_builder_chromeos.cc b/chrome/browser/chromeos/policy/user_policy_manager_builder_chromeos.cc
index 6d75afe..44ab761 100644
--- a/chrome/browser/chromeos/policy/user_policy_manager_builder_chromeos.cc
+++ b/chrome/browser/chromeos/policy/user_policy_manager_builder_chromeos.cc
@@ -308,7 +308,7 @@
     bool wildcard_match = false;
     if (connector->IsEnterpriseManaged() &&
         chromeos::CrosSettings::Get()->IsUserAllowlisted(
-            account_id.GetUserEmail(), &wildcard_match) &&
+            account_id.GetUserEmail(), &wildcard_match, user->GetType()) &&
         wildcard_match &&
         !connector->IsNonEnterpriseUser(account_id.GetUserEmail())) {
       manager->EnableWildcardLoginCheck(account_id.GetUserEmail());
diff --git a/chrome/browser/chromeos/settings/cros_settings.cc b/chrome/browser/chromeos/settings/cros_settings.cc
index f4f2c0c0..4f6e84f9 100644
--- a/chrome/browser/chromeos/settings/cros_settings.cc
+++ b/chrome/browser/chromeos/settings/cros_settings.cc
@@ -76,20 +76,6 @@
   g_using_cros_settings_for_testing = false;
 }
 
-bool CrosSettings::IsUserAllowlisted(const std::string& username,
-                                     bool* wildcard_match) const {
-  // Skip allowlist check for tests.
-  if (chromeos::switches::ShouldSkipOobePostLogin()) {
-    return true;
-  }
-
-  bool allow_new_user = false;
-  GetBoolean(kAccountsPrefAllowNewUser, &allow_new_user);
-  if (allow_new_user)
-    return true;
-  return FindEmailInList(kAccountsPrefUsers, username, wildcard_match);
-}
-
 CrosSettings::CrosSettings() = default;
 
 CrosSettings::CrosSettings(DeviceSettingsService* device_settings_service,
@@ -194,6 +180,28 @@
   return false;
 }
 
+bool CrosSettings::IsUserAllowlisted(
+    const std::string& username,
+    bool* wildcard_match,
+    const base::Optional<user_manager::UserType>& user_type) const {
+  // Skip allowlist check for tests.
+  if (chromeos::switches::ShouldSkipOobePostLogin()) {
+    return true;
+  }
+
+  bool allow_new_user = false;
+  GetBoolean(kAccountsPrefAllowNewUser, &allow_new_user);
+  if (allow_new_user)
+    return true;
+
+  if (FindEmailInList(kAccountsPrefUsers, username, wildcard_match))
+    return true;
+
+  bool family_link_allowed = false;
+  GetBoolean(kAccountsPrefFamilyLinkAccountsAllowed, &family_link_allowed);
+  return family_link_allowed && user_type == user_manager::USER_TYPE_CHILD;
+}
+
 bool CrosSettings::FindEmailInList(const std::string& path,
                                    const std::string& email,
                                    bool* wildcard_match) const {
diff --git a/chrome/browser/chromeos/settings/cros_settings.h b/chrome/browser/chromeos/settings/cros_settings.h
index 0219252..b6c07ad 100644
--- a/chrome/browser/chromeos/settings/cros_settings.h
+++ b/chrome/browser/chromeos/settings/cros_settings.h
@@ -13,9 +13,11 @@
 #include "base/callback_forward.h"
 #include "base/callback_list.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "chromeos/settings/cros_settings_names.h"
 #include "chromeos/settings/cros_settings_provider.h"
+#include "components/user_manager/user_type.h"
 
 class PrefService;
 
@@ -45,12 +47,6 @@
   static void SetForTesting(CrosSettings* test_instance);
   static void ShutdownForTesting();
 
-  // Checks if the given username is on the list of users allowed to sign-in to
-  // this device. |wildcard_match| may be NULL. If it's present, it'll be set to
-  // true if the list check was satisfied via a wildcard.
-  bool IsUserAllowlisted(const std::string& username,
-                         bool* wildcard_match) const;
-
   // Creates an instance with no providers as yet. This is meant for unit tests,
   // production code uses the singleton returned by Get() above.
   CrosSettings();
@@ -95,6 +91,16 @@
   bool GetDictionary(const std::string& path,
                      const base::DictionaryValue** out_value) const;
 
+  // Checks if the given username is on the list of users allowed to sign-in to
+  // this device. |wildcard_match| may be nullptr. If it's present, it'll be set
+  // to true if the list check was satisfied via a wildcard. In some
+  // configurations user can be allowed based on the |user_type|. See
+  // |DeviceFamilyLinkAccountsAllowed| policy.
+  bool IsUserAllowlisted(
+      const std::string& username,
+      bool* wildcard_match,
+      const base::Optional<user_manager::UserType>& user_type) const;
+
   // Helper function for the allowlist op. Implemented here because we will need
   // this in a few places. The functions searches for |email| in the pref |path|
   // It respects allowlists so [email protected] will match *@bar.baz too. If the
diff --git a/chrome/browser/chromeos/settings/cros_settings_unittest.cc b/chrome/browser/chromeos/settings/cros_settings_unittest.cc
index 77326026..681409b9 100644
--- a/chrome/browser/chromeos/settings/cros_settings_unittest.cc
+++ b/chrome/browser/chromeos/settings/cros_settings_unittest.cc
@@ -10,6 +10,8 @@
 
 #include "base/bind.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos.h"
@@ -20,12 +22,14 @@
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/settings/cros_settings_names.h"
 #include "chromeos/tpm/stub_install_attributes.h"
 #include "components/ownership/mock_owner_key_util.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/proto/chrome_device_policy.pb.h"
 #include "components/policy/proto/device_management_backend.pb.h"
+#include "components/user_manager/user_type.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_utils.h"
@@ -131,6 +135,11 @@
                                                 NULL);
   }
 
+  bool IsUserAllowed(const std::string& username,
+                     const base::Optional<user_manager::UserType>& user_type) {
+    return CrosSettings::Get()->IsUserAllowlisted(username, nullptr, user_type);
+  }
+
   content::BrowserTaskEnvironment task_environment_{
       content::BrowserTaskEnvironment::IO_MAINLOOP};
 
@@ -348,4 +357,83 @@
   EXPECT_TRUE(wildcard_match);
 }
 
+// DeviceFamilyLinkAccountsAllowed should not have any effect if allowlist is
+// not set.
+TEST_F(CrosSettingsTest, AllowFamilyLinkAccountsWithEmptyAllowlist) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      chromeos::features::kFamilyLinkOnSchoolDevice);
+
+  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
+      false);
+  device_policy_.payload().mutable_user_allowlist()->clear_user_allowlist();
+  device_policy_.payload()
+      .mutable_family_link_accounts_allowed()
+      ->set_family_link_accounts_allowed(true);
+
+  StoreDevicePolicy();
+
+  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
+  ExpectPref(kAccountsPrefUsers, base::ListValue());
+  ExpectPref(kAccountsPrefFamilyLinkAccountsAllowed, base::Value(false));
+
+  EXPECT_FALSE(IsUserAllowed(kUser1, base::nullopt));
+  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::USER_TYPE_CHILD));
+  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::USER_TYPE_REGULAR));
+}
+
+// DeviceFamilyLinkAccountsAllowed should not have any effect if the feature is
+// disabled.
+TEST_F(CrosSettingsTest, AllowFamilyLinkAccountsWithFeatureDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      chromeos::features::kFamilyLinkOnSchoolDevice);
+
+  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
+      false);
+  device_policy_.payload().mutable_user_allowlist()->add_user_allowlist(kOwner);
+  device_policy_.payload()
+      .mutable_family_link_accounts_allowed()
+      ->set_family_link_accounts_allowed(true);
+
+  StoreDevicePolicy();
+
+  base::ListValue allowlist;
+  allowlist.AppendString(kOwner);
+  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
+  ExpectPref(kAccountsPrefUsers, allowlist);
+  ExpectPref(kAccountsPrefFamilyLinkAccountsAllowed, base::Value(false));
+
+  EXPECT_TRUE(IsUserAllowed(kOwner, base::nullopt));
+  EXPECT_FALSE(IsUserAllowed(kUser1, base::nullopt));
+  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::USER_TYPE_CHILD));
+  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::USER_TYPE_REGULAR));
+}
+
+TEST_F(CrosSettingsTest, AllowFamilyLinkAccountsWithAllowlist) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      chromeos::features::kFamilyLinkOnSchoolDevice);
+
+  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
+      false);
+  device_policy_.payload().mutable_user_allowlist()->add_user_allowlist(kOwner);
+  device_policy_.payload()
+      .mutable_family_link_accounts_allowed()
+      ->set_family_link_accounts_allowed(true);
+
+  StoreDevicePolicy();
+
+  base::ListValue allowlist;
+  allowlist.AppendString(kOwner);
+  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
+  ExpectPref(kAccountsPrefUsers, allowlist);
+  ExpectPref(kAccountsPrefFamilyLinkAccountsAllowed, base::Value(true));
+
+  EXPECT_TRUE(IsUserAllowed(kOwner, base::nullopt));
+  EXPECT_FALSE(IsUserAllowed(kUser1, base::nullopt));
+  EXPECT_TRUE(IsUserAllowed(kUser1, user_manager::USER_TYPE_CHILD));
+  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::USER_TYPE_REGULAR));
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/settings/device_settings_provider.cc b/chrome/browser/chromeos/settings/device_settings_provider.cc
index 278fa30..ccbe3b4 100644
--- a/chrome/browser/chromeos/settings/device_settings_provider.cc
+++ b/chrome/browser/chromeos/settings/device_settings_provider.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/chromeos/settings/device_settings_cache.h"
 #include "chrome/browser/chromeos/settings/stats_reporting_controller.h"
 #include "chrome/browser/chromeos/tpm_firmware_update.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/cryptohome/cryptohome_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
@@ -216,12 +217,18 @@
   }
 
   // Value of DeviceFamilyLinkAccountsAllowed policy does not affect
-  // |kAccountsPrefAllowNewUser| setting. Family Link accounts will be only
-  // allowed if both |kAccountsPrefAllowNewUser| and
-  // |kAccountsPrefFamilyLinkAccountsAllowed| are true.
+  // |kAccountsPrefAllowNewUser| setting. Family Link accounts are only
+  // allowed if user allowlist is enforced.
+  bool user_allowlist_enforced =
+      ((policy.has_user_whitelist() &&
+        policy.user_whitelist().user_whitelist_size() > 0) ||
+       (policy.has_user_allowlist() &&
+        policy.user_allowlist().user_allowlist_size() > 0));
   new_values_cache->SetBoolean(
       kAccountsPrefFamilyLinkAccountsAllowed,
-      policy.has_family_link_accounts_allowed() &&
+      chromeos::features::IsFamilyLinkOnSchoolDeviceEnabled() &&
+          user_allowlist_enforced &&
+          policy.has_family_link_accounts_allowed() &&
           policy.family_link_accounts_allowed()
               .has_family_link_accounts_allowed() &&
           policy.family_link_accounts_allowed().family_link_accounts_allowed());
diff --git a/chrome/browser/chromeos/settings/device_settings_provider_unittest.cc b/chrome/browser/chromeos/settings/device_settings_provider_unittest.cc
index 8f4ea60..70cc07db 100644
--- a/chrome/browser/chromeos/settings/device_settings_provider_unittest.cc
+++ b/chrome/browser/chromeos/settings/device_settings_provider_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/macros.h"
 #include "base/path_service.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_path_override.h"
 #include "base/values.h"
 #include "chrome/browser/chromeos/policy/device_local_account.h"
@@ -25,6 +26,7 @@
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/settings/cros_settings_names.h"
 #include "chromeos/tpm/stub_install_attributes.h"
 #include "components/policy/proto/chrome_device_policy.pb.h"
@@ -421,6 +423,13 @@
     BuildAndInstallDevicePolicy();
   }
 
+  void AddUserToAllowlist(const std::string& user_id) {
+    em::UserAllowlistProto* proto =
+        device_policy_->payload().mutable_user_allowlist();
+    proto->add_user_allowlist(user_id);
+    BuildAndInstallDevicePolicy();
+  }
+
   void VerifyDeviceShowLowDiskSpaceNotification(bool expected) {
     const base::Value expected_value(expected);
     EXPECT_EQ(expected_value,
@@ -1177,14 +1186,43 @@
   VerifyDeviceShowLowDiskSpaceNotification(false);
 }
 
-TEST_F(DeviceSettingsProviderTest, DeviceFamilyLinkAccountsAllowed) {
+// Tests DeviceFamilyLinkAccountsAllowed policy with the feature disabled.
+// The policy should have no effect.
+TEST_F(DeviceSettingsProviderTest, DeviceFamilyLinkAccountsAllowedDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      chromeos::features::kFamilyLinkOnSchoolDevice);
+
   base::Value default_value(false);
   VerifyPolicyValue(kAccountsPrefFamilyLinkAccountsAllowed, &default_value);
 
+  // Family Link allowed with allowlist set, but the feature is disabled.
   SetDeviceFamilyLinkAccountsAllowed(true);
+  AddUserToAllowlist("*@managedchrome.com");
+  EXPECT_EQ(base::Value(false),
+            *provider_->Get(kAccountsPrefFamilyLinkAccountsAllowed));
+}
+
+// Tests DeviceFamilyLinkAccountsAllowed policy with the feature enabled.
+TEST_F(DeviceSettingsProviderTest, DeviceFamilyLinkAccountsAllowedEnabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      chromeos::features::kFamilyLinkOnSchoolDevice);
+
+  base::Value default_value(false);
+  VerifyPolicyValue(kAccountsPrefFamilyLinkAccountsAllowed, &default_value);
+
+  // Family Link allowed, but no allowlist set.
+  SetDeviceFamilyLinkAccountsAllowed(true);
+  EXPECT_EQ(base::Value(false),
+            *provider_->Get(kAccountsPrefFamilyLinkAccountsAllowed));
+
+  // Family Link allowed with allowlist set.
+  AddUserToAllowlist("*@managedchrome.com");
   EXPECT_EQ(base::Value(true),
             *provider_->Get(kAccountsPrefFamilyLinkAccountsAllowed));
 
+  // Family Link disallowed with allowlist set.
   SetDeviceFamilyLinkAccountsAllowed(false);
   EXPECT_EQ(base::Value(false),
             *provider_->Get(kAccountsPrefFamilyLinkAccountsAllowed));
diff --git a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
index 9c577ba..d91ca1b 100644
--- a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
+++ b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
@@ -1411,8 +1411,18 @@
   showAllowlistCheckFailedError(show, opt_data) {
     if (show) {
       const isManaged = opt_data && opt_data.enterpriseManaged;
-      this.$['gaia-allowlist-error'].textContent = loadTimeData.getValue(
-          isManaged ? 'allowlistErrorEnterprise' : 'allowlistErrorConsumer');
+      const isFamilyLinkAllowed = opt_data && opt_data.familyLinkAllowed;
+      errorMessage = '';
+      if (isManaged && isFamilyLinkAllowed) {
+        errorMessage = 'allowlistErrorEnterpriseAndFamilyLink';
+      } else if (isManaged) {
+        errorMessage = 'allowlistErrorEnterprise';
+      } else {
+        errorMessage = 'allowlistErrorConsumer';
+      }
+
+      this.$['gaia-allowlist-error'].textContent =
+          loadTimeData.getValue(errorMessage);
       // To make animations correct, we need to make sure Gaia is completely
       // reloaded. Otherwise ChromeOS overlays hide and Gaia page is shown
       // somewhere in the middle of animations.
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index 4263b3d..4a6dbf3 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -214,6 +214,18 @@
     params->SetString("flow", "nosignup");
 }
 
+bool ShouldCheckUserTypeBeforeAllowing() {
+  if (!chromeos::features::IsFamilyLinkOnSchoolDeviceEnabled())
+    return false;
+
+  CrosSettings* cros_settings = CrosSettings::Get();
+  bool family_link_allowed = false;
+  cros_settings->GetBoolean(kAccountsPrefFamilyLinkAccountsAllowed,
+                            &family_link_allowed);
+
+  return family_link_allowed;
+}
+
 void RecordSAMLScrapingVerificationResultInHistogram(bool success) {
   UMA_HISTOGRAM_BOOLEAN("ChromeOS.SAML.Scraping.VerificationResult", success);
 }
@@ -631,6 +643,8 @@
   builder->Add("allowlistErrorConsumer", IDS_LOGIN_ERROR_ALLOWLIST);
   builder->Add("allowlistErrorEnterprise",
                IDS_ENTERPRISE_LOGIN_ERROR_ALLOWLIST);
+  builder->Add("allowlistErrorEnterpriseAndFamilyLink",
+               IDS_ENTERPRISE_AND_FAMILY_LINK_LOGIN_ERROR_ALLOWLIST);
   builder->Add("tryAgainButton", IDS_ALLOWLIST_ERROR_TRY_AGAIN_BUTTON);
   builder->Add("learnMoreButton", IDS_LEARN_MORE);
   builder->Add("gaiaLoading", IDS_LOGIN_GAIA_LOADING_MESSAGE);
@@ -772,10 +786,16 @@
 }
 
 void GaiaScreenHandler::HandleIdentifierEntered(const std::string& user_email) {
+  // We cannot tell a user type from the identifier, so we delay checking if
+  // the account should be allowed.
+  if (ShouldCheckUserTypeBeforeAllowing())
+    return;
+
   if (LoginDisplayHost::default_host() &&
       !LoginDisplayHost::default_host()->IsUserAllowlisted(
           user_manager::known_user::GetAccountId(
-              user_email, std::string() /* id */, AccountType::UNKNOWN))) {
+              user_email, std::string() /* id */, AccountType::UNKNOWN),
+          base::nullopt)) {
     ShowAllowlistCheckFailedError();
   }
 }
@@ -919,6 +939,17 @@
 
   DCHECK(!email.empty());
   DCHECK(!gaia_id.empty());
+
+  // Execute delayed allowlist check that is based on user type.
+  const user_manager::UserType user_type =
+      GetUsertypeFromServicesString(services);
+  if (ShouldCheckUserTypeBeforeAllowing() &&
+      !LoginDisplayHost::default_host()->IsUserAllowlisted(
+          GetAccountId(email, gaia_id, AccountType::GOOGLE), user_type)) {
+    ShowAllowlistCheckFailedError();
+    return;
+  }
+
   const std::string sanitized_email = gaia::SanitizeEmail(email);
   LoginDisplayHost::default_host()->SetDisplayEmail(sanitized_email);
 
@@ -1519,6 +1550,12 @@
                     g_browser_process->platform_part()
                         ->browser_policy_connector_chromeos()
                         ->IsEnterpriseManaged());
+
+  bool family_link_allowed = false;
+  CrosSettings::Get()->GetBoolean(kAccountsPrefFamilyLinkAccountsAllowed,
+                                  &family_link_allowed);
+  params.SetBoolean("familyLinkAllowed", family_link_allowed);
+
   CallJS("login.GaiaSigninScreen.showAllowlistCheckFailedError", true, params);
 }
 
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 4ab2e2c..5a0afd2c 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -256,6 +256,11 @@
 const base::Feature kExoPointerLock{"ExoPointerLock",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables policy that controls feature to allow Family Link accounts on school
+// owned devices.
+const base::Feature kFamilyLinkOnSchoolDevice{
+    "FamilyLinkOnSchoolDevice", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables the camera folder handling in files app.
 const base::Feature kFilesCameraFolder{"FilesCameraFolder",
                                        base::FEATURE_ENABLED_BY_DEFAULT};
@@ -653,6 +658,10 @@
   return base::FeatureList::IsEnabled(kDiagnosticsApp);
 }
 
+bool IsFamilyLinkOnSchoolDeviceEnabled() {
+  return base::FeatureList::IsEnabled(kFamilyLinkOnSchoolDevice);
+}
+
 bool IsImeSandboxEnabled() {
   return base::FeatureList::IsEnabled(kEnableImeSandbox);
 }
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 1ef3278..96e5e6b 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -126,6 +126,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kExoPointerLock;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kFamilyLinkOnSchoolDevice;
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kFilesCameraFolder;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kFilesNG;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
@@ -290,6 +292,7 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsChildSpecificSigninEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsDeepLinkingEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsDiagnosticsAppEnabled();
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsFamilyLinkOnSchoolDeviceEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsImeSandboxEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 bool IsInstantTetheringBackgroundAdvertisingSupported();
diff --git a/chromeos/login/auth/login_performer.cc b/chromeos/login/auth/login_performer.cc
index 012297a1..b267434 100644
--- a/chromeos/login/auth/login_performer.cc
+++ b/chromeos/login/auth/login_performer.cc
@@ -142,7 +142,8 @@
   bool wildcard_match = false;
 
   const AccountId& account_id = user_context.GetAccountId();
-  if (!IsUserAllowlisted(account_id, &wildcard_match)) {
+  if (!IsUserAllowlisted(account_id, &wildcard_match,
+                         user_context.GetUserType())) {
     NotifyAllowlistCheckFailure();
     return;
   }
diff --git a/chromeos/login/auth/login_performer.h b/chromeos/login/auth/login_performer.h
index 2c33d9aa..7b1ff222 100644
--- a/chromeos/login/auth/login_performer.h
+++ b/chromeos/login/auth/login_performer.h
@@ -13,10 +13,12 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "chromeos/login/auth/auth_status_consumer.h"
 #include "chromeos/login/auth/authenticator.h"
 #include "chromeos/login/auth/extended_authenticator.h"
 #include "chromeos/login/auth/user_context.h"
+#include "components/user_manager/user_type.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 
 class AccountId;
@@ -129,9 +131,13 @@
 
   // Check if user is allowed to sign in on device. |wildcard_match| will
   // contain additional information whether this user is explicitly listed or
-  // not (may be relevant for external-based sign-in).
-  virtual bool IsUserAllowlisted(const AccountId& account_id,
-                                 bool* wildcard_match) = 0;
+  // not (may be relevant for external-based sign-in). |user_type| will be used
+  // to check if the user is allowed because of the user type, pass
+  // base::nullopt if user type is not known.
+  virtual bool IsUserAllowlisted(
+      const AccountId& account_id,
+      bool* wildcard_match,
+      const base::Optional<user_manager::UserType>& user_type) = 0;
 
  protected:
   // Platform-dependant methods to be implemented by concrete class.