[go: nahoru, domu]

blob: 1f4f88c4e62778874cd5866afe6430284183ccc6 [file] [log] [blame]
xiaoyinh2bbdd102017-05-18 23:29:421// Copyright 2017 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Jacob Dufaultffd9b0d2017-11-15 23:07:165#include "ash/login/login_screen_controller.h"
xiaoyinh2bbdd102017-05-18 23:29:426
Toni Barzicf61c4452017-10-05 03:57:487#include "ash/login/lock_screen_apps_focus_observer.h"
jdufaulteb4c9f1e2017-06-08 23:08:308#include "ash/login/ui/lock_screen.h"
Jacob Dufault40623d52017-09-15 17:22:539#include "ash/login/ui/login_data_dispatcher.h"
Sarah Hu069eea12017-09-08 01:28:4010#include "ash/public/cpp/ash_pref_names.h"
Aga Wronska16abb432018-01-11 23:49:5911#include "ash/root_window_controller.h"
Sarah Hu069eea12017-09-08 01:28:4012#include "ash/session/session_controller.h"
13#include "ash/shell.h"
Aga Wronska16abb432018-01-11 23:49:5914#include "ash/system/status_area_widget.h"
Sarah Hu069eea12017-09-08 01:28:4015#include "base/strings/string_number_conversions.h"
Jialiu Linf99b788b2018-01-17 23:01:2116#include "base/strings/utf_string_conversions.h"
xiaoyinh2bbdd102017-05-18 23:29:4217#include "chromeos/cryptohome/system_salt_getter.h"
Roman Sorokinc5590012017-09-28 00:48:2918#include "chromeos/login/auth/authpolicy_login_helper.h"
xiaoyinh2bbdd102017-05-18 23:29:4219#include "chromeos/login/auth/user_context.h"
Jialiu Linf99b788b2018-01-17 23:01:2120#include "components/password_manager/core/browser/hash_password_manager.h"
Sarah Hu069eea12017-09-08 01:28:4021#include "components/prefs/pref_registry_simple.h"
22#include "components/prefs/pref_service.h"
Jacob Dufault957e0922017-12-06 19:16:0923#include "components/session_manager/session_manager_types.h"
xiaoyinh2bbdd102017-05-18 23:29:4224
25namespace ash {
26
Sarah Hu069eea12017-09-08 01:28:4027namespace {
xiaoyinh2bbdd102017-05-18 23:29:4228
Sarah Hu069eea12017-09-08 01:28:4029std::string CalculateHash(const std::string& password,
30 const std::string& salt,
31 chromeos::Key::KeyType key_type) {
32 chromeos::Key key(password);
33 key.Transform(key_type, salt);
34 return key.GetSecret();
35}
36
Aga Wronskaa844cdcd12018-01-29 16:06:4437enum class SystemTrayVisibility {
38 kNone, // Tray not visible anywhere.
39 kPrimary, // Tray visible only on primary display.
40 kAll, // Tray visible on all displays.
41};
42
43void SetSystemTrayVisibility(SystemTrayVisibility visibility) {
44 RootWindowController* primary_window_controller =
45 Shell::GetPrimaryRootWindowController();
46 for (RootWindowController* window_controller :
47 Shell::GetAllRootWindowControllers()) {
48 StatusAreaWidget* status_area = window_controller->GetStatusAreaWidget();
49 if (!status_area)
50 continue;
51 if (window_controller == primary_window_controller) {
52 status_area->SetSystemTrayVisibility(
53 visibility == SystemTrayVisibility::kPrimary ||
54 visibility == SystemTrayVisibility::kAll);
55 } else {
56 status_area->SetSystemTrayVisibility(visibility ==
57 SystemTrayVisibility::kAll);
58 }
59 }
Aga Wronska16abb432018-01-11 23:49:5960}
61
Sarah Hu069eea12017-09-08 01:28:4062} // namespace
63
James Cookede316a2017-12-14 22:38:4364LoginScreenController::LoginScreenController() : weak_factory_(this) {}
James Cook8f1e6062017-11-13 23:40:5965
Jacob Dufaultffd9b0d2017-11-15 23:07:1666LoginScreenController::~LoginScreenController() = default;
xiaoyinh2bbdd102017-05-18 23:29:4267
Sarah Hu069eea12017-09-08 01:28:4068// static
Jacob Dufaultffd9b0d2017-11-15 23:07:1669void LoginScreenController::RegisterProfilePrefs(PrefRegistrySimple* registry,
70 bool for_test) {
Sarah Hu069eea12017-09-08 01:28:4071 if (for_test) {
72 // There is no remote pref service, so pretend that ash owns the pref.
73 registry->RegisterStringPref(prefs::kQuickUnlockPinSalt, "");
74 return;
75 }
76
77 // Pref is owned by chrome and flagged as PUBLIC.
78 registry->RegisterForeignPref(prefs::kQuickUnlockPinSalt);
79}
80
Jacob Dufaultffd9b0d2017-11-15 23:07:1681void LoginScreenController::BindRequest(mojom::LoginScreenRequest request) {
James Cookede316a2017-12-14 22:38:4382 bindings_.AddBinding(this, std::move(request));
xiaoyinh2bbdd102017-05-18 23:29:4283}
84
Jacob Dufaultffd9b0d2017-11-15 23:07:1685void LoginScreenController::SetClient(mojom::LoginScreenClientPtr client) {
86 login_screen_client_ = std::move(client);
xiaoyinh2bbdd102017-05-18 23:29:4287}
88
Jacob Dufaultffd9b0d2017-11-15 23:07:1689void LoginScreenController::ShowLockScreen(ShowLockScreenCallback on_shown) {
Jacob Dufaultcbc1ee02018-02-28 18:38:5490 OnShow();
Jacob Dufault957e0922017-12-06 19:16:0991 ash::LockScreen::Show(ash::LockScreen::ScreenType::kLock);
92 std::move(on_shown).Run(true);
93}
94
95void LoginScreenController::ShowLoginScreen(ShowLoginScreenCallback on_shown) {
96 // Login screen can only be used during login.
97 if (Shell::Get()->session_controller()->GetSessionState() !=
98 session_manager::SessionState::LOGIN_PRIMARY) {
99 std::move(on_shown).Run(false);
100 return;
101 }
102
Jacob Dufaultcbc1ee02018-02-28 18:38:54103 OnShow();
Jacob Dufault957e0922017-12-06 19:16:09104 // TODO(jdufault): rename ash::LockScreen to ash::LoginScreen.
105 ash::LockScreen::Show(ash::LockScreen::ScreenType::kLogin);
jdufaulteb4c9f1e2017-06-08 23:08:30106 std::move(on_shown).Run(true);
107}
108
Jacob Dufaultffd9b0d2017-11-15 23:07:16109void LoginScreenController::ShowErrorMessage(int32_t login_attempts,
110 const std::string& error_text,
111 const std::string& help_link_text,
112 int32_t help_topic_id) {
xiaoyinh2bbdd102017-05-18 23:29:42113 NOTIMPLEMENTED();
114}
115
Jacob Dufaultffd9b0d2017-11-15 23:07:16116void LoginScreenController::ClearErrors() {
xiaoyinh2bbdd102017-05-18 23:29:42117 NOTIMPLEMENTED();
118}
119
Jacob Dufaultffd9b0d2017-11-15 23:07:16120void LoginScreenController::ShowUserPodCustomIcon(
xiaoyinh9f6fa0e2017-06-07 19:22:32121 const AccountId& account_id,
Jacob Dufaultc5738ca2017-10-16 23:18:16122 mojom::EasyUnlockIconOptionsPtr icon) {
Jacob Dufaulta0225592017-10-17 21:53:38123 DataDispatcher()->ShowEasyUnlockIcon(account_id, icon);
xiaoyinh9f6fa0e2017-06-07 19:22:32124}
125
Jacob Dufaultffd9b0d2017-11-15 23:07:16126void LoginScreenController::HideUserPodCustomIcon(const AccountId& account_id) {
Jacob Dufaulta0225592017-10-17 21:53:38127 auto icon_options = mojom::EasyUnlockIconOptions::New();
128 icon_options->icon = mojom::EasyUnlockIconId::NONE;
129 DataDispatcher()->ShowEasyUnlockIcon(account_id, icon_options);
xiaoyinh9f6fa0e2017-06-07 19:22:32130}
131
Jacob Dufaultffd9b0d2017-11-15 23:07:16132void LoginScreenController::SetAuthType(
xiaoyinh820778c52017-06-21 01:42:51133 const AccountId& account_id,
134 proximity_auth::mojom::AuthType auth_type,
135 const base::string16& initial_value) {
Jacob Dufaulta0225592017-10-17 21:53:38136 if (auth_type == proximity_auth::mojom::AuthType::USER_CLICK) {
137 DataDispatcher()->SetClickToUnlockEnabledForUser(account_id,
138 true /*enabled*/);
139 } else {
140 NOTIMPLEMENTED();
141 }
xiaoyinh9f6fa0e2017-06-07 19:22:32142}
143
Jacob Dufaultffd9b0d2017-11-15 23:07:16144void LoginScreenController::LoadUsers(
145 std::vector<mojom::LoginUserInfoPtr> users,
146 bool show_guest) {
Jacob Dufault40623d52017-09-15 17:22:53147 DCHECK(DataDispatcher());
148
Sarah Huf3a99dd02017-10-03 22:04:11149 DataDispatcher()->NotifyUsers(users);
xiaoyinh9f6fa0e2017-06-07 19:22:32150}
151
Jacob Dufaultffd9b0d2017-11-15 23:07:16152void LoginScreenController::SetPinEnabledForUser(const AccountId& account_id,
153 bool is_enabled) {
Sarah Hu069eea12017-09-08 01:28:40154 // Chrome will update pin pod state every time user tries to authenticate.
155 // LockScreen is destroyed in the case of authentication success.
Jacob Dufault40623d52017-09-15 17:22:53156 if (DataDispatcher())
157 DataDispatcher()->SetPinEnabledForUser(account_id, is_enabled);
xiaoyinhf534c4f2017-06-13 20:50:27158}
159
Wenzhao Zanga05dcefc2017-11-30 05:50:03160void LoginScreenController::SetDevChannelInfo(
161 const std::string& os_version_label_text,
162 const std::string& enterprise_info_text,
163 const std::string& bluetooth_name) {
164 if (DataDispatcher()) {
165 DataDispatcher()->SetDevChannelInfo(os_version_label_text,
166 enterprise_info_text, bluetooth_name);
167 }
168}
169
Sarah Hu0bfd1872017-12-12 18:00:05170void LoginScreenController::IsReadyForPassword(
171 IsReadyForPasswordCallback callback) {
172 std::move(callback).Run(LockScreen::IsShown() && !is_authenticating_);
173}
174
Sarah Huf4cbba82018-03-07 01:34:12175void LoginScreenController::SetPublicSessionDisplayName(
176 const AccountId& account_id,
177 const std::string& display_name) {
178 if (DataDispatcher())
179 DataDispatcher()->SetPublicSessionDisplayName(account_id, display_name);
180}
181
182void LoginScreenController::SetPublicSessionLocales(
183 const AccountId& account_id,
184 std::unique_ptr<base::ListValue> locales,
185 const std::string& default_locale,
186 bool show_advanced_view) {
187 if (DataDispatcher()) {
188 DataDispatcher()->SetPublicSessionLocales(
189 account_id, std::move(locales), default_locale, show_advanced_view);
190 }
191}
192
Jacob Dufaultb7a2d842017-12-01 23:21:15193void LoginScreenController::AuthenticateUser(const AccountId& account_id,
194 const std::string& password,
195 bool authenticated_by_pin,
196 OnAuthenticateCallback callback) {
197 // Ignore concurrent auth attempts. This can happen if the user quickly enters
198 // two separate passwords and hits enter.
199 if (!login_screen_client_ || is_authenticating_) {
200 LOG_IF(ERROR, is_authenticating_) << "Ignoring concurrent auth attempt";
201 std::move(callback).Run(base::nullopt);
xiaoyinh9f6fa0e2017-06-07 19:22:32202 return;
Jacob Dufaultb7a2d842017-12-01 23:21:15203 }
204 is_authenticating_ = true;
xiaoyinh9f6fa0e2017-06-07 19:22:32205
Jacob Dufaulteafc6fe2017-10-11 21:16:52206 // If auth is disabled by the debug overlay bypass the mojo call entirely, as
207 // it will dismiss the lock screen if the password is correct.
Jacob Dufault0fbed9c02017-11-14 19:22:24208 switch (force_fail_auth_for_debug_overlay_) {
209 case ForceFailAuth::kOff:
210 break;
211 case ForceFailAuth::kImmediate:
Jacob Dufaultb7a2d842017-12-01 23:21:15212 OnAuthenticateComplete(std::move(callback), false /*success*/);
Jacob Dufault0fbed9c02017-11-14 19:22:24213 return;
214 case ForceFailAuth::kDelayed:
215 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
Jacob Dufaultb7a2d842017-12-01 23:21:15216 FROM_HERE,
217 base::BindOnce(&LoginScreenController::OnAuthenticateComplete,
218 weak_factory_.GetWeakPtr(), base::Passed(&callback),
219 false),
Jacob Dufault0fbed9c02017-11-14 19:22:24220 base::TimeDelta::FromSeconds(1));
221 return;
Jacob Dufaulteafc6fe2017-10-11 21:16:52222 }
223
Jacob Dufaultb7a2d842017-12-01 23:21:15224 // |DoAuthenticateUser| requires the system salt, so we fetch it first, and
225 // then run |DoAuthenticateUser| as a continuation.
226 auto do_authenticate = base::BindOnce(
227 &LoginScreenController::DoAuthenticateUser, weak_factory_.GetWeakPtr(),
jdufaulteb4c9f1e2017-06-08 23:08:30228 account_id, password, authenticated_by_pin, std::move(callback));
Jacob Dufaultb7a2d842017-12-01 23:21:15229 chromeos::SystemSaltGetter::Get()->GetSystemSalt(base::BindRepeating(
230 &LoginScreenController::OnGetSystemSalt, weak_factory_.GetWeakPtr(),
231 base::Passed(&do_authenticate)));
xiaoyinh9f6fa0e2017-06-07 19:22:32232}
233
Jacob Dufaultffd9b0d2017-11-15 23:07:16234void LoginScreenController::HandleFocusLeavingLockScreenApps(bool reverse) {
Toni Barzicf61c4452017-10-05 03:57:48235 for (auto& observer : lock_screen_apps_focus_observers_)
236 observer.OnFocusLeavingLockScreenApps(reverse);
237}
238
Jacob Dufaultffd9b0d2017-11-15 23:07:16239void LoginScreenController::AttemptUnlock(const AccountId& account_id) {
240 if (!login_screen_client_)
xiaoyinh9f6fa0e2017-06-07 19:22:32241 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16242 login_screen_client_->AttemptUnlock(account_id);
Sarah Hue0e01a52017-10-25 20:29:30243
244 Shell::Get()->metrics()->login_metrics_recorder()->SetAuthMethod(
245 LoginMetricsRecorder::AuthMethod::kSmartlock);
xiaoyinh9f6fa0e2017-06-07 19:22:32246}
247
Jacob Dufaultffd9b0d2017-11-15 23:07:16248void LoginScreenController::HardlockPod(const AccountId& account_id) {
249 if (!login_screen_client_)
xiaoyinh9f6fa0e2017-06-07 19:22:32250 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16251 login_screen_client_->HardlockPod(account_id);
xiaoyinh9f6fa0e2017-06-07 19:22:32252}
253
Jacob Dufaultffd9b0d2017-11-15 23:07:16254void LoginScreenController::RecordClickOnLockIcon(const AccountId& account_id) {
255 if (!login_screen_client_)
xiaoyinh9f6fa0e2017-06-07 19:22:32256 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16257 login_screen_client_->RecordClickOnLockIcon(account_id);
xiaoyinh9f6fa0e2017-06-07 19:22:32258}
259
Jacob Dufaultffd9b0d2017-11-15 23:07:16260void LoginScreenController::OnFocusPod(const AccountId& account_id) {
261 if (!login_screen_client_)
xiaoyinhf534c4f2017-06-13 20:50:27262 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16263 login_screen_client_->OnFocusPod(account_id);
xiaoyinhf534c4f2017-06-13 20:50:27264}
265
Jacob Dufaultffd9b0d2017-11-15 23:07:16266void LoginScreenController::OnNoPodFocused() {
267 if (!login_screen_client_)
xiaoyinhf534c4f2017-06-13 20:50:27268 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16269 login_screen_client_->OnNoPodFocused();
xiaoyinhf534c4f2017-06-13 20:50:27270}
271
Jacob Dufaultffd9b0d2017-11-15 23:07:16272void LoginScreenController::LoadWallpaper(const AccountId& account_id) {
273 if (!login_screen_client_)
xiaoyinhf534c4f2017-06-13 20:50:27274 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16275 login_screen_client_->LoadWallpaper(account_id);
xiaoyinhf534c4f2017-06-13 20:50:27276}
277
Jacob Dufaultffd9b0d2017-11-15 23:07:16278void LoginScreenController::SignOutUser() {
279 if (!login_screen_client_)
xiaoyinhf534c4f2017-06-13 20:50:27280 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16281 login_screen_client_->SignOutUser();
xiaoyinhf534c4f2017-06-13 20:50:27282}
283
Jacob Dufaultffd9b0d2017-11-15 23:07:16284void LoginScreenController::CancelAddUser() {
285 if (!login_screen_client_)
Wenzhao Zang16e7ea722017-09-16 01:27:30286 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16287 login_screen_client_->CancelAddUser();
Wenzhao Zang16e7ea722017-09-16 01:27:30288}
289
Aga Wronska6a32f9872018-01-06 00:16:10290void LoginScreenController::LoginAsGuest() {
291 if (!login_screen_client_)
292 return;
293 login_screen_client_->LoginAsGuest();
294}
295
Jacob Dufaultffd9b0d2017-11-15 23:07:16296void LoginScreenController::OnMaxIncorrectPasswordAttempted(
xiaoyinhf534c4f2017-06-13 20:50:27297 const AccountId& account_id) {
Jacob Dufaultffd9b0d2017-11-15 23:07:16298 if (!login_screen_client_)
xiaoyinhf534c4f2017-06-13 20:50:27299 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16300 login_screen_client_->OnMaxIncorrectPasswordAttempted(account_id);
xiaoyinhf534c4f2017-06-13 20:50:27301}
302
Jacob Dufaultffd9b0d2017-11-15 23:07:16303void LoginScreenController::FocusLockScreenApps(bool reverse) {
304 if (!login_screen_client_)
Toni Barzicf61c4452017-10-05 03:57:48305 return;
Jacob Dufaultffd9b0d2017-11-15 23:07:16306 login_screen_client_->FocusLockScreenApps(reverse);
Toni Barzicf61c4452017-10-05 03:57:48307}
308
Sarah Hu9fba0e752018-02-07 01:41:09309void LoginScreenController::ShowGaiaSignin() {
310 if (!login_screen_client_)
311 return;
312 login_screen_client_->ShowGaiaSignin();
313}
314
Jacob Dufaultfc31c742018-03-20 17:32:19315void LoginScreenController::OnRemoveUserWarningShown() {
316 if (!login_screen_client_)
317 return;
318 login_screen_client_->OnRemoveUserWarningShown();
319}
320
321void LoginScreenController::RemoveUser(const AccountId& account_id) {
322 if (!login_screen_client_)
323 return;
324 login_screen_client_->RemoveUser(account_id);
325}
326
Jacob Dufaultffd9b0d2017-11-15 23:07:16327void LoginScreenController::AddLockScreenAppsFocusObserver(
Toni Barzicf61c4452017-10-05 03:57:48328 LockScreenAppsFocusObserver* observer) {
329 lock_screen_apps_focus_observers_.AddObserver(observer);
330}
331
Jacob Dufaultffd9b0d2017-11-15 23:07:16332void LoginScreenController::RemoveLockScreenAppsFocusObserver(
Toni Barzicf61c4452017-10-05 03:57:48333 LockScreenAppsFocusObserver* observer) {
334 lock_screen_apps_focus_observers_.RemoveObserver(observer);
335}
336
Jacob Dufaultffd9b0d2017-11-15 23:07:16337void LoginScreenController::FlushForTesting() {
338 login_screen_client_.FlushForTesting();
Toni Barzicf61c4452017-10-05 03:57:48339}
340
Jacob Dufaultb7a2d842017-12-01 23:21:15341void LoginScreenController::DoAuthenticateUser(const AccountId& account_id,
342 const std::string& password,
343 bool authenticated_by_pin,
344 OnAuthenticateCallback callback,
345 const std::string& system_salt) {
Sarah Hu069eea12017-09-08 01:28:40346 int dummy_value;
347 bool is_pin =
348 authenticated_by_pin && base::StringToInt(password, &dummy_value);
349 std::string hashed_password = CalculateHash(
350 password, system_salt, chromeos::Key::KEY_TYPE_SALTED_SHA256_TOP_HALF);
351
Jialiu Linf99b788b2018-01-17 23:01:21352 // Used for GAIA password reuse detection.
353 password_manager::SyncPasswordData sync_password_data(
354 base::UTF8ToUTF16(password), /*force_update=*/false);
355
Sarah Hu069eea12017-09-08 01:28:40356 PrefService* prefs =
357 Shell::Get()->session_controller()->GetLastActiveUserPrefService();
358 if (is_pin && prefs) {
359 hashed_password =
360 CalculateHash(password, prefs->GetString(prefs::kQuickUnlockPinSalt),
361 chromeos::Key::KEY_TYPE_SALTED_PBKDF2_AES256_1234);
362 }
363
Roman Sorokinc5590012017-09-28 00:48:29364 if (account_id.GetAccountType() == AccountType::ACTIVE_DIRECTORY && !is_pin) {
365 // Try to get kerberos TGT while we have user's password typed on the lock
366 // screen. Using invalid/bad password is fine. Failure to get TGT here is OK
367 // - that could mean e.g. Active Directory server is not reachable.
368 // AuthPolicyCredentialsManager regularly checks TGT status inside the user
369 // session.
370 chromeos::AuthPolicyLoginHelper::TryAuthenticateUser(
371 account_id.GetUserEmail(), account_id.GetObjGuid(), password);
372 }
373
Sarah Hue0e01a52017-10-25 20:29:30374 Shell::Get()->metrics()->login_metrics_recorder()->SetAuthMethod(
375 is_pin ? LoginMetricsRecorder::AuthMethod::kPin
376 : LoginMetricsRecorder::AuthMethod::kPassword);
Jacob Dufaultb7a2d842017-12-01 23:21:15377 login_screen_client_->AuthenticateUser(
Jialiu Linf99b788b2018-01-17 23:01:21378 account_id, hashed_password, sync_password_data, is_pin,
Jacob Dufaultb7a2d842017-12-01 23:21:15379 base::BindOnce(&LoginScreenController::OnAuthenticateComplete,
380 weak_factory_.GetWeakPtr(), base::Passed(&callback)));
jdufaulteb4c9f1e2017-06-08 23:08:30381}
382
Jacob Dufaultb7a2d842017-12-01 23:21:15383void LoginScreenController::OnAuthenticateComplete(
384 OnAuthenticateCallback callback,
385 bool success) {
386 is_authenticating_ = false;
387 std::move(callback).Run(success);
388}
389
390void LoginScreenController::OnGetSystemSalt(PendingDoAuthenticateUser then,
391 const std::string& system_salt) {
392 std::move(then).Run(system_salt);
xiaoyinh2bbdd102017-05-18 23:29:42393}
394
Jacob Dufaultffd9b0d2017-11-15 23:07:16395LoginDataDispatcher* LoginScreenController::DataDispatcher() const {
Jacob Dufault40623d52017-09-15 17:22:53396 if (!ash::LockScreen::IsShown())
397 return nullptr;
398 return ash::LockScreen::Get()->data_dispatcher();
399}
400
Jacob Dufaultcbc1ee02018-02-28 18:38:54401void LoginScreenController::OnShow() {
402 SetSystemTrayVisibility(SystemTrayVisibility::kPrimary);
403 is_authenticating_ = false;
404}
405
xiaoyinh2bbdd102017-05-18 23:29:42406} // namespace ash