1// Copyright 2014 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
5#include "chrome/browser/chromeos/policy/consumer_management_service.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/location.h"
10#include "base/logging.h"
11#include "base/message_loop/message_loop.h"
12#include "base/prefs/pref_registry_simple.h"
13#include "base/prefs/pref_service.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/time/time.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/browser_process_platform_part.h"
18#include "chrome/browser/chrome_notification_types.h"
19#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
20#include "chrome/browser/chromeos/policy/device_cloud_policy_initializer.h"
21#include "chrome/browser/chromeos/policy/enrollment_status_chromeos.h"
22#include "chrome/browser/chromeos/profiles/profile_helper.h"
23#include "chrome/browser/notifications/notification.h"
24#include "chrome/browser/notifications/notification_delegate.h"
25#include "chrome/browser/notifications/notification_ui_manager.h"
26#include "chrome/browser/profiles/profile.h"
27#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
28#include "chrome/browser/signin/signin_manager_factory.h"
29#include "chrome/browser/ui/browser_navigator.h"
30#include "chrome/common/pref_names.h"
31#include "chrome/common/url_constants.h"
32#include "chromeos/dbus/cryptohome/rpc.pb.h"
33#include "chromeos/dbus/cryptohome_client.h"
34#include "components/policy/core/common/cloud/cloud_policy_constants.h"
35#include "components/signin/core/browser/profile_oauth2_token_service.h"
36#include "components/signin/core/browser/signin_manager_base.h"
37#include "components/user_manager/user_manager.h"
38#include "content/public/browser/notification_details.h"
39#include "content/public/browser/notification_service.h"
40#include "content/public/browser/notification_source.h"
41#include "google_apis/gaia/gaia_constants.h"
42#include "google_apis/gaia/google_service_auth_error.h"
43#include "grit/generated_resources.h"
44#include "grit/theme_resources.h"
45#include "policy/proto/device_management_backend.pb.h"
46#include "third_party/WebKit/public/web/WebTextDirection.h"
47#include "ui/base/l10n/l10n_util.h"
48#include "ui/base/page_transition_types.h"
49#include "ui/base/resource/resource_bundle.h"
50#include "ui/base/window_open_disposition.h"
51#include "ui/message_center/notification.h"
52#include "ui/message_center/notification_types.h"
53#include "ui/message_center/notifier_settings.h"
54#include "url/gurl.h"
55
56namespace {
57
58// Boot atttributes ID.
59const char kAttributeOwnerId[] = "consumer_management.owner_id";
60
61// Desktop notification constants.
62const char kEnrollmentNotificationId[] = "consumer_management.enroll";
63const char kEnrollmentNotificationUrl[] = "chrome://consumer-management/enroll";
64
65// The path to the consumer management enrollment/unenrollment confirmation
66// overlay, relative to the settings page URL.
67const char kConsumerManagementOverlay[] = "consumer-management-overlay";
68
69  // Returns the account ID signed in to |profile|.
70const std::string& GetAccountIdFromProfile(Profile* profile) {
71  return SigninManagerFactory::GetForProfile(profile)->
72      GetAuthenticatedAccountId();
73}
74
75class DesktopNotificationDelegate : public NotificationDelegate {
76 public:
77  // |button_click_callback| is called when the button in the notification is
78  // clicked.
79  DesktopNotificationDelegate(const std::string& id,
80                              const base::Closure& button_click_callback);
81
82  // NotificationDelegate:
83  virtual std::string id() const OVERRIDE;
84  virtual content::WebContents* GetWebContents() const OVERRIDE;
85  virtual void Display() OVERRIDE;
86  virtual void ButtonClick(int button_index) OVERRIDE;
87  virtual void Error() OVERRIDE;
88  virtual void Close(bool by_user) OVERRIDE;
89  virtual void Click() OVERRIDE;
90
91 private:
92  virtual ~DesktopNotificationDelegate();
93
94  std::string id_;
95  base::Closure button_click_callback_;
96
97  DISALLOW_COPY_AND_ASSIGN(DesktopNotificationDelegate);
98};
99
100DesktopNotificationDelegate::DesktopNotificationDelegate(
101    const std::string& id,
102    const base::Closure& button_click_callback)
103    : id_(id), button_click_callback_(button_click_callback) {
104}
105
106DesktopNotificationDelegate::~DesktopNotificationDelegate() {
107}
108
109std::string DesktopNotificationDelegate::id() const {
110  return id_;
111}
112
113content::WebContents* DesktopNotificationDelegate::GetWebContents() const {
114  return NULL;
115}
116
117void DesktopNotificationDelegate::Display() {
118}
119
120void DesktopNotificationDelegate::ButtonClick(int button_index) {
121  button_click_callback_.Run();
122}
123
124void DesktopNotificationDelegate::Error() {
125}
126
127void DesktopNotificationDelegate::Close(bool by_user) {
128}
129
130void DesktopNotificationDelegate::Click() {
131}
132
133// The string of Status enum.
134const char* kStatusString[] = {
135  "StatusUnknown",
136  "StatusEnrolled",
137  "StatusEnrolling",
138  "StatusUnenrolled",
139  "StatusUnenrolling",
140};
141
142COMPILE_ASSERT(
143    arraysize(kStatusString) == policy::ConsumerManagementService::STATUS_LAST,
144    "invalid kStatusString array size.");
145
146}  // namespace
147
148namespace em = enterprise_management;
149
150namespace policy {
151
152ConsumerManagementService::ConsumerManagementService(
153    chromeos::CryptohomeClient* client,
154    chromeos::DeviceSettingsService* device_settings_service)
155    : Consumer("consumer_management_service"),
156      client_(client),
157      device_settings_service_(device_settings_service),
158      enrolling_profile_(NULL),
159      weak_ptr_factory_(this) {
160  registrar_.Add(this,
161                 chrome::NOTIFICATION_LOGIN_USER_PROFILE_PREPARED,
162                 content::NotificationService::AllSources());
163  // A NULL value may be passed in tests.
164  if (device_settings_service_)
165    device_settings_service_->AddObserver(this);
166}
167
168ConsumerManagementService::~ConsumerManagementService() {
169  if (enrolling_profile_) {
170    ProfileOAuth2TokenServiceFactory::GetForProfile(enrolling_profile_)->
171        RemoveObserver(this);
172  }
173  if (device_settings_service_)
174    device_settings_service_->RemoveObserver(this);
175  registrar_.Remove(this,
176                    chrome::NOTIFICATION_LOGIN_USER_PROFILE_PREPARED,
177                    content::NotificationService::AllSources());
178}
179
180// static
181void ConsumerManagementService::RegisterPrefs(PrefRegistrySimple* registry) {
182  registry->RegisterIntegerPref(
183      prefs::kConsumerManagementEnrollmentStage, ENROLLMENT_STAGE_NONE);
184}
185
186void ConsumerManagementService::AddObserver(Observer* observer) {
187  observers_.AddObserver(observer);
188}
189
190void ConsumerManagementService::RemoveObserver(Observer* observer) {
191  observers_.RemoveObserver(observer);
192}
193
194ConsumerManagementService::Status
195ConsumerManagementService::GetStatus() const {
196  if (!device_settings_service_)
197    return STATUS_UNKNOWN;
198
199  const enterprise_management::PolicyData* policy_data =
200      device_settings_service_->policy_data();
201  if (!policy_data)
202    return STATUS_UNKNOWN;
203
204  if (policy_data->management_mode() == em::PolicyData::CONSUMER_MANAGED) {
205    // TODO(davidyu): Check if unenrollment is in progress.
206    // http://crbug.com/353050.
207    return STATUS_ENROLLED;
208  }
209
210  EnrollmentStage stage = GetEnrollmentStage();
211  if (stage > ENROLLMENT_STAGE_NONE && stage < ENROLLMENT_STAGE_SUCCESS)
212    return STATUS_ENROLLING;
213
214  return STATUS_UNENROLLED;
215}
216
217std::string ConsumerManagementService::GetStatusString() const {
218  return kStatusString[GetStatus()];
219}
220
221ConsumerManagementService::EnrollmentStage
222ConsumerManagementService::GetEnrollmentStage() const {
223  const PrefService* prefs = g_browser_process->local_state();
224  int stage = prefs->GetInteger(prefs::kConsumerManagementEnrollmentStage);
225  if (stage < 0 || stage >= ENROLLMENT_STAGE_LAST) {
226    LOG(ERROR) << "Unknown enrollment stage: " << stage;
227    stage = 0;
228  }
229  return static_cast<EnrollmentStage>(stage);
230}
231
232void ConsumerManagementService::SetEnrollmentStage(EnrollmentStage stage) {
233  PrefService* prefs = g_browser_process->local_state();
234  prefs->SetInteger(prefs::kConsumerManagementEnrollmentStage, stage);
235
236  NotifyStatusChanged();
237}
238
239void ConsumerManagementService::GetOwner(const GetOwnerCallback& callback) {
240  cryptohome::GetBootAttributeRequest request;
241  request.set_name(kAttributeOwnerId);
242  client_->GetBootAttribute(
243      request,
244      base::Bind(&ConsumerManagementService::OnGetBootAttributeDone,
245                 weak_ptr_factory_.GetWeakPtr(),
246                 callback));
247}
248
249void ConsumerManagementService::SetOwner(const std::string& user_id,
250                                         const SetOwnerCallback& callback) {
251  cryptohome::SetBootAttributeRequest request;
252  request.set_name(kAttributeOwnerId);
253  request.set_value(user_id.data(), user_id.size());
254  client_->SetBootAttribute(
255      request,
256      base::Bind(&ConsumerManagementService::OnSetBootAttributeDone,
257                 weak_ptr_factory_.GetWeakPtr(),
258                 callback));
259}
260
261void ConsumerManagementService::OwnershipStatusChanged() {
262}
263
264void ConsumerManagementService::DeviceSettingsUpdated() {
265  NotifyStatusChanged();
266}
267
268void ConsumerManagementService::Observe(
269    int type,
270    const content::NotificationSource& source,
271    const content::NotificationDetails& details) {
272  if (type != chrome::NOTIFICATION_LOGIN_USER_PROFILE_PREPARED) {
273    NOTREACHED() << "Unexpected notification " << type;
274    return;
275  }
276
277  Profile* profile = content::Details<Profile>(details).ptr();
278  if (chromeos::ProfileHelper::IsOwnerProfile(profile))
279    OnOwnerSignin(profile);
280}
281
282void ConsumerManagementService::OnRefreshTokenAvailable(
283    const std::string& account_id) {
284  CHECK(enrolling_profile_);
285
286  if (account_id == GetAccountIdFromProfile(enrolling_profile_)) {
287    ProfileOAuth2TokenServiceFactory::GetForProfile(enrolling_profile_)->
288        RemoveObserver(this);
289    OnOwnerRefreshTokenAvailable();
290  }
291}
292
293void ConsumerManagementService::OnGetTokenSuccess(
294      const OAuth2TokenService::Request* request,
295      const std::string& access_token,
296      const base::Time& expiration_time) {
297  DCHECK_EQ(token_request_, request);
298  base::MessageLoop::current()->DeleteSoon(FROM_HERE, token_request_.release());
299
300  OnOwnerAccessTokenAvailable(access_token);
301}
302
303void ConsumerManagementService::OnGetTokenFailure(
304      const OAuth2TokenService::Request* request,
305      const GoogleServiceAuthError& error) {
306  DCHECK_EQ(token_request_, request);
307  base::MessageLoop::current()->DeleteSoon(FROM_HERE, token_request_.release());
308
309  LOG(ERROR) << "Failed to get the access token: " << error.ToString();
310  EndEnrollment(ENROLLMENT_STAGE_GET_TOKEN_FAILED);
311}
312
313void ConsumerManagementService::OnGetBootAttributeDone(
314    const GetOwnerCallback& callback,
315    chromeos::DBusMethodCallStatus call_status,
316    bool dbus_success,
317    const cryptohome::BaseReply& reply) {
318  if (!dbus_success || reply.error() != 0) {
319    LOG(ERROR) << "Failed to get the owner info from boot lockbox.";
320    callback.Run("");
321    return;
322  }
323
324  callback.Run(
325      reply.GetExtension(cryptohome::GetBootAttributeReply::reply).value());
326}
327
328void ConsumerManagementService::OnSetBootAttributeDone(
329    const SetOwnerCallback& callback,
330    chromeos::DBusMethodCallStatus call_status,
331    bool dbus_success,
332    const cryptohome::BaseReply& reply) {
333  if (!dbus_success || reply.error() != 0) {
334    LOG(ERROR) << "Failed to set owner info in boot lockbox.";
335    callback.Run(false);
336    return;
337  }
338
339  cryptohome::FlushAndSignBootAttributesRequest request;
340  client_->FlushAndSignBootAttributes(
341      request,
342      base::Bind(&ConsumerManagementService::OnFlushAndSignBootAttributesDone,
343                 weak_ptr_factory_.GetWeakPtr(),
344                 callback));
345}
346
347void ConsumerManagementService::OnFlushAndSignBootAttributesDone(
348    const SetOwnerCallback& callback,
349    chromeos::DBusMethodCallStatus call_status,
350    bool dbus_success,
351    const cryptohome::BaseReply& reply) {
352  if (!dbus_success || reply.error() != 0) {
353    LOG(ERROR) << "Failed to flush and sign boot lockbox.";
354    callback.Run(false);
355    return;
356  }
357
358  callback.Run(true);
359}
360
361void ConsumerManagementService::OnOwnerSignin(Profile* profile) {
362  const EnrollmentStage stage = GetEnrollmentStage();
363  switch (stage) {
364    case ENROLLMENT_STAGE_NONE:
365      // Do nothing.
366      return;
367
368    case ENROLLMENT_STAGE_OWNER_STORED:
369      // Continue the enrollment process after the owner signs in.
370      ContinueEnrollmentProcess(profile);
371      return;
372
373    case ENROLLMENT_STAGE_SUCCESS:
374    case ENROLLMENT_STAGE_CANCELED:
375    case ENROLLMENT_STAGE_BOOT_LOCKBOX_FAILED:
376    case ENROLLMENT_STAGE_DM_SERVER_FAILED:
377    case ENROLLMENT_STAGE_GET_TOKEN_FAILED:
378      ShowDesktopNotificationAndResetStage(stage, profile);
379      return;
380
381    case ENROLLMENT_STAGE_REQUESTED:
382    case ENROLLMENT_STAGE_LAST:
383      NOTREACHED() << "Unexpected enrollment stage " << stage;
384      return;
385  }
386}
387
388void ConsumerManagementService::ContinueEnrollmentProcess(Profile* profile) {
389  enrolling_profile_ = profile;
390
391  // First, we need to ensure that the refresh token is available.
392  const std::string& account_id = GetAccountIdFromProfile(profile);
393  ProfileOAuth2TokenService* token_service =
394      ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
395  if (token_service->RefreshTokenIsAvailable(account_id)) {
396    OnOwnerRefreshTokenAvailable();
397  } else {
398    token_service->AddObserver(this);
399  }
400}
401
402void ConsumerManagementService::OnOwnerRefreshTokenAvailable() {
403  CHECK(enrolling_profile_);
404
405  // Now we can request the OAuth access token for device management to send the
406  // device registration request to the device management server.
407  OAuth2TokenService::ScopeSet oauth_scopes;
408  oauth_scopes.insert(GaiaConstants::kDeviceManagementServiceOAuth);
409  const std::string& account_id = GetAccountIdFromProfile(enrolling_profile_);
410  token_request_ = ProfileOAuth2TokenServiceFactory::GetForProfile(
411      enrolling_profile_)->StartRequest(account_id, oauth_scopes, this);
412}
413
414void ConsumerManagementService::OnOwnerAccessTokenAvailable(
415    const std::string& access_token) {
416  // Now that we have the access token, we got everything we need to send the
417  // device registration request to the device management server.
418  BrowserPolicyConnectorChromeOS* connector =
419      g_browser_process->platform_part()->browser_policy_connector_chromeos();
420  DeviceCloudPolicyInitializer* initializer =
421      connector->GetDeviceCloudPolicyInitializer();
422  CHECK(initializer);
423
424  policy::DeviceCloudPolicyInitializer::AllowedDeviceModes device_modes;
425  device_modes[policy::DEVICE_MODE_ENTERPRISE] = true;
426
427  initializer->StartEnrollment(
428      enterprise_management::PolicyData::ENTERPRISE_MANAGED,
429      connector->GetDeviceManagementServiceForConsumer(),
430      access_token,
431      false,  // is_auto_enrollment
432      device_modes,
433      base::Bind(&ConsumerManagementService::OnEnrollmentCompleted,
434                 weak_ptr_factory_.GetWeakPtr()));
435}
436
437void ConsumerManagementService::OnEnrollmentCompleted(EnrollmentStatus status) {
438  if (status.status() != EnrollmentStatus::STATUS_SUCCESS) {
439    LOG(ERROR) << "Failed to enroll the device."
440               << " status=" << status.status()
441               << " client_status=" << status.client_status()
442               << " http_status=" << status.http_status()
443               << " store_status=" << status.store_status()
444               << " validation_status=" << status.validation_status();
445    EndEnrollment(ENROLLMENT_STAGE_DM_SERVER_FAILED);
446    return;
447  }
448
449  EndEnrollment(ENROLLMENT_STAGE_SUCCESS);
450}
451
452void ConsumerManagementService::EndEnrollment(EnrollmentStage stage) {
453  Profile* profile = enrolling_profile_;
454  enrolling_profile_ = NULL;
455
456  SetEnrollmentStage(stage);
457  if (user_manager::UserManager::Get()->IsCurrentUserOwner())
458    ShowDesktopNotificationAndResetStage(stage, profile);
459}
460
461void ConsumerManagementService::ShowDesktopNotificationAndResetStage(
462    EnrollmentStage stage, Profile* profile) {
463  base::string16 title;
464  base::string16 body;
465  base::string16 button_label;
466  base::Closure button_click_callback;
467
468  if (stage == ENROLLMENT_STAGE_SUCCESS) {
469    title = l10n_util::GetStringUTF16(
470        IDS_CONSUMER_MANAGEMENT_ENROLLMENT_NOTIFICATION_TITLE);
471    body = l10n_util::GetStringUTF16(
472        IDS_CONSUMER_MANAGEMENT_ENROLLMENT_NOTIFICATION_BODY);
473    button_label = l10n_util::GetStringUTF16(
474        IDS_CONSUMER_MANAGEMENT_NOTIFICATION_MODIFY_SETTINGS_BUTTON);
475    button_click_callback = base::Bind(
476        &ConsumerManagementService::OpenSettingsPage,
477        weak_ptr_factory_.GetWeakPtr(),
478        profile);
479  } else {
480    title = l10n_util::GetStringUTF16(
481        IDS_CONSUMER_MANAGEMENT_ENROLLMENT_FAILURE_NOTIFICATION_TITLE);
482    body = l10n_util::GetStringUTF16(
483        IDS_CONSUMER_MANAGEMENT_ENROLLMENT_FAILURE_NOTIFICATION_BODY);
484    button_label = l10n_util::GetStringUTF16(
485        IDS_CONSUMER_MANAGEMENT_NOTIFICATION_TRY_AGAIN_BUTTON);
486    button_click_callback = base::Bind(
487        &ConsumerManagementService::TryEnrollmentAgain,
488        weak_ptr_factory_.GetWeakPtr(),
489        profile);
490  }
491
492  message_center::RichNotificationData optional_field;
493  optional_field.buttons.push_back(message_center::ButtonInfo(button_label));
494  Notification notification(
495      message_center::NOTIFICATION_TYPE_SIMPLE,
496      GURL(kEnrollmentNotificationUrl),
497      title,
498      body,
499      ui::ResourceBundle::GetSharedInstance().GetImageNamed(
500          IDR_CONSUMER_MANAGEMENT_NOTIFICATION_ICON),
501      blink::WebTextDirectionDefault,
502      message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT,
503                                 kEnrollmentNotificationId),
504      base::string16(),  // display_source
505      base::UTF8ToUTF16(kEnrollmentNotificationId),
506      optional_field,
507      new DesktopNotificationDelegate(kEnrollmentNotificationId,
508                                      button_click_callback));
509  notification.SetSystemPriority();
510  g_browser_process->notification_ui_manager()->Add(notification, profile);
511
512  SetEnrollmentStage(ENROLLMENT_STAGE_NONE);
513}
514
515void ConsumerManagementService::OpenSettingsPage(Profile* profile) const {
516  const GURL url(chrome::kChromeUISettingsURL);
517  chrome::NavigateParams params(profile, url, ui::PAGE_TRANSITION_LINK);
518  params.disposition = NEW_FOREGROUND_TAB;
519  chrome::Navigate(&params);
520}
521
522void ConsumerManagementService::TryEnrollmentAgain(Profile* profile) const {
523  const GURL base_url(chrome::kChromeUISettingsURL);
524  const GURL url = base_url.Resolve(kConsumerManagementOverlay);
525
526  chrome::NavigateParams params(profile, url, ui::PAGE_TRANSITION_LINK);
527  params.disposition = NEW_FOREGROUND_TAB;
528  chrome::Navigate(&params);
529}
530
531void ConsumerManagementService::NotifyStatusChanged() {
532  FOR_EACH_OBSERVER(Observer, observers_, OnConsumerManagementStatusChanged());
533}
534
535}  // namespace policy
536