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.
5 #include "base/enterprise_util.h"
7 #import <OpenDirectory/OpenDirectory.h>
12 #include "base/logging.h"
13 #include "base/mac/foundation_util.h"
14 #include "base/process/launch.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/sys_string_conversions.h"
21 bool IsMachineExternallyManaged() {
22 DeviceUserDomainJoinState join_state = AreDeviceAndUserJoinedToDomain();
23 return join_state.device_joined || join_state.user_joined;
26 MacDeviceManagementStateOld IsDeviceRegisteredWithManagementOld() {
28 std::vector<std::string> profiler_argv{"/usr/sbin/system_profiler",
29 "SPConfigurationProfileDataType",
36 std::string profiler_stdout;
37 if (!GetAppOutput(profiler_argv, &profiler_stdout)) {
38 LOG(WARNING) << "Could not get system_profiler output.";
39 return MacDeviceManagementStateOld::kFailureAPIUnavailable;
42 NSArray* root = base::mac::ObjCCast<NSArray>([NSPropertyListSerialization
43 propertyListWithData:[SysUTF8ToNSString(profiler_stdout)
44 dataUsingEncoding:NSUTF8StringEncoding]
45 options:NSPropertyListImmutable
49 LOG(WARNING) << "Could not parse system_profiler output.";
50 return MacDeviceManagementStateOld::kFailureUnableToParseResult;
53 for (NSDictionary* results in root) {
54 for (NSDictionary* dict in results[@"_items"]) {
55 for (NSDictionary* device_config_profiles in dict[@"_items"]) {
56 for (NSDictionary* profile_item in
57 device_config_profiles[@"_items"]) {
58 if (![profile_item[@"_name"] isEqual:@"com.apple.mdm"])
61 NSString* payload_data =
62 profile_item[@"spconfigprofile_payload_data"];
63 NSDictionary* payload_data_dict = base::mac::ObjCCast<
64 NSDictionary>([NSPropertyListSerialization
65 propertyListWithData:[payload_data
66 dataUsingEncoding:NSUTF8StringEncoding]
67 options:NSPropertyListImmutable
71 if (!payload_data_dict)
74 // Verify that the URL validates.
75 if ([NSURL URLWithString:payload_data_dict[@"CheckInURL"]])
76 return MacDeviceManagementStateOld::kMDMEnrollment;
82 return MacDeviceManagementStateOld::kNoEnrollment;
86 MacDeviceManagementStateNew IsDeviceRegisteredWithManagementNew() {
87 if (@available(macOS 10.13.4, *)) {
88 std::vector<std::string> profiles_argv{"/usr/bin/profiles", "status",
89 "-type", "enrollment"};
91 std::string profiles_stdout;
92 if (!GetAppOutput(profiles_argv, &profiles_stdout)) {
93 LOG(WARNING) << "Could not get profiles output.";
94 return MacDeviceManagementStateNew::kFailureAPIUnavailable;
97 std::vector<StringPiece> lines = SplitStringPiece(
98 profiles_stdout, "\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
100 bool enrolled_via_dep = false;
101 bool mdm_enrollment_not_approved = false;
102 bool mdm_enrollment_user_approved = false;
104 for (const auto& line : lines) {
105 std::vector<StringPiece> halves =
106 SplitStringPiece(line, ":", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
107 if (halves.size() != 2)
108 return MacDeviceManagementStateNew::kFailureUnableToParseResult;
109 StringPiece property = halves[0];
110 StringPiece state = halves[1];
112 if (property == "Enrolled via DEP") {
114 enrolled_via_dep = true;
115 else if (state != "No")
116 return MacDeviceManagementStateNew::kFailureUnableToParseResult;
117 } else if (property == "MDM enrollment") {
119 mdm_enrollment_not_approved = true;
120 else if (state == "Yes (User Approved)")
121 mdm_enrollment_user_approved = true;
122 else if (state != "No")
123 return MacDeviceManagementStateNew::kFailureUnableToParseResult;
125 // Ignore any other output lines, for future extensibility.
129 if (!enrolled_via_dep && !mdm_enrollment_not_approved &&
130 !mdm_enrollment_user_approved) {
131 return MacDeviceManagementStateNew::kNoEnrollment;
134 if (!enrolled_via_dep && mdm_enrollment_not_approved &&
135 !mdm_enrollment_user_approved) {
136 return MacDeviceManagementStateNew::kLimitedMDMEnrollment;
139 if (!enrolled_via_dep && !mdm_enrollment_not_approved &&
140 mdm_enrollment_user_approved) {
141 return MacDeviceManagementStateNew::kFullMDMEnrollment;
144 if (enrolled_via_dep && !mdm_enrollment_not_approved &&
145 mdm_enrollment_user_approved) {
146 return MacDeviceManagementStateNew::kDEPMDMEnrollment;
149 return MacDeviceManagementStateNew::kFailureUnableToParseResult;
151 return MacDeviceManagementStateNew::kFailureAPIUnavailable;
155 DeviceUserDomainJoinState AreDeviceAndUserJoinedToDomain() {
156 DeviceUserDomainJoinState state{false, false};
159 ODSession* session = [ODSession defaultSession];
160 if (session == nil) {
161 DLOG(WARNING) << "ODSession default session is nil.";
165 NSError* error = nil;
167 NSArray<NSString*>* all_node_names =
168 [session nodeNamesAndReturnError:&error];
169 if (!all_node_names) {
170 DLOG(WARNING) << "ODSession failed to give node names: "
171 << error.localizedDescription.UTF8String;
175 NSUInteger num_nodes = all_node_names.count;
177 DLOG(WARNING) << "ODSession returned too few node names: "
178 << all_node_names.description.UTF8String;
183 // Non-enterprise machines have:"/Search", "/Search/Contacts",
184 // "/Local/Default". Everything else would be enterprise management.
185 state.device_joined = true;
188 ODNode* node = [ODNode nodeWithSession:session
189 type:kODNodeTypeAuthentication
192 DLOG(WARNING) << "ODSession cannot obtain the authentication node: "
193 << error.localizedDescription.UTF8String;
197 // Now check the currently logged on user.
198 ODQuery* query = [ODQuery queryWithNode:node
199 forRecordTypes:kODRecordTypeUsers
200 attribute:kODAttributeTypeRecordName
201 matchType:kODMatchEqualTo
202 queryValues:NSUserName()
203 returnAttributes:kODAttributeTypeAllAttributes
207 DLOG(WARNING) << "ODSession cannot create user query: "
208 << mac::NSToCFCast(error);
212 NSArray* results = [query resultsAllowingPartial:NO error:&error];
214 DLOG(WARNING) << "ODSession cannot obtain current user node: "
215 << error.localizedDescription.UTF8String;
219 if (results.count != 1) {
220 DLOG(WARNING) << @"ODSession unexpected number of user nodes: "
224 for (id element in results) {
225 ODRecord* record = mac::ObjCCastStrict<ODRecord>(element);
226 NSArray* attributes =
227 [record valuesForAttribute:kODAttributeTypeMetaRecordName error:nil];
228 for (id attribute in attributes) {
229 NSString* attribute_value = mac::ObjCCastStrict<NSString>(attribute);
230 // Example: "uid=johnsmith,ou=People,dc=chromium,dc=org
231 NSRange domain_controller =
232 [attribute_value rangeOfString:@"(^|,)\\s*dc="
233 options:NSRegularExpressionSearch];
234 if (domain_controller.length > 0) {
235 state.user_joined = true;
239 // Scan alternative identities.
241 [record valuesForAttribute:kODAttributeTypeAltSecurityIdentities
243 for (id attribute in attributes) {
244 NSString* attribute_value = mac::ObjCCastStrict<NSString>(attribute);
246 [attribute_value rangeOfString:@"CN=com.apple.idms.appleid.prd"
247 options:NSCaseInsensitiveSearch];
248 if (!icloud.length) {
249 // Any alternative identity that is not iCloud is likely enterprise
251 state.user_joined = true;