[M108 Migration][VD] Support set time and time zone offset
[platform/framework/web/chromium-efl.git] / base / enterprise_util_mac.mm
1 // Copyright 2019 The Chromium Authors
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 "base/enterprise_util.h"
6
7 #import <OpenDirectory/OpenDirectory.h>
8
9 #include <string>
10 #include <vector>
11
12 #include "base/logging.h"
13 #include "base/mac/foundation_util.h"
14 #include "base/process/launch.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/sys_string_conversions.h"
18
19 namespace base {
20
21 bool IsManagedDevice() {
22   // MDM enrollment indicates the device is actively being managed. Simply being
23   // joined to a domain, however, does not.
24
25   // IsDeviceRegisteredWithManagementNew is only available after 10.13.4.
26   // Eventually switch to it when that is the minimum OS required by Chromium.
27   if (@available(macOS 10.13.4, *)) {
28     base::MacDeviceManagementStateNew mdm_state =
29         base::IsDeviceRegisteredWithManagementNew();
30     return mdm_state ==
31                base::MacDeviceManagementStateNew::kLimitedMDMEnrollment ||
32            mdm_state == base::MacDeviceManagementStateNew::kFullMDMEnrollment ||
33            mdm_state == base::MacDeviceManagementStateNew::kDEPMDMEnrollment;
34   }
35
36   base::MacDeviceManagementStateOld mdm_state =
37       base::IsDeviceRegisteredWithManagementOld();
38   return mdm_state == base::MacDeviceManagementStateOld::kMDMEnrollment;
39 }
40
41 bool IsEnterpriseDevice() {
42   // Domain join is a basic indicator of being an enterprise device.
43   DeviceUserDomainJoinState join_state = AreDeviceAndUserJoinedToDomain();
44   return join_state.device_joined || join_state.user_joined;
45 }
46
47 MacDeviceManagementStateOld IsDeviceRegisteredWithManagementOld() {
48   static MacDeviceManagementStateOld state = [] {
49     @autoreleasepool {
50       std::vector<std::string> profiler_argv{"/usr/sbin/system_profiler",
51                                              "SPConfigurationProfileDataType",
52                                              "-detailLevel",
53                                              "mini",
54                                              "-timeout",
55                                              "15",
56                                              "-xml"};
57
58       std::string profiler_stdout;
59       if (!GetAppOutput(profiler_argv, &profiler_stdout)) {
60         LOG(WARNING) << "Could not get system_profiler output.";
61         return MacDeviceManagementStateOld::kFailureAPIUnavailable;
62       };
63
64       NSArray* root = base::mac::ObjCCast<NSArray>([NSPropertyListSerialization
65           propertyListWithData:[SysUTF8ToNSString(profiler_stdout)
66                                    dataUsingEncoding:NSUTF8StringEncoding]
67                        options:NSPropertyListImmutable
68                         format:nil
69                          error:nil]);
70       if (!root) {
71         LOG(WARNING) << "Could not parse system_profiler output.";
72         return MacDeviceManagementStateOld::kFailureUnableToParseResult;
73       };
74
75       for (NSDictionary* results in root) {
76         for (NSDictionary* dict in results[@"_items"]) {
77           for (NSDictionary* device_config_profiles in dict[@"_items"]) {
78             for (NSDictionary* profile_item in
79                      device_config_profiles[@"_items"]) {
80               if (![profile_item[@"_name"] isEqual:@"com.apple.mdm"])
81                 continue;
82
83               NSString* payload_data =
84                   profile_item[@"spconfigprofile_payload_data"];
85               NSDictionary* payload_data_dict =
86                   base::mac::ObjCCast<NSDictionary>([NSPropertyListSerialization
87                       propertyListWithData:
88                           [payload_data dataUsingEncoding:NSUTF8StringEncoding]
89                                    options:NSPropertyListImmutable
90                                     format:nil
91                                      error:nil]);
92
93               if (!payload_data_dict)
94                 continue;
95
96               // Verify that the URL validates.
97               if ([NSURL URLWithString:payload_data_dict[@"CheckInURL"]])
98                 return MacDeviceManagementStateOld::kMDMEnrollment;
99             }
100           }
101         }
102       }
103
104       return MacDeviceManagementStateOld::kNoEnrollment;
105     }
106   }();
107
108   return state;
109 }
110
111 MacDeviceManagementStateNew IsDeviceRegisteredWithManagementNew() {
112   static MacDeviceManagementStateNew state = [] {
113     if (@available(macOS 10.13.4, *)) {
114       std::vector<std::string> profiles_argv{"/usr/bin/profiles", "status",
115                                              "-type", "enrollment"};
116
117       std::string profiles_stdout;
118       if (!GetAppOutput(profiles_argv, &profiles_stdout)) {
119         LOG(WARNING) << "Could not get profiles output.";
120         return MacDeviceManagementStateNew::kFailureAPIUnavailable;
121       }
122
123       // Sample output of `profiles` with full MDM enrollment:
124       // Enrolled via DEP: Yes
125       // MDM enrollment: Yes (User Approved)
126       // MDM server: https://applemdm.example.com/some/path?foo=bar
127       StringPairs property_states;
128       if (!SplitStringIntoKeyValuePairs(profiles_stdout, ':', '\n',
129                                         &property_states)) {
130         return MacDeviceManagementStateNew::kFailureUnableToParseResult;
131       }
132
133       bool enrolled_via_dep = false;
134       bool mdm_enrollment_not_approved = false;
135       bool mdm_enrollment_user_approved = false;
136
137       for (const auto& property_state : property_states) {
138         StringPiece property =
139             TrimString(property_state.first, kWhitespaceASCII, TRIM_ALL);
140         StringPiece state =
141             TrimString(property_state.second, kWhitespaceASCII, TRIM_ALL);
142
143         if (property == "Enrolled via DEP") {
144           if (state == "Yes")
145             enrolled_via_dep = true;
146           else if (state != "No")
147             return MacDeviceManagementStateNew::kFailureUnableToParseResult;
148         } else if (property == "MDM enrollment") {
149           if (state == "Yes")
150             mdm_enrollment_not_approved = true;
151           else if (state == "Yes (User Approved)")
152             mdm_enrollment_user_approved = true;
153           else if (state != "No")
154             return MacDeviceManagementStateNew::kFailureUnableToParseResult;
155         } else {
156           // Ignore any other output lines, for future extensibility.
157         }
158       }
159
160       if (!enrolled_via_dep && !mdm_enrollment_not_approved &&
161           !mdm_enrollment_user_approved) {
162         return MacDeviceManagementStateNew::kNoEnrollment;
163       }
164
165       if (!enrolled_via_dep && mdm_enrollment_not_approved &&
166           !mdm_enrollment_user_approved) {
167         return MacDeviceManagementStateNew::kLimitedMDMEnrollment;
168       }
169
170       if (!enrolled_via_dep && !mdm_enrollment_not_approved &&
171           mdm_enrollment_user_approved) {
172         return MacDeviceManagementStateNew::kFullMDMEnrollment;
173       }
174
175       if (enrolled_via_dep && !mdm_enrollment_not_approved &&
176           mdm_enrollment_user_approved) {
177         return MacDeviceManagementStateNew::kDEPMDMEnrollment;
178       }
179
180       return MacDeviceManagementStateNew::kFailureUnableToParseResult;
181     } else {
182       return MacDeviceManagementStateNew::kFailureAPIUnavailable;
183     }
184   }();
185
186   return state;
187 }
188
189 DeviceUserDomainJoinState AreDeviceAndUserJoinedToDomain() {
190   static DeviceUserDomainJoinState state = [] {
191     DeviceUserDomainJoinState state{false, false};
192
193     @autoreleasepool {
194       ODSession* session = [ODSession defaultSession];
195       if (session == nil) {
196         DLOG(WARNING) << "ODSession default session is nil.";
197         return state;
198       }
199
200       NSError* error = nil;
201
202       NSArray<NSString*>* all_node_names =
203           [session nodeNamesAndReturnError:&error];
204       if (!all_node_names) {
205         DLOG(WARNING) << "ODSession failed to give node names: "
206                       << error.localizedDescription.UTF8String;
207         return state;
208       }
209
210       NSUInteger num_nodes = all_node_names.count;
211       if (num_nodes < 3) {
212         DLOG(WARNING) << "ODSession returned too few node names: "
213                       << all_node_names.description.UTF8String;
214         return state;
215       }
216
217       if (num_nodes > 3) {
218         // Non-enterprise machines have:"/Search", "/Search/Contacts",
219         // "/Local/Default". Everything else would be enterprise management.
220         state.device_joined = true;
221       }
222
223       ODNode* node = [ODNode nodeWithSession:session
224                                         type:kODNodeTypeAuthentication
225                                        error:&error];
226       if (node == nil) {
227         DLOG(WARNING) << "ODSession cannot obtain the authentication node: "
228                       << error.localizedDescription.UTF8String;
229         return state;
230       }
231
232       // Now check the currently logged on user.
233       ODQuery* query = [ODQuery queryWithNode:node
234                                forRecordTypes:kODRecordTypeUsers
235                                     attribute:kODAttributeTypeRecordName
236                                     matchType:kODMatchEqualTo
237                                   queryValues:NSUserName()
238                              returnAttributes:kODAttributeTypeAllAttributes
239                                maximumResults:0
240                                         error:&error];
241       if (query == nil) {
242         DLOG(WARNING) << "ODSession cannot create user query: "
243                       << mac::NSToCFCast(error);
244         return state;
245       }
246
247       NSArray* results = [query resultsAllowingPartial:NO error:&error];
248       if (!results) {
249         DLOG(WARNING) << "ODSession cannot obtain current user node: "
250                       << error.localizedDescription.UTF8String;
251         return state;
252       }
253
254       if (results.count != 1) {
255         DLOG(WARNING) << @"ODSession unexpected number of user nodes: "
256                       << results.count;
257       }
258
259       for (id element in results) {
260         ODRecord* record = mac::ObjCCastStrict<ODRecord>(element);
261         NSArray* attributes =
262             [record valuesForAttribute:kODAttributeTypeMetaRecordName
263                                  error:nil];
264         for (id attribute in attributes) {
265           NSString* attribute_value = mac::ObjCCastStrict<NSString>(attribute);
266           // Example: "uid=johnsmith,ou=People,dc=chromium,dc=org
267           NSRange domain_controller =
268               [attribute_value rangeOfString:@"(^|,)\\s*dc="
269                                      options:NSRegularExpressionSearch];
270           if (domain_controller.length > 0) {
271             state.user_joined = true;
272           }
273         }
274
275         // Scan alternative identities.
276         attributes =
277             [record valuesForAttribute:kODAttributeTypeAltSecurityIdentities
278                                  error:nil];
279         for (id attribute in attributes) {
280           NSString* attribute_value = mac::ObjCCastStrict<NSString>(attribute);
281           NSRange icloud =
282               [attribute_value rangeOfString:@"CN=com.apple.idms.appleid.prd"
283                                      options:NSCaseInsensitiveSearch];
284           if (!icloud.length) {
285             // Any alternative identity that is not iCloud is likely enterprise
286             // management.
287             state.user_joined = true;
288           }
289         }
290       }
291     }
292
293     return state;
294   }();
295
296   return state;
297 }
298
299 }  // namespace base