1 // Copyright 2019 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/child_accounts/time_limits/app_time_policy_helpers.h"
6 
7 #include <utility>
8 
9 #include "base/logging.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/time/time.h"
12 #include "base/values.h"
13 #include "chrome/browser/chromeos/child_accounts/time_limits/app_types.h"
14 
15 namespace chromeos {
16 namespace app_time {
17 namespace policy {
18 
19 const char kUrlList[] = "url_list";
20 const char kAppList[] = "app_list";
21 const char kAppId[] = "app_id";
22 const char kAppType[] = "app_type";
23 const char kAppLimitsArray[] = "app_limits";
24 const char kAppInfoDict[] = "app_info";
25 const char kRestrictionEnum[] = "restriction";
26 const char kDailyLimitInt[] = "daily_limit_mins";
27 const char kLastUpdatedString[] = "last_updated_millis";
28 const char kResetAtDict[] = "reset_at";
29 const char kHourInt[] = "hour";
30 const char kMinInt[] = "minute";
31 const char kActivityReportingEnabled[] = "activity_reporting_enabled";
32 
PolicyStringToAppType(const std::string & app_type)33 apps::mojom::AppType PolicyStringToAppType(const std::string& app_type) {
34   if (app_type == "ARC")
35     return apps::mojom::AppType::kArc;
36   if (app_type == "BOREALIS")
37     return apps::mojom::AppType::kBorealis;
38   if (app_type == "BUILT-IN")
39     return apps::mojom::AppType::kBuiltIn;
40   if (app_type == "CROSTINI")
41     return apps::mojom::AppType::kCrostini;
42   if (app_type == "EXTENSION")
43     return apps::mojom::AppType::kExtension;
44   if (app_type == "PLUGIN-VM")
45     return apps::mojom::AppType::kPluginVm;
46   if (app_type == "WEB")
47     return apps::mojom::AppType::kWeb;
48 
49   NOTREACHED();
50   return apps::mojom::AppType::kUnknown;
51 }
52 
AppTypeToPolicyString(apps::mojom::AppType app_type)53 std::string AppTypeToPolicyString(apps::mojom::AppType app_type) {
54   switch (app_type) {
55     case apps::mojom::AppType::kArc:
56       return "ARC";
57     case apps::mojom::AppType::kBorealis:
58       return "BOREALIS";
59     case apps::mojom::AppType::kBuiltIn:
60       return "BUILT-IN";
61     case apps::mojom::AppType::kCrostini:
62       return "CROSTINI";
63     case apps::mojom::AppType::kExtension:
64       return "EXTENSION";
65     case apps::mojom::AppType::kPluginVm:
66       return "PLUGIN-VM";
67     case apps::mojom::AppType::kWeb:
68       return "WEB";
69     default:
70       NOTREACHED();
71       return "";
72   }
73 }
74 
PolicyStringToAppRestriction(const std::string & restriction)75 AppRestriction PolicyStringToAppRestriction(const std::string& restriction) {
76   if (restriction == "BLOCK")
77     return AppRestriction::kBlocked;
78   if (restriction == "TIME_LIMIT")
79     return AppRestriction::kTimeLimit;
80 
81   NOTREACHED();
82   return AppRestriction::kUnknown;
83 }
84 
AppRestrictionToPolicyString(const AppRestriction & restriction)85 std::string AppRestrictionToPolicyString(const AppRestriction& restriction) {
86   switch (restriction) {
87     case AppRestriction::kBlocked:
88       return "BLOCK";
89     case AppRestriction::kTimeLimit:
90       return "TIME_LIMIT";
91     default:
92       NOTREACHED();
93       return "";
94   }
95 }
96 
AppIdFromDict(const base::Value & dict)97 base::Optional<AppId> AppIdFromDict(const base::Value& dict) {
98   if (!dict.is_dict())
99     return base::nullopt;
100 
101   const std::string* id = dict.FindStringKey(kAppId);
102   if (!id || id->empty()) {
103     DLOG(ERROR) << "Invalid id.";
104     return base::nullopt;
105   }
106 
107   const std::string* type_string = dict.FindStringKey(kAppType);
108   if (!type_string || type_string->empty()) {
109     DLOG(ERROR) << "Invalid type.";
110     return base::nullopt;
111   }
112 
113   return AppId(PolicyStringToAppType(*type_string), *id);
114 }
115 
AppIdToDict(const AppId & app_id)116 base::Value AppIdToDict(const AppId& app_id) {
117   base::Value value(base::Value::Type::DICTIONARY);
118   value.SetKey(kAppId, base::Value(app_id.app_id()));
119   value.SetKey(kAppType, base::Value(AppTypeToPolicyString(app_id.app_type())));
120 
121   return value;
122 }
123 
AppIdFromAppInfoDict(const base::Value & dict)124 base::Optional<AppId> AppIdFromAppInfoDict(const base::Value& dict) {
125   if (!dict.is_dict())
126     return base::nullopt;
127 
128   const base::Value* app_info = dict.FindKey(kAppInfoDict);
129   if (!app_info || !app_info->is_dict()) {
130     DLOG(ERROR) << "Invalid app info dictionary.";
131     return base::nullopt;
132   }
133   return AppIdFromDict(*app_info);
134 }
135 
AppLimitFromDict(const base::Value & dict)136 base::Optional<AppLimit> AppLimitFromDict(const base::Value& dict) {
137   if (!dict.is_dict())
138     return base::nullopt;
139 
140   const std::string* restriction_string = dict.FindStringKey(kRestrictionEnum);
141   if (!restriction_string || restriction_string->empty()) {
142     DLOG(ERROR) << "Invalid restriction.";
143     return base::nullopt;
144   }
145   const AppRestriction restriction =
146       PolicyStringToAppRestriction(*restriction_string);
147 
148   base::Optional<int> daily_limit_mins = dict.FindIntKey(kDailyLimitInt);
149   if ((restriction == AppRestriction::kTimeLimit && !daily_limit_mins) ||
150       (restriction == AppRestriction::kBlocked && daily_limit_mins)) {
151     DLOG(ERROR) << "Invalid restriction.";
152     return base::nullopt;
153   }
154 
155   base::Optional<base::TimeDelta> daily_limit;
156   if (daily_limit_mins) {
157     daily_limit = base::TimeDelta::FromMinutes(*daily_limit_mins);
158     if (daily_limit && (*daily_limit < base::TimeDelta::FromHours(0) ||
159                         *daily_limit > base::TimeDelta::FromHours(24))) {
160       DLOG(ERROR) << "Invalid daily limit.";
161       return base::nullopt;
162     }
163   }
164 
165   const std::string* last_updated_string =
166       dict.FindStringKey(kLastUpdatedString);
167   int64_t last_updated_millis;
168   if (!last_updated_string || last_updated_string->empty() ||
169       !base::StringToInt64(*last_updated_string, &last_updated_millis)) {
170     DLOG(ERROR) << "Invalid last updated time.";
171     return base::nullopt;
172   }
173 
174   const base::Time last_updated =
175       base::Time::UnixEpoch() +
176       base::TimeDelta::FromMilliseconds(last_updated_millis);
177 
178   return AppLimit(restriction, daily_limit, last_updated);
179 }
180 
AppLimitToDict(const AppLimit & limit)181 base::Value AppLimitToDict(const AppLimit& limit) {
182   base::Value value(base::Value::Type::DICTIONARY);
183   value.SetKey(kRestrictionEnum,
184                base::Value(AppRestrictionToPolicyString(limit.restriction())));
185   if (limit.daily_limit())
186     value.SetKey(kDailyLimitInt, base::Value(limit.daily_limit()->InMinutes()));
187   const std::string last_updated_string = base::NumberToString(
188       (limit.last_updated() - base::Time::UnixEpoch()).InMilliseconds());
189   value.SetKey(kLastUpdatedString, base::Value(last_updated_string));
190 
191   return value;
192 }
193 
ResetTimeFromDict(const base::Value & dict)194 base::Optional<base::TimeDelta> ResetTimeFromDict(const base::Value& dict) {
195   if (!dict.is_dict())
196     return base::nullopt;
197 
198   const base::Value* reset_dict = dict.FindKey(kResetAtDict);
199   if (!reset_dict || !reset_dict->is_dict()) {
200     DLOG(ERROR) << "Invalid reset time dictionary.";
201     return base::nullopt;
202   }
203 
204   base::Optional<int> hour = reset_dict->FindIntKey(kHourInt);
205   if (!hour) {
206     DLOG(ERROR) << "Invalid reset hour.";
207     return base::nullopt;
208   }
209 
210   base::Optional<int> minutes = reset_dict->FindIntKey(kMinInt);
211   if (!minutes) {
212     DLOG(ERROR) << "Invalid reset minutes.";
213     return base::nullopt;
214   }
215 
216   const int hour_in_mins = base::TimeDelta::FromHours(1).InMinutes();
217   return base::TimeDelta::FromMinutes(hour.value() * hour_in_mins +
218                                       minutes.value());
219 }
220 
ResetTimeToDict(int hour,int minutes)221 base::Value ResetTimeToDict(int hour, int minutes) {
222   base::Value value(base::Value::Type::DICTIONARY);
223   value.SetKey(kHourInt, base::Value(hour));
224   value.SetKey(kMinInt, base::Value(minutes));
225 
226   return value;
227 }
228 
ActivityReportingEnabledFromDict(const base::Value & dict)229 base::Optional<bool> ActivityReportingEnabledFromDict(const base::Value& dict) {
230   if (!dict.is_dict())
231     return base::nullopt;
232   return dict.FindBoolPath(kActivityReportingEnabled);
233 }
234 
AppLimitsFromDict(const base::Value & dict)235 std::map<AppId, AppLimit> AppLimitsFromDict(const base::Value& dict) {
236   std::map<AppId, AppLimit> app_limits;
237 
238   const base::Value* limits_array = dict.FindListKey(kAppLimitsArray);
239   if (!limits_array) {
240     DLOG(ERROR) << "Invalid app limits list.";
241     return app_limits;
242   }
243 
244   base::Value::ConstListView list_view = limits_array->GetList();
245   for (const base::Value& dict : list_view) {
246     if (!dict.is_dict()) {
247       DLOG(ERROR) << "Invalid app limits entry. ";
248       continue;
249     }
250 
251     base::Optional<AppId> app_id = AppIdFromAppInfoDict(dict);
252     if (!app_id) {
253       DLOG(ERROR) << "Invalid app id.";
254       continue;
255     }
256 
257     base::Optional<AppLimit> app_limit = AppLimitFromDict(dict);
258     if (!app_limit) {
259       DLOG(ERROR) << "Invalid app limit.";
260       continue;
261     }
262 
263     app_limits.emplace(*app_id, *app_limit);
264   }
265 
266   return app_limits;
267 }
268 
269 }  // namespace policy
270 }  // namespace app_time
271 }  // namespace chromeos
272